Tutorial

E2E Testing Angular Applications with TestCafe

Published on November 20, 2017
author

Nataliia Krylova

E2E Testing Angular Applications with TestCafe

TestCafe is a free and open source Node.js tool for testing web applications. One of its main perks is that it takes about a minute to setup and to start testing (and it doesn’t use WebDriver).

It works with most popular operating systems and browsers. Tests are written in JavaScript or TypeScript.

In this article, we’ll cover testing Angular applications specifically. You will learn how to:

  • Setup test environment
  • Create basic tests using Angular selectors
  • Improve your tests with Page Model pattern
  • Debug the failed tests

Setup for Testing

First, make sure you have a Node.js version 4.x or higher installed.

We’ll need two npm modules: TestCafe itself, and a plugin with Angular selectors. Run this command:

$ npm i testcafe testcafe-angular-selectors

Let’s also install the Angular Augury extension to Google Chrome. We’ll use it to look up component selectors in the application’s component tree.

Component Tree view in Angular Augury

That’s it, we are ready to test!

Write a Simple Test

We’ll be testing a sample Book Collection Angular application, published here: https://miherlosev.github.io/e2e_angular. Our first test will be just a login form.

Login page of the sample application

Note that testcafe-angular-selectors and Angular Augry require an application running in development mode.

Let’s create an index.js file and add the following code:

index.js

import { AngularSelector, waitForAngular } from 'testcafe-angular-selectors';

fixture `Book Collection`
  .page('https://miherlosev.github.io/e2e_angular/')
  .beforeEach(async t => {
    await waitForAngular();
  });

test('Login', async t => {
  const loginForm = AngularSelector('bc-login-form');

  await t
    .typeText(loginForm.find('#md-input-1'), 'test')
    .typeText(loginForm.find('#md-input-3'), 'test')
    .click(loginForm.find('.mat-button'));
});

Here’s what happens. We start with importing Angular selectors. We’ll use them to address page elements with the component selectors.

In the fixture, we specify the tested page and use the beforeEach test hook. This hook is useful when you need to perform specific actions before each test run. In our case, the waitForAngular method allows us to wait until the Angular component tree is loaded. After that our test can find Login and Password fields by their component names, fill them and press the Login button.

Let’s run our test. Create a package.json file with the following content:

package.json
{
  "scripts": {
    "test": "testcafe chrome index.js"
  }
}

The test task runs tests from index.js in Google Chrome. Now run this command:

$ npm test

The first test has passed

TestCafe’s report shows us that our test has passed.

Improve the Test With the Page Model Pattern

Page Model is a pattern where you create an abstraction of the tested page, and use it to refer to page elements. It helps to save time supporting tests if the page markup changes.

Let’s create a page model for the Login page. The page has three elements: Username and Password inputs, and the Login button. We’ll represent the whole page as a JavaScript class, and its elements as selectors.

Create a page-model.js file and add the following code:

page-model.js
import { AngularSelector } from 'testcafe-angular-selectors';

export class LoginPage {
  constructor () {
    const loginForm = AngularSelector('bc-login-form');

    this.username = loginForm.find('#md-input-1');
    this.password = loginForm.find('#md-input-3');
    this.loginBtn = loginForm.find('.mat-button');
  }
}

We use the AngularSelector constructor. You can pass component selectors separated by spaces to this constructor, and it returns a native TestCafe selector. TestCafe selector allows additional filtering by tag names, IDs, etc. In our example, we use its find method to locate text fields and the button.

Now let’s refactor index.js. We’ve extracted selector related code from the test and moved it into the page model. After this the test code contains only interactions with page elements:

import { waitForAngular } from 'testcafe-angular-selectors';
import { LoginPage } from './page-model.js';

const loginPage = new LoginPage();

fixture `Book Collection`
  .page('https://miherlosev.github.io/e2e_angular/')
  .beforeEach(async t => {
    await waitForAngular();
});

test('Login', async t => {
  await t
    .typeText(loginPage.username, 'test')
    .typeText(loginPage.password, 'test')
    .click(loginPage.loginBtn);
});

Run the test with the same command, and see that it works just as it did before.

Create a More Complex Test

Our test doesn’t actually check anything at the moment. Let’s add an assertion: modify the test to find a specific book, and check that search results aren’t empty.

We’ll go on with the Page Model pattern, so let’s update page-model.js. First, add one more import in the beginning:

