This site uses cookies. Continue to use the site as normal if you are happy with this, or read more about cookies and how to manage them.

×

This site uses cookies. Continue to use the site as normal if you are happy with this, or read more about cookies and how to manage them.

×

If you're using browser.sleep() in your Cucumber, Protractor, or Selenium tests, you're doing it wrong!

The problem with browser.sleep() is that it’s fragile and will sometimes fail if the system runs slower than normal. These occasional failures are solved by increasing the timeout value. This introduces additional latency into the test cycle which increases the feedback time, which is bad. If the sleep() is in a low level helper method, used by many tests, it can add minutes to a test run, which slows down the feedback time.

The problem

My Cucumber test does not wait for the “next” page to load when the user clicks a button. The issue is described in detail here: https://github.com/angular/protractor/issues/2358 and here https://github.com/angular/protractor/issues/2567 Also see: http://stackoverflow.com/questions/31498207/protractor-button-click-calls-the-callback-sooner-than-desired

This issue is likely to effect any angular web-app that requires a user to login. Often the login page will have a button and if the authentication works the system will route the user to the home page. If logon fails they are shown an error and asked to login again.

Here’s my gherkin:

Scenario: A User with valid credentials can login
    Given I am on the login page
    When I login with "some.user@example.com" and "password"
    Then I can see the current projects

The first Given calls browser.get(url) to load the main page, which results in the user being asked to login. In this case Protractor knows to wait for the angular $digest cycle to complete before running the next step.

The When clause uses emailElem.sendKeys(email) to populate the email field and this is repeated for the password field too. Finally, the LoginPage will call submitElem.click() to press the login button. This button click is where the problem starts.

The final Then clause is expecting to see the some data on the page that is loaded following a successful login. This expect(...) call is executed prematurely, before the page has completed loading the new data (a list of current projects) and therefore the test always fails.

Low latency solution

The often quoted solution is to add a browser.sleep(), but I think we can improve on this. The solution I’ve used with reasonable success adds an isLoaded() method to the page driver. In the scenario above, the step I can see the current projects uses the home page driver to expect(...) that the “Current projects” component is displayed after a successful login. It uses the isLoaded() function as an alternative to browser.wait().

this.isShowingCurrentProjects = function () {
    this.isLoaded();
    return expect(headingElem.getText()).to.eventually.equal(headingText);
};

this.isLoaded = function () {
    return browser.wait(function () {
        return browser.driver.isElementPresent(headingByXpath);
    });
};

The key to this non-sleeping solution is to bypass protractor and use selenium's isElementPresent() and then to combine this with Protractor's browser.wait(). The variable headingByXpath, is a page element that is loaded as part of an angular template that's only on the new page. Once this is loaded into the DOM then isElementPresent() returns true and Protractor can resume waiting for the $digest cycle to complete.

So far in testing this has proven to be a reliable technique for waiting for new pages to load without using browser.sleep().