Fast and Furious E2E: Using Testcafe for Easy End-to-End Testing

Building automated tests is the liver and onions of the programming world. Everyone agrees that it’s good for you, but no one really wants to get into it. I find this is takes place for three reasons: First, we often aren’t sure what to test when building new features. Second, setting up the testing environments for the modern web can be difficult, especially ones for end-to-end (E2E) testing. Finally, setting up the test can sometimes be more difficult and time-consuming than building that new feature.

Here, I’m going to show you how setting up an E2E suite for web applications and libraries is now much easier, and I’m going to show you a pattern for testing software libraries using the web mapping library Leaflet as an example.

We’re going to build a testing environment, we’re going to build a testing sandbox, and we’re going to build some tests. In less than an hour.

Note: You should have a decent understanding of Node JS, Javascript, and Git in order to get the best out of this tutorial.

Understanding Testing

If you’re new to automated testing, you should take a moment to learn the differences between the types of test which you would run. The four distinct categories of tests are Static Testing, Unit Testing, Integration Testing, and End to End Testing. The first type, Static Testing, deals with errors a programmer may make while typing the code which linters can observe as bad, such as syntax or patterns. Unit Testing is the next step up, where you are looking at a single function in isolation. Integration Testing looks at many of these functions when together. And finally, E2E looks at what results from specific interactions. If you want to learn more about this, then I suggest reading into this article by Kent C Dodds, who brilliantly summarizes this topic.

What is Testcafe?

Image result for testcafe logo

Testcafe is a fully-integrated E2E testing library written for NodeJS (Node). It was born from the difficulty of building testing E2E platforms which relied on other technologies. This platform ensures that teams can focus chiefly on the tests, as opposed to the framework which runs the tests. Testcafe comes from Developer Express, a services shop that specializes in dashboards and other user interface components for the Microsoft proprietary ecosystem.

There are many extensions available for this testing suite including Browserstack. Also, it offers a UI-based testing tool for automated E2E tests at cost. Because it operates as a standalone E2E entity, it doesn’t need to integrate with modern Javascript frameworks. But for special cases, there is a rich plugin ecosystem that allows users to integrate if this is something that’s really needed.

Simply put, where all the other testing suites require too many steps, Testcafe is easy. Download the library, set up the tests, run. You don’t have to assemble the vacuum. You just have to decide what you’re going to vacuum, how to turn it on, then point.

Building E2E Tests

If you are stuck, this github repository contains stepped solutions for each of the sections of this tutorial.

In this tutorial, we’re going to build fictional E2E tests for Leaflet, where Testcafe is used to capture changes to DOM elements as well as network request. To follow this tutorial, you’re going to require the following:

  1. A text editor. My preference is VSCode.
  2. A command line terminal. This tutorial is written for a Mac or Linux machine.
  3. Git. It comes with Linux or Mac, but Windows users can find it here
  4. NodeJS, at least at version 8.15 (lts/carbon).

This list makes up a common toolsuite for developers. If you’re building applications in React, Ember, or Vue, there’s a good chance you already have all of these things.

Start the project

To start things, you’re going to want to open your preferred terminal, and start a new directory:

mkdir e2e-sample && cd e2e-sample
git init
touch README.md && echo "# End to End Testing with Testcafe" > README.md
npm init

This will then start Node’s CLI process for building a new application. Run through all of the questions, and once finished, add a .gitignore file to ensure we don’t commit the wrong things.

wget https://raw.githubusercontent.com/github/gitignore/master/Node.gitignore<br>mv Node.gitignore .gitignore

This is a good time to commit.

git add . && git commit -m “Basic repo"

Install the Dependencies (branch)

In order to run Testcafe, all you need is something capable of serving HTML from static files and Testcafe itself. The following is all we need to do our thing.

npm i -D testcafe http-server

This installs the dependencies as “dev-dependencies”. This means that these tools are part of the development environment, but will not be built as part of the production environment.

Build the Sandbox (branch)

Because we’re testing a Leaflet–a Javascript library–as opposed to a client application, we need to construct a sandbox for the purposes of testing. To start the sandbox, we’re going to want to build a new directory, add some HTML and JS that uses the library, then add a NPM command which serves the sandbox:

mkdir sandbox && touch ./sandbox/index.html ./sandbox/index.js

From inside your text editor, add the HTML and JS that makes up the sandbox, which we will gloss over as this is a testing tutorial. You can find it in this gist

Finally, modify the package.json file to run the sandbox with http-server. Add the following line into the “scripts” object:

"sandbox": "http-server ./sandbox -p 3001"

With this line in your package.json file, you should be able to run npm run sandbox in the terminal window.

sandbox

If the server starts, you should be able to go to http://localhost:3001, and see something like this:

sandbox

This is a sandbox that tests the leaflet library. It simulates a pan to a random coordinate, a random zoom, changing the layer, and adding a marker to the centre of the map at the last pan.

Now is a good time to commit.

git add . && git commit -m "Functional sandbox in leaflet"

Create Your First Test (branch)

At this point, we have a testing toolchain, and we have a sandbox environment to run those tests. The next step here is to build the tests. The first test ensures that we have, in fact, a working E2E environment. We’re going to add the first test file, then we’re going to add a script, and then we’re going to write the test.

