While this tutorial has content that we believe is of great benefit to our community, we have not yet tested or edited it to ensure you have an error-free learning experience. It's on our list, and we're working on it! You can help us out by using the "report an issue" button at the bottom of the tutorial.

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.

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.

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

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 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.

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.