Protractor blocking wait

I’ve been working with non-angular protractor testing lately, and have been frustrated at the lack of documentation for how to perform simple tasks. For example, blocking execution while waiting for an element to appear, or another condition. My team’s tests were relying heavily on sleep() in the absence of such knowledge.

This week, I figured out the secret to a blocking wait.

Let’s say you’ve got some page rendering going on, and you need to wait for an element to show up before continuing:

browser.wait(element(by.id('some-element')).isPresent);

That’s it! Now, and this is absolutely vital: note that we pass in the function isPresent, not the promise it returns. This is because browser.wait() may run the function *many* times, and return many promises, until one of them resolves and passes the test.

What if we have a more complex condition, such as waiting for the element to be removed? In that case, we need to return our own promise-returning function:

browser.wait(function() {
  var deferred = protractor.promise.defer();
  element(by.id('some-element')).isPresent()
  	.then(function (isPresent) {
      deferred.fulfill(!isPresent);
    });
  return deferred.promise;
});

browser.wait() will execute our outer anonymous function as many times as it needs to until the promise is fulfilled with a truthy value.

Here’s a more generic function to handle pretty much any blocking-wait-for-condition that you’ll need:

function waitForPromiseTest(promiseFn, testFn) {
  browser.wait(function () {
    var deferred = protractor.promise.defer();
    promiseFn().then(function (data) {
      deferred.fulfill(testFn(data));
    });
    return deferred.promise;
  });
}

…then use this like so:

waitForPromiseTest(element(by.id('some-element')).isPresent,
  function (isPresent) {
    return !isPresent;
  });

Not a single line of code will execute until your test function returns true.

Note about browser vs. protractor.getInstance(): they’re exactly the same object! browser is the new preferred syntax.

