Simplify your Protractor tests using Generator functions

by Auke — 3 minutes

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

Install jasmine-co dependency.

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

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;
  },
};

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.

Source code

Look at the complete source code https://github.com/baconcutter/protractor-generator-example.

meerdivotion

Cases

Blogs

Event