Better E2E testing with TestCafe

marques woodson - May 27 '19 - - Dev Community

If you want to learn more about TestCafe, please check out my course End-to-end Web Testing with TestCafe: Getting Started on Pluralsight.

TestCafe is a Node.JS tool for running end-to-end tests in the browser.

Why I like TestCafe

With TestCafe I can write all of my code in Typescript or the latest (ES2017) JS. When I can write my E2E tests in the same manner that I write my application code, I see it as a huge win. I use Typescript in my application code to reduce confusion around what my code is actually doing so that the rest of my team can better understand my code. Using these same principles in E2E testing seems like a no-brainer. Also, IntelliSense :)

One of my favorite parts about being able to use more modern Javascript in my tests is the use of async/await. Since most of the actions that are run during a test return promises, my code looks like this:

// good
test('Should be able to login', async t => {
  await t.typeText(Selector('#username'));
  await t.click(Selector('#login-button'));
  //Or chain the calls
  await t.typeText(Selector('#username')).click(Selector('#login-button'));
});

Instead of using callbacks:

// booo!
test('Should be able to login', t => {
  t.typeText(Selector('#username')).then(() => {
    t.click(Selector('#login-button'));
  });
});

Testcafe's ability to automatically wait for pages to load, XHR requests to finish and elements to appear without me having to set up timeouts makes my life so much easier. I also like the feature of slowing down a test's execution, so that you can easily see what is happening in the browser.

Speed to setup

To get started, all you have to do is install TestCafe with npm:

$ npm install -g testcafe

Node.js is your only dependency. One of my favorite parts is that you DON'T need webdriver installed! You literally just npm install TestCafe and you're ready to go.

Each test starts with a fixture. A fixture is basically a test category, and every test file requires one or more fixtures:

fixture(fixtureName);
// or
fixture`fixtureName`;

Now you can start adding tests:

fixture`Login Functionality`;
test('Should be able to login', async t => {
  await t
    .typeText(Selector('#username'))
    .click(Selector('#login-button'))
    .expect(true)
    .ok(); //This is just an example assertion. Don't do   this in real tests.
});
fixture`Logout functionality`;
test('Should be able to logout', async t => {
  await t
    .click(Selector('#logout-button'))
    .expect(true)
    .ok();
});

Here's a link to the test API that (https://devexpress.github.io/testcafe/documentation/test-api/) shows all of the available testing capabilities.

Page Models

Setting up page models is still the recommended practice in-browser tests. A page model is basically a file that holds references to browser elements (buttons, links, anything that you're testing will be interacting with). I also like to add page interaction methods to the page models as well. Doing so will keep your tests cleaner and smaller.

import { Selector } from 'testcafe';
export default class HomeModel {
  constructor() {
    this.signInLink = Selector('#sign-in');
    this.username = Selector('input[type=“text”]');
    this.password = Selector('.password');
    this.submitButton = Selector('#submit');
    this.forgotPasswordLink = Selector('.forgot-password');
  }
  async loginUser(t: TestController) {
    await t
    .typeText(this.username, 'someUser')
    .typeText(this.password, 'password123')
    .click(this.submitButton);
  }
}
//Or if you prefer Typescript
export default class HomeModel {
  signInLink: Selector;
  forgotPasswordLink: Selector;

  constructor() {
    this.signInLink = Selector('#sign-in');
    this.forgotPasswordLink = Selector('.forgot-password');
  }
}

In our page model above, we define page elements in the constructor using the Selector API from TestCafe. Then we have a loginUser method that interacts with those page elements. This means that in our test, we can just write:

import HomeModel from './home-model';
const Home = new HomeModel();
 //Test fixture code
test('Loggin in', async t => {
  await Home.loginUser(t);
  await t.expect().ok();
});

The reason we want this is so that if the application code every changes, you should only need to update your page models and not all of your tests. The Selector API is fairly robust on its own, but if you're using a framework like React, AngularJS, Angular, Vue, or Aurelia, TestCafe has you covered there (https://devexpress.github.io/testcafe/documentation/test-api/selecting-page-elements/framework-specific-selectors.html). There are selector libraries specific to each of those frameworks developed by the community.

You can still run your tests remotely if need be. We use SauceLabs, but I'm sure the other players in that space are just as good.

If you are running browser/e2e tests (and you really should be) then I recommend trying out Testcafe. It's fast to set up and works with the most popular JS frameworks. If you want strongly typed tests, you can use Typescript without any extra Typescript configuration (it's bundled in the TestCafe package).

I hope this post helps out anyone looking at options for E2E testing their application. Feel free to leave a comment if I got anything wrong or if you have any questions. FO Thanks for reading :)

Follow me on twitter 😀

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .