Direct Server HTTP Calls in Protractor

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.

15 thoughts on “Direct Server HTTP Calls in Protractor

  1. 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()?

  2. 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.

  3. 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

  4. 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;
    });
    });

  5. 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.

  6. 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?

  7. 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.

  8. I am getting following error when I try the above sample code.

    Error: Cannot find module ‘request’

    Please suggest

  9. 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!

  10. 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 setup beforeEach and afterEach 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.

  11. 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?

  12. 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.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Spam protection by WP Captcha-Free