15 thoughts on “Protractor blocking wait

  1. Man, o man… this is incredible… this is nowhere in the docs yet it’s exactly what I was after… I have a case where two elements are require to be present be executing the tests as the page is a bit weighty and takes a second or two to load.

    Page.prototype.isReady = function () { 
        var deferred = protractor.promise.defer();
    
        element(by.css('#oading-message')).isDisplayed().then(function (visible) {
            element(by.css('.posts')).isDisplayed().then(function (visible) {
                deferred.fulfill(!visible);
            });
        });
            
      return deferred.promise;
    }
    
  2. Two things about your code:
    1. isDisplayed() will actually throw an error if the element doesn’t exist yet. isPresent() should be used before isDisplayed() unless you know for certain the element is there.
    2. Yes, you can rewrite that whole thing (without the custom promise) as:

    Page.prototype.isReady = function () {
      var el1 = element(by.css('#oading-message')),
        el2 = element(by.css('.posts'));
      browser.wait(el1.isPresent);
      browser.wait(el1.isDisplayed);
      browser.wait(el2.isPresent);
      browser.wait(el2.isDisplayed);
    };
  3. When I try the following…

    browser.wait(element(by.id('some-element')).isPresent);

    I receive the following error…

    TypeError: Cannot call method 'count' of undefined
    at ElementFinder.isPresent (C:\Users\thardy\AppData\Roaming\npm\node_modules\protractor\lib\protractor.js:890:42)
    at webdriver.promise.ControlFlow.runInNewFrame_ (C:\Users\thardy\AppData\Roaming\npm\node_modules\protractor\node_modules\selenium-webdriver\lib\webdriver\promise.js:1640:20)
    at pollCondition (C:\Users\thardy\AppData\Roaming\npm\node_modules\protractor\node_modules\selenium-webdriver\lib\webdriver\promise.js:1406:12)
    at C:\Users\thardy\AppData\Roaming\npm\node_modules\protractor\node_modules\selenium-webdriver\lib\webdriver\promise.js:1402:5
    at webdriver.promise.ControlFlow.runInNewFrame_ (C:\Users\thardy\AppData\Roaming\npm\node_modules\protractor\node_modules\selenium-webdriver\lib\webdriver\promise.js:1640:20)
    at webdriver.promise.ControlFlow.runEventLoop_ (C:\Users\thardy\AppData\Roaming\npm\node_modules\protractor\node_modules\selenium-webdriver\lib\webdriver\promise.js:1505:8)

  4. Thanks for pointing this out! I was still on Protractor 0.22.0 when I wrote this. In 0.24.0 and above, the isPresent method makes use of the “this” keyword instead of closure to refer to the element object, meaning that is has to be run in the context of the element. So now, use this pattern instead:

    browser.wait(function(){
      return element(by.id('some-element')).isPresent();
    });
    
  5. Could you help me, please?
    I’m trying to wait for an element before start next statement in test script, but it doesn’t work.

    Here is my code. It find an element that doesn’t exist (by.css(“.notGonnaFindThis”)), so it should wait for 20 sec before log the message “Finish wait” to console.

    describe(‘Just testing’, function () {
    ‘use strict’;
    var MyApp = require(‘../pageObjects/myApp.js’);
    var app;
    app = new MyApp();

    beforeEach(function () { });

    it(‘Test wait’, function () {
    app.get();
    console.log(“Finish wait”);
    });

    Here is code in myApp.js

    function waitUntilLoaded() {
    browser.wait(function() {
    var deferred = protractor.promise.defer();
    element(by.css(“.notGonnaFindThis”)).isPresent().then(function (data) {
    var testFn = function (isPresent) {
    return isPresent;
    }
    deferred.fulfill(testFn(data));
    });
    return deferred.promise;
    }, 20000);
    }

    this.get = function () {
    browser.get(‘#’);
    waitUntilLoaded();
    }

  6. Hi Buaban,

    The “wait” is more of a queue block than it is a javascript-level block. Your console.log takes immediate action rather than loading into the Jasmine queue, so therefore it will always run first before the wait is resolved.

    In order for it to run after, you can put it into the resolve block of a promise (which will obey the queue).

  7. I am following page object pattern. I have created one js file for all login related cases. I have created more js file for other functionality like created user….
    I want to run all the js file . I have called all login cases in a spec file but if I call other js page method the page become bulky. I want to know, Can we run more than one spec file in protractor by doing any change in conf file.

  8. Hello,
    I have a test in which I click on a canvas diagram, which opens a configuration panel and I check the if some elements on this configuration panel exist.
    In my test I use browser. wait to check if the configuration panel is loaded completely before doing anything else. But the problem is that protractor executes the part written after the wait, “before” the wait condition, and my tests fails.
    I also tried to use expect(element.isPresent()).toBe(true) but it works only sometimes and not in everytime I run my test. Here is the test:

    it 'should be possible for a user to open the configuration panel, choose scala and write code', ->
      #click on a canvas diagram
      EipDiagram.clickAtPosition(328,34)
      #browser.sleep 2000
      #expect(ptor.isElementPresent(By.css('#routedesignConfiguration'))).toBe(true)
    
      #this is supposed to replace my above browser.sleep 2000 
      browser.driver.wait( (->
        ptor.isElementPresent(By.css('#routedesignConfiguration')).then (present) ->
          expect(present).toBe(true))
        ,10000, 'config panel should appear')
    
      #choose scala
      ConfigurationPanel.chooseLanguage(0)
      ConfigurationPanel.closePanel()
  9. Hello again,
    I changed my code and put the code which must be executed after the condition in the resolve part of a promise (as following) but the log is displayed before. Please help I don’t understand the problem:

    it 'should be possible for a user to open the "EIP: Splitter" configuration panel, choose scala and write code without wait', ->
      EipDiagram.clickAtPosition(328,34)
      #browser.sleep 2000
      testFn = (isPresent) ->
        !isPresent
    
      testElement = ->
        deferred = protractor.promise.defer()
        ptor.findElement(By.css('#routedesignConfiguration')).then (container) ->
          deferred.fulfill (testFn(container))
    
        deferred.promise
    
      browser.wait(testElement, 20000, 'config panel should appear').then ->
        console.log('After wait')
        ConfigurationPanel.chooseLanguage(0)
        browser.sleep 2000
        ConfigurationPanel.closePanel()
  10. My fast answer was too fast! I later realised that you were pasting Coffeescript (which I avoid like it’s radioactive), sorry! Now that I’ve compiled it to javascript I am taking a look at it:

    it('should be possible for a user to open the "EIP: Splitter" configuration panel, choose scala and write code without wait', function() {
      var testElement, testFn;
      EipDiagram.clickAtPosition(328, 34);
      testFn = function(isPresent) {
        return !isPresent;
      };
      testElement = function() {
        var deferred;
        deferred = protractor.promise.defer();
        ptor.findElement(By.css('#routedesignConfiguration')).then(function(container) {
          return deferred.fulfill(testFn(container));
        });
        return deferred.promise;
      };
      return browser.wait(testElement, 20000, 'config panel should appear').then(function() {
        console.log('After wait');
        ConfigurationPanel.chooseLanguage(0);
        browser.sleep(2000);
        return ConfigurationPanel.closePanel();
      });
    });
  11. I think the problem is that you don’t have a valid test for whether the element is present. Try something like this instead:

    it('should be possible for a user to open the "EIP: Splitter" configuration panel, choose scala and write code without wait', function() {
      var el = element(by.id('routedesignConfiguration'));
      EipDiagram.clickAtPosition(328, 34);
      browser.wait(function(){
        return el.isPresent();
      }, 20000, 'config panel should appear').then(function(){
        console.log('After wait');
        ConfigurationPanel.chooseLanguage(0);
        browser.sleep(2000);
        return ConfigurationPanel.closePanel();
      });
    });
  12. Hello Doc Splendid,
    So I read your comment. I changed my tests as you said. But no luck. It works but once every 3 times I run tests. So it is not a reliable solution for me.
    Thank you so much again for your help.
    I will probably use jasmine wait and run which seems work better than protractor solution

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>