Sometimes it can be useful to verify the state of a web service in an end-to-end test. In my case, I was testing a web application that was using a third-party Javascript plugin that logged page views to a Rest service. I wanted to have some tests to verify that all our web pages did include the plugin, and that it was communicating with the Rest service properly when a new page was opened.
Because the webpages were written with AngularJS, Protractor was our framework of choice for our end-to-end test suite. But how to verify web service state in Protractor?
My first draft of a Protractor test looked like this:
[javascript]
var businessMonitoring = require(‘../util/businessMonitoring.js’);
var wizard = require(‘./../pageobjects/wizard.js’);
describe(‘Business Monitoring’, function() {
it(‘should log the page name of every page view in the wizard’, function() {
wizard.open();
expect(wizard.activeStepNumber.getText()).toBe(‘1’);
// We opened the first page of the wizard and we expect it to have been logged
expect(businessMonitoring.getMonitoredPageName()).toBe(‘/wizard/introduction’);
wizard.nextButton.click();
expect(wizard.completeStep.getAttribute(‘class’)).toContain(‘active’);
// We have clicked the ‘next’ button so the ‘completed’ page has opened, this should have // been logged as well
expect(businessMonitoring.getMonitoredPageName()).toBe(‘/wizard/completed’);
});
});
[/javascript]
The next thing I had to write was the businessMonitoring.js script, which should somehow make contact with the Rest service to verify that the correct page name was logged.
First I needed a simple plugin to make http requests. I found the ‘request’ npm package , which provides a simple API to make a http request like this:
[javascript]
var request = require(‘request’);
var executeRequest = function(method, url) {
var defer = protractor.promise.defer();
// method can be ‘GET’, ‘POST’ or ‘PUT’
request({uri: url, method: method, json: true}, function(error, response, body) {
if (error || response.statusCode >= 400) {
defer.reject({
error : error,
message : response
});
} else {
defer.fulfill(body);
}
});
// Return a promise so the caller can wait on it for the request to complete
return defer.promise;
};
[/javascript]
Then I completed the businessmonitoring.js script with a method that gets the last request from the Rest service, using the request plugin.
It looked like this:
[javascript]
var businessMonitoring = exports;
< .. The request wrapper with the executeRequest method is included here, left out here for brevity ..>
businessMonitoring.getMonitoredPageName = function () {
var defer = protractor.promise.defer();
executeRequest(‘GET’, ‘lastRequest’) // Calls the method which was defined above
.then(function success(data) {
defer.fulfill(data,.url);
}, function error(e) {
defer.reject(‘Error when calling BusinessMonitoring web service: ‘ + e);
});
return defer.promise;
};
[/javascript]
It just fires a GET request to the Rest service to see which page was logged. It is an Ajax call so the result is not immediately available, so a promise is returned instead.
But when I plugged the script into my Protractor test, it didn’t work.
I could see that the requests to the Rest service were done, but they were done immediately before any of my end-to-end tests were executed.
How come?
The reason is that Protractor uses the WebdriverJS framework to handle its control flow. Statements like expect(), which we use in our Protractor tests, don’t execute their assertions immediately, but instead they put their assertions on a queue. WebdriverJS first fills the queue with all assertions and other statements from the test, and then it executes the commands on the queue. Click here for a more extensive explanation of the WebdriverJs control flow.
That means that all statements in Protractor tests need to return promises, otherwise they will execute immediately when Protractor is only building its test queue. And that’s what happened with my first implementation of the businessMonitoring mock.
The solution is to let the getMonitoredPageName return its promise within another promise, like this:
[javascript]
var businessMonitoring = exports;
businessMonitoring.getMonitoredPageName = function () {
// Return a promise that will execute the rest call,
// so that the call is only done when the controlflow queue is executed.
var deferredExecutor = protractor.promise.defer();
deferredExecutor.then(function() {
var defer = protractor.promise.defer();
executeRequest(‘GET’, ‘lastRequest’)
.then(function success(data) {
defer.fulfill(data.url);
}, function error(e) {
defer.reject(‘Error when calling BusinessMonitoring mock: ‘ + e);
});
return defer.promise;
});
return deferredExecutor;
};
[/javascript]
Protractor takes care of resolving all the promises, so the code in my Protractor test did not have to be changed.
Qxperts. We empower companies to deliver reliable & high-quality software. Any questions? We are here to help! www.qxperts.io