How to write ATDD tests with cucumber-js, protractor and typescript

ATDD (Acceptance Test Driven Development) has been around for a while now. I use it quite a lot on projects that I work on. It helps me and others translate requirements into automated tests with the minimum amount of ceremony. We can talk to users about what they want and write that down in a format that they understand and we can automate.

One of the ways in which I use ATDD is with AngularJS. There is an end-to-end testing tool for AngularJS called Protractor that supports writing ATDD tests using a testframework called cucumber-js. It works pretty well with just javascript, but since we use Typescript a lot more now I figured, why not use typescript for cucumber tests as well?

What is cucumber and why should I care?

For those that don’t know cucumber or ATDD for that matter. Cucumber is framework that allows you to write feature files. A feature file looks like this:

1
2
3
4
5
Feature: Basic authentication
Scenario: Users can login using a username and password
Given I have a user account willem with password somethingElse
When I login with my username 'willem' and password 'somethingElse'
Then I am redirected to my personal dashboard

The idea behind ATDD is that you define acceptance tests in a readable format and automate them using code so that you don’t have to execute them manually everytime you want to validate the functionality of your application.

Using a readable format for your users helps you create a common understanding of what the application does for the user. Automating these specs into runnable tests closes the gap between the requirements and the tests. Often times people write specs and create a separate set of tests. The problem with this is that the tests tend to drift away from the original spec. This causes bugs that could easily be avoided if you make the specs the tests.

Setting up cucumber with protractor

In order to write acceptance tests for Angular 2 or AngularJS you need to use protractor. Protractor is a tool that links your tests to a webdriver which ultimately links your tests to a browser. This is useful since you can now navigate to a page in the application and communicate with the DOM. That way you can validate your app by querying if the right HTML elements were shown and much more.

To set protractor up you need to install the following NPM packages on your machine:

  • webdriver-manager
  • protractor

You can install these with the following command:

1
npm install -g protractor webdriver-manager

Make sure that you update the webdriver-manager after you installed it by running

1
webdriver-manager update

This will download the proper webdriver files for you so you can start running tests against the various supported browsers such as IE, Chrome and FireFox.

Next create a new protractor configuration file in your angular project.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
exports.config = {
baseUrl: 'http://localhost:9000',
// Specify the patterns for test files
// to include in the test run
specs: [
'features/**/*.feature'
],
// Use this to exclude files from the test run.
// In this case it's empty since we want all files
// that are mentioned in the specs.
exclude: [],
// Use cucumber for the tests
framework: 'custom',
frameworkPath: require.resolve('protractor-cucumber-framework'),
// Contains additional settings for cucumber-js
cucumberOpts: {
},
// These capabilities tell protractor about the browser
// it should use for the tests. In this case chrome.
capabilities: {
'browserName': 'chrome',
'chromeOptions': {
}
},
// This setting tells protractor to wait for all apps
// to load on the page instead of just the first.
useAllAngular2AppRoots: true
}

The file should point to the feature files in your project. I put those in the features folder, but you can place them somewhere else if you like. Next you need to set the framework to custom and point protractor to the cucumber-js framework using the frameworkPath setting.

Make sure that you install the protractor-cucumber-framework as part of your project using the following command:

1
npm install protractor-cucumber-framework --save-dev

Check your configuration by executing protractor protractor.conf.js. If you have a feature file in your project you should see protractor spin up a browser and spit out a bunch of text on the console about your feature file. This usually should involve a couple of green and yellow lines of text. The green lines show steps in the feature files that were executed. The yellow lines show steps that aren’t implemented yet.

Automating feature files with typescript

The cucumber-js framework is meant to be used with Javascript. In order to do this you need to write step definitions in javascript files that look like this:

1
2
3
4
5
module.exports = function() {
this.Given(/^I have a user account (.*) with password (.*)$/, function(username, password) {
//TODO: Do something useful
});
};

You include them in the protractor config file by specifying a setting called cucumberOpts that tells the cucumber framework to require your step definition javascript files.

1
2
3
cucumberOpts: {
require: 'features/step_definitions/**/*.js'
}

I found that javascript is good, but quite annoying to work with since there’s so much crap in the syntax to deal with. People do the weirdest things to their javascript code. Cucumber-js sadly isn’t an exception to that rule.

Typescript offers a much better way of defining step definitions for cucumber-js. But you have to do a little work for it to function properly.

First you need to install the ts-node and cucumber-js-tsflow packages in your project using the following command:

1
npm install --save-dev cucumber-js-tsflow ts-node

Next you need to modify the protractor configuration so that it includes the following cucumberOpts settings:

1
2
3
4
cucumberOpts: {
require: ['features/step_definitions/**/*.ts'],
compiler: 'ts:ts-node/register'
}

This configures cucumber so that it looks for step definitions in the features/step_definitions folder and tries to load the typescript files directly from there. The problem is that it can’t load typescript files under normal circumstances. However, you can specify a custom compiler for the cucumber framework. This is where ts-node comes in. When you configure cucumber-js to use the typescript interface from ts-node it is capable of loading the step definitions.

Time to write some step definitions in typescript:

1
2
3
4
5
6
7
8
9
10
11
import { binding, given, when, then } from 'cucumber-js-tsflow';
@binding()
class MyStepDefinitions {
@given(/^I have a user account (.*) with password (.*)$/)
givenIHaveAUserAccount(username:string, password:string): void {
//TODO: Do something useful
}
}
export = MyStepDefinitions;

This looks way easier to read and I can assure you it’s much more ergonomic to write. And luckely it is not much work to set up in the end.