import { t } from 'testcafe';

t is a TestCafe object called TestContext which allows performing various actions, such as clicking and typing.

And then add the following at the end:

page-model.js
class BasePage {
  constructor () {
    const navigationItem = AngularSelector('bc-nav-item');

    this.toolbar = AngularSelector('bc-toolbar');
    this.sidenav = {
      myCollectionNavItem: navigationItem.find('a').withText('My Collection'),
      browseBooksNavItem:  navigationItem.find('a').withText('Browse Books')
    };

    this.openToolbarBtn = this.toolbar.find('button');
  }

  async navigateToBrowseBooksPage () {
    await t
      .click(this.openToolbarBtn)
      .click(this.sidenav.browseBooksNavItem);
  }
}

export class FindBookPage extends BasePage {
  constructor () {
    super();

    this.searchInput  = AngularSelector('bc-book-search').find('input');
    this.bookPreviews = AngularSelector('bc-book-preview');
  }
}

The BasePage class deals with UI elements that are shared across the other pages (toolbar and main menu). For all other pages, e.g. the FindBookPage, we can just extend this class.

Now modify index.js to look like this:

import { waitForAngular } from 'testcafe-angular-selectors';
import { LoginPage, FindBookPage, } from './page-model';

const loginPage = new LoginPage();
const findBookPage = new FindBookPage();

fixture `Book Collection`
  .page('https://miherlosev.github.io/e2e_angular/')
  .beforeEach(async t => {
    await waitForAngular();

    await t
      .typeText(loginPage.username, 'test')
      .typeText(loginPage.password, 'test')
      .click(loginPage.loginBtn);
  });

test("find a book", async t => {
  await findBookPage.navigateToBrowseBooksPage();

  await t
    .typeText(findBookPage.searchInput, 'The Hunger Games')
    .expect(findBookPage.bookPreviews.count).gt(0);
});

Note that we’ve moved authorization into the beforeEach test hook. We do it because our app requires authorization, and TestCafe runs each test in a clean environment to avoid flaky tests. We’ll keep it simple for the purpose of this tutorial, but there’s also a more advanced authorization mechanism called User Roles. It allows to isolate authentication and apply it whenever you need to switch the user account.

As you see, the code only contains the test logic. We don’t need to wait for page elements to load, animation, XHR completion or any other boilerplate code.

Now let’s run the test with npm test and see the results.

The second test has passed

The test has passed. By default TestCafe displays test results in a console. There are also plugins that provide custom reporters, for TeamCity, Slack, etc.

Locate Errors when a Test Fails

Let’s see what a failed test looks like. Change the last assertion so the test will fail:

.expect(findBookPage.bookPreviews.count).eql(0);

Now run the test.

TestCafe highlights the line where the test failed

For each failed test TestCafe highlights the failed step. It also reports the browser, callsite and other details allowing you to quickly find the reason for failure. To fix the test change the code back.

In this tutorial, we made simple tests with TestCafe and the Page Model pattern. For more examples and recommendations have a look at the official documentation.

You can find the full code from this tutorial on GitHub.

If you have any questions regarding TestCafe, feel free to ask them on the forum. For feature requests and bugs, go to GitHub issues page.

Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.

Learn more about our products

About the authors
Default avatar
Nataliia Krylova

author

While we believe that this content benefits our community, we have not yet thoroughly reviewed it. If you have any suggestions for improvements, please let us know by clicking the “report an issue“ button at the bottom of the tutorial.

Still looking for an answer?

Ask a questionSearch for more help

Was this helpful?
 
Leave a comment


This textbox defaults to using Markdown to format your answer.

You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!

Try DigitalOcean for free

Click below to sign up and get $200 of credit to try our products over 60 days!

Sign up

Join the Tech Talk
Success! Thank you! Please check your email for further details.

Please complete your information!

Become a contributor for community

Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.

DigitalOcean Documentation

Full documentation for every DigitalOcean product.

Resources for startups and SMBs

The Wave has everything you need to know about building a business, from raising funding to marketing your product.

Get our newsletter

Stay up to date by signing up for DigitalOcean’s Infrastructure as a Newsletter.

New accounts only. By submitting your email you agree to our Privacy Policy

The developer cloud

Scale up as you grow — whether you're running one virtual machine or ten thousand.

Get started for free

Sign up and get $200 in credit for your first 60 days with DigitalOcean.*

*This promotional offer applies to new accounts only.