mkdir tests && touch ./tests/1-map-render.jS

Now, we’re going to modify our lone “test” script in our package.json file to run all files in the test directory. Replace the scripts entry “test” with the following:

"test": "testcafe chrome ./tests/** --app \"http-server ./sandbox -p 3001 -s\"",

This gets us the test command. In lieu of chrome, Testcafe can also test against other browsers. Browsers such as chrome:headless, firefox, firefox:headless, safari, will all work so long as the browser is installed on your computer. Finally, we’re going to write the tests. In the new first testing JS file, we first want to declare the fixture, or the thing we wish to test against. Add the following to your test file, 1-map-render.js:

fixture `Leaflet Map - Testing Render`<br>    .page `http://localhost:3001`;

This indicates the name of the fixture, and where the mapping library can load it from. With this, we can add the test, below the fixture:

test('The testing environment works', async t => {});

This is a boilerplate test. Using this, we can run the command npm run test in the terminal. If successful, we should see the following:

test-1

This proves that we have a functional E2E environment, and we can go forth and build real tests. At this point, it is a good time to commit:

git add . && git commit -m "My first test"

Now we can add more tests!

Test DOM Elements (branch)

Now that we have a testing environment, we can start to test all the things! Here, we’re going to test whether a marker appears when we press the marker appearance button.

The first step is to have Testcafe press a button. In the sandbox, I’ve given all the buttons on the right hand side unique ID’s in the HTML for this very purpose. First, we’re going to need to import the Selector object from Testcafe. Write the following at the top of the 1-map-render.js:

import { Selector } from 'testcafe';

Now add the following code to the bottom:

test('The marker is visible', async t => {
 await t
 .click(addMarker)
 .wait(500)
});

Now try running npm run test. If successful, you should see the following for half a second:

test-sandbox-leaflet

Notice that you can see a mouse cursor clicking on the DOM element #addMarker. We now have made an action, but we need to go further and ensure that the marker populates and that it comes from the correct source. To verify whether it populates, we can write the following:

test('The marker is visible', async t => {
 await t
 .click(addMarker)
 .expect(Selector('img.leaflet-marker.icon'))
 .ok('The marker must exist')

})

This is our first expectation. We’re expecting the selector to exist and not return null. If it isn’t ok, then we will indicate to the tester that this fails. Now we want to add another test that determines whether the marker populates from the correct source:

test('The marker image pulls from the correct source', async t => {
 await t
   .click(addMarker)
   .expect(Selector('.leaflet-marker-icon')
   .withAttribute('src', 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.0.0/images/marker-icon.png'))
   .ok('The marker must come from the proper source')
})

Run this test. Once you have 4/4 working tests, this would be an excellent time to commit before we move to the final section: Network Requests.

git add .
git commit -m "Test marker properties"

Testing Network Requests (branch)

Because this introduction also seeks to test the Leaflet JS library, it would be remiss if we didn’t test for outgoing network requests on certain events. We want to know if leaflet loads the desired tiles, and whether it loads a different set of tiles when we load a second layer. Let’s start by creating a new test file and adding some code:

touch ./tests/2-tile-loading.js

For this part, we want to import the request logger object from Testcafe with a fixture and a selector. Start your file with the following:

import { RequestLogger, Selector } from 'testcafe';


fixture`Test network requests for leaflet`
 .page`http://localhost:3001`;
 
 const addCycleLayer = Selector('#addCycleLayer');

We have the start of the file, and we now have a fixture. Now we’re going to build two request logger objects:

const osmLogger = RequestLogger(/\.tile\.openstreetmap\.org/, {
 logRequestHeaders: true,
});


const cycleMapLogger = RequestLogger(/tile\.opencyclemap\.org/, {
 logRequestHeaders: true,
});

These are objects which capture outgoing requests and use regular expressions (regex) to compare strings to patterns. Finally, we use the first logger to build a test:

test.requestHooks(osmLogger)('Test outgoing osm requests on map load...', async t => {
 await t
   .wait(200)
   .expect(osmLogger.count(() => true))
   .gt(5, 'Must request more than 5 tiles from OSM')
})

Because we’re monitoring network requests as opposed to DOM elements, the syntax changes. We’re using the requestHooks higher-order function, as opposed to the vanilla test function for the testing. We’re injecting a logger which monitors network requests that follow a specific pattern. In this test, we want to ensure that at least 5 tiles are being pulled from the correct server.

Let’s now test what happens when we load tiles from a different source. Add the following to the end of the file:

test.requestHooks(cycleMapLogger)('Test outgoing opencycle requests on layer load', async t => {
 await t
 .click(addCycleLayer)
 .wait(200)
 .expect(cycleMapLogger.count(() => true))
 .gt(5, 'Must request more than 5 tiles from OpenCycleMap')
})

With this, if you’re able to get 5/5 tests working, then you’ve succeeded in building an End-to-End testing environment for a client library using a custom sandbox. In Sparkgeo, we use these tests on our Maptiks products before they meet our customers. Congratulations, you are now on your way to making your software more robust using end-to-end testing!