When you’re running end-to-end tests, chances are that sometimes you need to set up the system before running the actual test code. It can involve cleaning up after previous executions, going through some data setup “wizard” or just calling the raw server API directly. Here’s how you can do it with Protractor.
Protractor is a slick piece of technology that makes end-to-end testing pretty enjoyable. It wires together Node, Selenium (via WebDriverJS) and Jasmine, and on top of that it provides some very useful extensions for testing Angular apps and improving areas where Selenium and Jasmine are lacking.
To make this concrete, let’s say that we want to execute two calls to the server before interacting with the application. One of them removes everything from database, another kicks off a procedure that fills it with some well-known initial state. Let’s write some naive code for it.
Using an HTTP Client
var request = require('request'); describe("Sample test", function() { beforeEach(function() { var jar = request.jar(); var req = request.defaults({ jar : jar }); function post(url, params) { console.log("Calling", url); req.post(browser.baseUrl + url, params, function(error, message) { console.log("Done call to", url); }); } function purge() { post('api/v1/setup/purge', { qs : { key : browser.params.purgeSecret } }); } function setupCommon() { post('api/v1/setup/test'); } purge(); setupCommon(); }); it("should do something", function() { expect(2).toEqual(2); }); });
Since we’re running on Node, we can (and will) use its libraries in our tests. Here I’m using request, a popular HTTP client with the right level of abstraction, built-in support for cookies etc. I don’t need cookies for this test case – but in real life you often do (e.g. log in as some admin user to interact with the API), so I left that in.
What we want to achieve is running the “purge” call first, then the data setup, then move on to the actual test case. However, in this shape it doesn’t work. When I run the tests, I get:
Starting selenium standalone server... Selenium standalone server started at http://192.168.15.120:58033/wd/hub Calling api/v1/setup/purge Calling api/v1/setup/test . Finished in 0.063 seconds 1 test, 1 assertion, 0 failures Done call to api/v1/setup/purge Done call to api/v1/setup/test Shutting down selenium standalone server.
It’s all wrong! First it starts the “purge”, then it starts the data setup without waiting for purge to complete, then it runs the test (the little dot in the middle), and the server calls finish some time later.
Making It Sequential
Well, that one was easy – the HTTP is client is asynchronous, so that was to be expected. That’s nothing new, and finding a useful synchronous HTTP client on Node isn’t that easy. We don’t need to do that anyway.
One way to make this sequential is to use callbacks. Call purge, then data setup in its callback, then the actual test code in its callback. Luckily, we don’t need to visit the callback hell either.
The answer is promises. WebDriverJS has nice built-in support for promises. It also has the concept of control flows. The idea is that you can register functions that return promises on the control flow, and the driver will take care of chaining them together.
Finally, on top of that Protractor bridges the gap to Jasmine. It patches the assertions to “understand” promises and plugs them in to the control flow.
Here’s how we can improve our code:
var request = require('request'); describe("Sample test", function() { beforeEach(function() { var jar = request.jar(); var req = request.defaults({ jar : jar }); function post(url, params) { var defer = protractor.promise.defer(); console.log("Calling", url); req.post(browser.baseUrl + url, params, function(error, message) { console.log("Done call to", url); if (error || message.statusCode >= 400) { defer.reject({ error : error, message : message }); } else { defer.fulfill(message); } }); return defer.promise; } function purge() { return post('api/v1/setup/purge', { qs : { key : browser.params.purgeSecret } }); } function setupCommon() { return post('api/v1/setup/test'); } var flow = protractor.promise.controlFlow(); flow.execute(purge); flow.execute(setupCommon); }); it("should do something", function() { expect(2).toEqual(2); }); });
Now the post
function is a bit more complicated. First it initializes a deferred object. Then it kicks off the request to server, providing it with callback to fulfill or reject the promise on the deferred. Eventually it returns the promise. Note that now purge
and setupCommon
now return promises.
Finally, instead of calling those functions directly, we get access to the control flow and push those two promise-returning functions onto it.
When executed, it prints:
Starting selenium standalone server... Selenium standalone server started at http://192.168.15.120:53491/wd/hub Calling api/v1/setup/purge Done call to api/v1/setup/purge Calling api/v1/setup/test Done call to api/v1/setup/test . Finished in 1.018 seconds 1 test, 1 assertion, 0 failures Shutting down selenium standalone server.
Ta-da! Purge, then setup, then run the test (again, that little lonely dot).
One more thing worth noting here is that control flow not only takes care of executing the promises in sequence, but also it understands the promises enough to crash the test as soon as any of the promises is rejected. Once again, something that would be quite messy if you wanted to achieve it with callbacks.
In real life you would put that HTTP client wrapper in a separate module and just use it wherever you need. Let’s leave that out as an exercise.
Thanks for this, was exactly what I was looking for. One question though, webdriverjs is supposed to handle promise chaining and control flow automatically, is it necessary to explicitly call flow.execute()?
Yes, control flow is crucial.
If you think about it, you have to register a chain of async, promise-returning functions in some place that understands promises and runs them in sequence. If you just call these functions directly, the main function immediately regains control and executes the following statements.
Hi,
I am trying to implement a promise concept in protractor.
I have created a css loading animation for a http request. After server responds, the loading animation is stopped & data is rendered on the view. I have used angularjs for designing this app.
Problem is, I want protractor to wait until loading is complete & then execute the test cases. But it is executing the testcases while loading is still happening & in the screenshot I always get loading screen instead of actual screen.
Please suggest some better approach of doing this.
Thanks in advance
shobha – We have that as well, and deal with it with
browser.wait
at the beginning of the test. Other things on the control flow will not be executed until this wait is done.Something like:
browser.wait(function () {
return $("#myloadingdiv").isDisplayed().then(function (value) {
return !value;
});
});
Thanks for the reply Konrad.
Please find the sample code below.
describe(‘Section 1: Sample App ‘, function() {
beforeEach(function() {
browser.get(‘index.html#/CurrentTemperature’);
browser.wait(function () {
return element(by.id(‘loadingBg’)).getAttribute(‘Style’).then(function (value) {
expect(value).toEqual(‘display: none;’);
});
});
});
it(‘The list elements should have My location, Favourites’, function() {
var items = element.all(by.repeater(‘item in navigation’));
items.then(function(items) {
expect(items.length).toEqual(4);
expect(items[0].getText()).toEqual(‘My location’);
expect(items[1].getText()).toEqual(‘Favourites’);
});
});
});
I am firing an http request & showing loading animation until API responds. Once API responds, I am showing a HTML page, containing data received from API. Then I need to verify the data displayed on HTML page.
Currently the test case passes, but in the protractor reporter, the screenshot always displays loading animation instead of actual page.
Please suggest an ideal approach of testing this behavior.
Since you say the test passes, does it mean there is progress?
I’m not sure what you mean by the reporter issue. Is there any chance that you just see the file it generated when the test was still failing, and now (with success) it has nothing to show and doesn’t take screenshots?
Is there any way I can share you the screenshot?
Ideally I should get the screenshot of the html page[displayed after loading] which contains the data received from API. Instead I am getting a screenshot of the loading screen.
I think you could just call
browser.takeScreenshot
orptor.takeScreenshot
. Just make sure it plays well with the control flow: either put it on control flow at the right moment or maybe in the “after” method.See also some code samples at https://github.com/angular/protractor/issues/114.
I am using protractor-html-screenshot-reporter which will generate a HTML reporter with a screenshot for all the testcases, both passed & failed testcases.
Please refer to this link. http://qainsight.blogspot.in/2014/03/adding-html-reporter-in-protractor-js.html
Issue is, the screen shot is not matching with the html page. Since there is an API request before loading the page, all the screenshot images contains only loading.
I am getting following error when I try the above sample code.
Error: Cannot find module ‘request’
Please suggest
Hi Konrad,
Thanks very much for this useful post! After much tinkering, I found that much could be accomplished simply by requiring “supertest” (built on superagent) via:
var request = require('supertest')(browser.baseUrl);
Then, I use a special test endpoint that resets to fixtures and responds with 302 redirect. This is handy in all my WebDriver browser tests, and doubles as a page to “sync” WebDriver in order to get protractor to continue with all the other direct HTTP test(s).
it('resets database to fixture', function () {
browser.get('/api/test/fixtures/reset');
});
it('should respond with JSON array', function (done) {
request
.get('/api/rounds')
.expect(200)
.expect('Content-Type', /json/)
.end(function (err, res) {
if (err) return done(err);
res.body.should.be.instanceof(Array);
done();
});
});
I’m curious your thoughts about this implementation. Thanks again!
Nick,
Calling “reset” in
it
seems wrong. That should only be used for actual tests, and the “it” is more about “the system” than “the test suite”. Like “the system should respond 4 when adding 2 to 2”. For this kind of setupbeforeEach
andafterEach
are more appropriate.It not only reads differently, but also does not depend on order of test execution, does not pollute the test output, can be executed for more than one test etc.
I don’t know supertest, but it makes some sense and at the same time looks pretty confusing with regards to error handling.
I don’t see how it plays with Protractor though – does it register itself on the control flow? That bit is very important.
Nicks example is what I’ve been looking for over the last few hours. I’ve been tasked with making a test to confirm all our api get calls are functional . Nick, any chance of sharing more?
When I run Protractor, it throws an error, “Error: Cannot find module ‘request'” .. I even installed request module globally….
Kamalakannan – it’s hard to say without seeing the whole project, but…
Protractor is running on Node, and request is just a regular Node module that you need to install and make available. All I needed in that project was to put it in package.json and install with npm.
I have wrote test scripts like this,
describe(‘addition’, function() {
login(‘xxx’, ‘password’);
createAccount(‘username’, ‘password’);
……………
……………..//some scripts
……………..
it(‘account creation’, function() {
expect(msg).toBe(‘created successfully’);
});
});
it throws locator error, sometime element not found, sometime Error while waiting for protractor to sync with page.
Can You Please Guide
I’m writing a wrapper (a service) for this but I want to return the response of the request, as it is righr now when you do defer.fulfill(message) the actual message is lost and cannot be passed outside the service.
flow.execute(purge) does not return the result of the “purge” function so its result is lost.
Falcon – as far as I remember, flow.execute() is all about ordering and chaining promises. I suspect the values need to be passed in some other manner.
Thank you for the article Konrad. Have you used protractor with a restAPI that requires authorization:bearer (https)?
Randy – do you mean some kind of token in HTTP header? It’s still a regular HTTP request.
thanks for the tutorial!
hi Konrad,
what if I wanted to display the message as in defer.fulfill(message);
will it work if -> flow.execute(purge).then(function(e){
console.log(e);
};
regards,
edmond – offhand I don’t remember if flow.execute is chainable. But you can always do something like:
flow.execute(function() {
return purge().then(function(e) { console.log(e); });
});
OR put it inside purge:
function purge() {
return post('api/v1/setup/purge', {
qs : {
key : browser.params.purgeSecret
}
}).then(function (e) {
console.log(e);
});
}
Hey,
could you please tell me how could I send a request with NOT basic authentication. I have to fullfill 3 fields (username, password,company).
Thanks in advance,
Mat
Mat – “request” is a regular Node module that you happen to use in a Node program (Program). See its docs at https://github.com/request/request, especially “Forms” and “HTTP Authentication”.