---
title: "An Alternative to Protractor for Angular Projects"
url: "https://www.cypress.io/blog/an-alternative-to-protractor-for-angular-projects"
description: "Learn how to add Cypress end-to-end tests to Angular projects. Read “An Alternative to Protractor for Angular Projects” and more testing guides on the Cypress…"
---

# An Alternative to Protractor for Angular Projects

December 11, 2017

By Gleb Bahmutov

![An abstract image of blurry colors](https://storage.ghost.io/c/12/89/12899ca1-4786-4eea-b7e1-9c5268baa48a/content/images/2025/04/photo-1523821741446-edb2b68bb7a0.jpeg)

Fans of [Angular CLI](https://cli.angular.io/?ref=cypress-io.ghost.io) get Protractor end-to-end tests generated with each scaffolded project. In this blog post I will show how to add Cypress E2E tests instead with minimum effort (and TypeScript support)!

# Scaffolding a project

Scaffolding an Angular project using `ng new <name>` is a huge time saver. Just 4 commands give you a complete “Hello Angular” application with all recommended practices. Let us make an app - you can find the results in [bahmutov/ng-cli-hello](https://github.com/bahmutov/ng-cli-hello?ref=cypress-io.ghost.io) repository.

```shell
npm install -g @angular/cli
ng new ng-cli-hello
cd ng-cli-hello
ng serve
```

Open `[http://localhost:4200/](http://localhost:4200/?ref=cypress-io.ghost.io)` in the browser to see the running application - it even supports hot module reload!

Our application has a greeting, Angular logo image and a few links.

# Protractor tests

End-to-end tests are automatically scaffolded using [Protractor](https://www.protractortest.org/?ref=cypress-io.ghost.io). When we execute `ng e2e` command, Angular CLI takes care of all installation details, then runs the tests in the real browser (Chrome).

```shell
$ npm run e2e

> ng-cli-hello@0.0.0 e2e /ng-cli-hello
> ng e2e

** NG Live Development Server is listening on localhost:49152, open your browser on http://localhost:49152/ **
...

webpack: Compiled successfully.
[21:31:38] I/file_manager - creating folder /ng-cli-hello/node_modules/protractor/node_modules/webdriver-manager/selenium
[21:31:40] I/update - chromedriver: unzipping chromedriver_2.33.zip
[21:31:40] I/update - chromedriver: setting permissions to 0755 for /ng-cli-hello/node_modules/protractor/node_modules/webdriver-manager/selenium/chromedriver_2.33
[21:31:40] I/launcher - Running 1 instances of WebDriver
[21:31:40] I/direct - Using ChromeDriver directly...
Jasmine started

  ng-cli-hello App
    ✓ should display welcome message

Executed 1 of 1 spec SUCCESS in 0.993 sec.
[21:31:43] I/launcher - 0 instance(s) of WebDriver still running
[21:31:43] I/launcher - chrome #01 passed
```

The single test in the file [e2e/app.e2e-spec.ts](https://github.com/bahmutov/ng-cli-hello/blob/master/e2e/app.e2e-spec.ts?ref=cypress-io.ghost.io) confirms the greeting text.

```ts
import { AppPage } from './app.po';

describe('ng-cli-hello App', () => {
  let page: AppPage;

  beforeEach(() => {
    page = new AppPage();
  });

  it('should display welcome message', () => {
    page.navigateTo();
    expect(page.getParagraphText()).toEqual('Welcome to app!');
  });
});
```

Note the use of _a page object_ - which is defined in [e2e/app.po.ts](https://github.com/bahmutov/ng-cli-hello/blob/master/e2e/app.po.ts?ref=cypress-io.ghost.io). The page object abstracts common interactions with the application like opening the page or getting the greeting text.

```ts
import { browser, by, element } from 'protractor';

export class AppPage {
  navigateTo() {
    return browser.get('/');
  }

  getParagraphText() {
    return element(by.css('app-root h1')).getText();
  }
}
```

# Cypress tests

Recently, our team at Cypress has added TypeScript support to our Test Runner via [plugins and preprocessors](https://on.cypress.io/plugins-guide?ref=cypress-io.ghost.io). We have even released webpack and browserify [TypeScript recipes](https://on.cypress.io/recipes?ref=cypress-io.ghost.io), further simplifying the user workflow. Let us setup Cypress tests in less than 60 seconds.

```shell
npm i -D cypress
$(npm bin)/cypress open
```

Installing the [Cypress NPM module](https://www.npmjs.com/package/cypress?ref=cypress-io.ghost.io) should take just a few seconds on Mac, Linux or Windows platforms - it downloads an Electron-based binary for the current OS. When running Cypress for the very first time, it scaffolds e2e tests in a `cypress` folder.

We are not interested in the default example tests, so let’s go ahead and delete `example_spec.js` and create an empty `spec.ts` instead.

```shell
rm cypress/integration/example_spec.js
git touch cypress/integration/spec.ts
```

By default Cypress understands JavaScript and CoffeeScript source files. I want to write [TypeScript](https://www.typescriptlang.org/?ref=cypress-io.ghost.io), not copy and paste the example code and configure Cypress plugins. So I wrote NPM module [@bahmutov/add-typescript-to-cypress](https://github.com/bahmutov/add-typescript-to-cypress?ref=cypress-io.ghost.io) that does the Cypress preprocessor configuration using a post-install hook.

```shell
npm i -D @bahmutov/add-typescript-to-cypress
+ @bahmutov/add-typescript-to-cypress@1.0.3
```

During installation, it has written `cypress/plugins/index.js` to load TypeScript files using `ts-loader` (via [@cypress/webpack-preprocessor](https://github.com/cypress-io/cypress-webpack-preprocessor?ref=cypress-io.ghost.io)).

```js
const wp = require('@cypress/webpack-preprocessor')

const webpackOptions = {
  resolve: {
    extensions: ['.ts', '.js']
  },
  module: {
    rules: [
      {
        test: /\.ts$/,
        exclude: [/node_modules/],
        use: [
          {
            loader: 'ts-loader'
          }
        ]
      }
    ]
  }
}

module.exports = on => {
  const options = {
    webpackOptions
  }
  on('file:preprocessor', wp(options))
}
```

Now we can start writing tests. Open Cypress again, click on `spec.ts` in the list of tests and let it watch the tests in the spec file. First, let us confirm that the page loads. Note, during tests I have `ng serve` still running - the application is available at `localhost:4200`.

```ts
// cypress/integration/spec.ts
it('loads', () => {
  cy.visit('http://localhost:4200');
});
```

**note:** if your editor’s IntelliSense (I am using VS Code) does not pick up the definition for the `cy` or `Cypress` objects automatically, add to the root `tsconfig.json` file `"include": ["node_modules/cypress"]` folder. Or create `tsconfig.json` right in the `cypress` folder to have local TypeScript settings for the end-to-end tests. Here is my [tsconfig.json](https://github.com/bahmutov/ng-cli-hello/blob/master/cypress/tsconfig.json?ref=cypress-io.ghost.io) file.

```json
{
  "extends": "../tsconfig.json",
  "include": [
    "integration/*.ts",
    "support/*.ts",
    "../node_modules/cypress"
  ]
}
```

With TypeScript definitions found, our editor can show helpful information as we type Cypress commands, or when we hover over the code.

If you find an error in our typings, please take a minute to fix them in the [index.d.ts](https://github.com/cypress-io/cypress/tree/develop/cli/types?ref=cypress-io.ghost.io) file and open a pull request.

**Even this simple test above is useful:**

-   It runs in a real browser: Electron, Chrome (support for other browsers is [coming](https://github.com/cypress-io/cypress/issues/310?ref=cypress-io.ghost.io))
-   It only passes if the server returns an HTML page with [successful HTTP code](https://on.cypress.io/visit?ref=cypress-io.ghost.io#Rules)

**Let us confirm the greeting text.**

```ts
it('loads', () => {
  cy.visit('http://localhost:4200');
  cy.get('app-root h1').contains('Welcome to app!');
});
```

Here is where Cypress really shines - it is developer focused tool. It shows every step of the test in its Command Log on the left, and if I hover over a step, like “CONTAINS” the test area highlights _the current element being tested_.

The Protractor tests use a page object to abstract the implementation (like the exact page selectors) from the test. We can abstract interaction with the page the same way by moving utility functions into the `cypress/support` folder.

I am a strong believer in utility _functions_ rather than _page objects_ - I think using functions simplifies code a lot.

```ts
// cypress/support/po.ts
// we could place this url into cypress.json as "baseUrl"
const url = 'http://localhost:4200';

export const navigateTo = () => cy.visit(url);

export const getGreeting = () => cy.get('app-root h1');
```

Let us rewrite the greeting test using these functions.

```ts
// cypress/integration/spec.ts
import { navigateTo, getGreeting } from '../support/po';

describe('Hello Angular', () => {
  beforeEach(navigateTo);

  it('should display welcome message', () => {
    getGreeting().contains('Welcome to app!');
  });
});
```

Let us add another test - to confirm that we have 3 links on the page.

```ts
it('has 3 links', () => {
  cy.get('app-root li a').should('have.length', 3);
});
```

Great! The Angular project now can take full advantage of [Cypress’ extensive command API](https://on.cypress.io/api?ref=cypress-io.ghost.io), video recording, screenshots on failure, CI support, and many other [developer-friendly features](https://www.cypress.io/features/?ref=cypress-io.ghost.io).

If you like Cypress, [star it on GitHub](https://github.com/cypress-io/cypress?ref=cypress-io.ghost.io) or [tweet about it](https://www.cypress.io/?ref=cypress-io.ghost.io#testimonials). If you feel Protractor is still a better tool for end-to-end testing - [tell us why](https://github.com/cypress-io/cypress/issues?ref=cypress-io.ghost.io).