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:
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!
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:
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:
{
"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.
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:
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.
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:
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.
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.
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
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.
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!