Improving Protractor Tests Using Shared Functions and Promises

3 Comments

Larry Van Sickleby Larry Van Sickle, Senior Software Engineer

Protractor is used to write end-to-end tests for AngularJS applications. Protractor is a nodejs application that runs an application in a browser and interacts with the application using Selenium and WebDriverJS. Protractor is easily installed.

Protractor is used with Jasmine. Jasmine is a behavior driven development framework for testing JavaScript code.

Here is a very simple Protractor test that checks that the application runs and has the expected title:

describe('The Order Entry application', function() {

  beforeAll(function() {
    var url = 'http://' + browser.params.server + '/test';
    browser.get(url, 20000);
  });

  it('should have a title', function() {
    expect(browser.getTitle()).toEqual('Order Entry');
  });

});

The Protractor API is entirely asynchronous. All functions return promises. Most of the logic of dealing with promises is handled automatically by Protractor and Jasmine, and the test writer can write tests without explicitly dealing with promises and their control flow.   In the example test above, browser.get() and browser.getTitle() return promises, but the waiting for promises to be fulfilled is all handled by Protractor.   Filling in fields with sendKeys(), selecting menu options and check boxes, and clicking on buttons and links, the normal operations used to simulate user interaction, can be written sequentially without worrying about asynchronicity and promises. The asynchronous calls are handled automatically by Protractor.

However, there is one type of interaction with the browser that Protractor does not handle automatically. If a test needs to get values from the browser or the DOM outside of an expect(), then the test writer must explicitly handle the promise.   For example, we might want to count some type of element before and after an operation. To do that, we need to get and store the value before the operation.

In many applications a real test will want to compare before and after values of several elements on the page. For example, we might want to count not only line items in the order, but count messages and special offers, and also check the values of other elements. Each stored value requires a retrieval and a then().   For example:

describe('The Order Entry application', function() {

  it('should add a line item', function() {
    var lineItemCountBefore = 0;
    var messageCountBefore = 0;
    var lineItemCount = element.all(By.id('order-detail')).count();
    var messageCount = element.all(By.id('order-message')).count();
    lineItemCount.then(function(value) {
      lineItemCountBefore = value;
      messageCount.then(function(value) {
        messageCountBefore = value;
        addLineItemToOrder();
        expect(lineItemCount).toEqual(lineItemCountBefore + 1);
        expect(messageCount).toEqual(messageCountBefore + 1);
      });
    });
  });
});

lineItemCount and messageCount both return a promise that must be handled explicitly by then(). In the functions in then() we store the values for later use.

The then()’s have to be nested for correct operation of the test. This can make the testing code very deeply nested and more difficult to follow. It is also common to have several very similar tests testing different variations of the same operation, each test needing the same values. In both these cases, the DRY principle of software development dictates that such common or repeated code be put in a function.

In a Protractor test a common function should return a promise like the Protractor API. In such a function, create a Protractor promise, and fulfill or reject it. A test utility function to get the line item count and message count for the example above would look like:

var lineItemCount = element.all(By.id('order-detail')).count();
var messageCount = element.all(By.id('order-message')).count();

var getCounts = function() {
  var deferred = protractor.promise.defer();
  var result = {
    lineItemCount: 0,
    messageCount: 0,
  };
  lineItemCount.then(function(value) {
    result.lineItemCount = value;
    messageCount.then(function(value) {
      result.messageCount = value;
      deferred.fulfill(result);
    },
    function(reason) { deferred.reject(reason); });
  },
  function(reason) { deferred.reject(reason); });
  return deferred.promise;
};

We consolidate the two retrievals of line count and message count in one utility function, and return the two values in a single object, the result object. The test now becomes much simpler:

describe('The Order Entry application', function() {

  it('should add a line item', function() {
    var result = {};
    getCounts().then(function(value) {
      result = value;
      addLineItemToOrder();
      expect(lineItemCount).toEqual(result.lineItemCount + 1);
      expect(messageCount).toEqual(result.messageCount + 1);
    });
  });
});

In real tests where multiple counts and values need to be stored and compared, and with many similar tests of variations of the same operation, this technique greatly reduces the amount of test code and the nesting of the code, making the test code much easier to understand and maintain.

Author: bridge360blog

Software Changes Everything.... Bridge360 improves and develops custom application software. We specialize in solving complex problems at every phase of the software development lifecycle, removing roadblocks to help our clients’ software and applications reach their full potential in any market. The Bridge360 customer base includes software companies and world technology leaders, leading system integrators, federal and state government agencies, and small to enterprise businesses across the globe. Clients spanning industries from legal to healthcare, automotive to energy, and high tech to high fashion count on us to clear a path for success. Bridge360 was founded in 2001 (as Austin Test) and is headquartered in Austin, Texas with offices in Beijing, China.

3 thoughts on “Improving Protractor Tests Using Shared Functions and Promises

  1. Hi Larry!
    Finally I’ve found a great down-to-earth article on how promises actually work in protractor. Thanks a lot!

  2. Hi Larry!
    Finally I’ve found a great down to earth article on how promises work in protractor. Thanks a lot!

  3. i tried this code but i get error that getCounts is not defined.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s