Simplify your Protractor tests using Generator functions

Auke

Writing protractor tests can be quite difficult, because of all the asynchronous calls you have to deal with. Even promise-chaining does not help when your tests become longer and more complex. With Javascript generator functions it is possible to simplify your tests.

Example

Let’s say we have a todo-app created in AngularJS with two pages: a page with a list of todos and a page where a user can create a todo-item. We have two page objects created that can communicate with the pages in our Protractor tests. This is an example of an actual testcase with the use of promise chaining:

todo.spec.js - with promises
describe('todo test', function () {
  const todoListPage = require('../page-objects/todo-list.page.po');
  const todoCreatePage = require('../page-objects/todo-create.page.po');

  beforeEach(() => {
    browser.get('/');
  });

  it('should create a todo item', () => {
    //Check that number of items is 0 to begin with.
    todoListPage.open()
      .then(() => todoListPage.getNumberOfItems())
      .then(nrOfItems => {
        expect(nrOfItems).toBe(0);
      })
      .then(() => todoCreatePage.open())
      .then(() => todoCreatePage.createTodoItem('test'))
      .then(() => todoListPage.open())
      .then(() => todoListPage.getNumberOfItems())
      .then((nrOfItems) => {
        //number of todos should now be 1
        expect(nrOfItems).toBe(1);
      });
  });
});
todo.spec.js - with generators
describe('todo test', function () {
  const todoListPage = require('../page-objects/todo-list.page.po');
  const todoCreatePage = require('../page-objects/todo-create.page.po');

  beforeEach(() => {
    browser.get('/');
  });

  it('should create a todo item', function\*() {
    //Check that number of items is 0 to begin with.
    yield todoListPage.open();
    expect(yield todoListPage.getNumberOfItems()).toBe(0);

    //Add
    yield todoCreatePage.open();
    yield todoCreatePage.createTodoItem('test');
    yield todoListPage.open();

    //number of todos should now be 1
    expect(yield todoListPage.getNumberOfItems()).toBe(1);
  });
});

As you can see, the testcode is much easier to understand when the generator method is used.

How does it work?

The generator method which is defined with the function*(){} syntax can be used together with the jasmine-co library. jasmine-co wraps all methods and waits for each yield statement to be resolved before continuing to the next yield statement, even if this yield statement is resolved asynchronously.

Setup

1.1. Install jasmine-co dependency.

npm install jasmine-co --save-dev --save-exact

1.2. Install jasmine-co in the protractor.conf.js file.

Example protractor.conf.js
exports.config = {
  seleniumAddress: 'http://localhost:4444/wd/hub',

  getPageTimeout: 60000,
  allScriptsTimeout: 500000,
  baseUrl: 'http://localhost:3000',

  capabilities: {
    'browserName': 'chrome',
    'chromeOptions': {
      'args': \['--disable-extensions', '--start-maximized'\]
    }
  },

  specs: \['test/component-tests/\*\*/\*.spec.js'\],
  framework: 'jasmine',
  jasmineNodeOpts: {
    showColors: true,
    defaultTimeoutInterval: 3000000
  },

  onPrepare: function() {
	//This will wrap every test with jasmineCo(); Just like in the docs: https://www.npmjs.com/package/jasmine-co
    require('jasmine-co').install();

	//Define a shortcut for protractor.ExpectedConditions
    global.EC = protractor.ExpectedConditions;
  }
};

1.3. Start rewriting your tests incremently.

Important: It is not necessary to rewrite all your tests to generators, so you can try it out on one test first.

1. Source code

Release NPM package with git-flow TypeScript and ES6 import syntax