WebDriver.io logo

WaitFor in WebDriver.io

Have you ever seen your WebDriver test fail with generic errors such as element is not clickable at point, other element would receive the click, or maybe just a timeout when trying to locate an element on your page?

Well whether you have or you haven’t (yet), this post will help you minimize random failures while running automated browser tests via WebDriver.io and Selenium.

Best practices

There are some best practices in WebDriver.io that you should follow when interacting with elements on your pages.  Below are some suggestions for improving the quality and stability of your e2e tests.

Consider the following basic Page Object example which references a “Next” button and an “Email” input box:

/* eslint-disable strict  */
'use strict';
 
const BasePage = require('./BasePage');
 
 
/**
  * @desc Each page inherits from base page.
  * Pages generally contains three parts:
  * 1. selectors
  * 2. getters
  * 3. functions
  */
class ResultsPage extends BasePage {
    constructor() {
        super();
 
        // --------------------------------------------------------------
        // Define element selectors.
        // --------------------------------------------------------------
        this.nextButtonSelector = '[data-wdio="nextButton"]';
        this.emailInputSelector = '[data-wdio="emailInput"]';
    }
 
    // --------------------------------------------------------------
    // Define element getters.
    // --------------------------------------------------------------
    get nextButton() {
        return browser.element(this.nextButtonSelector);
    }
 
    get emailInput() {
        return browser.element(this.emailInputSelector);
    }
 
 
    // --------------------------------------------------------------
    // Define trusted interactions with elements.
    // --------------------------------------------------------------
 
    /**
     * Clicks the button.
     *
     * @return {boolean}
     */
    clickNextButton() {
        this.nextButton.click();
    }
 
    /**
     * Set the value for the element.
     *
     * @param {string} email
     * @return {boolean}
     */
    setEmailInputValue(email) {
        this.emailInput.setValue(email);
    }
}
module.exports = ResultsPage;

You must wait for an element to be visible before you interact with it.

The example above has a function which simulates the “Next” button’s click, however, it doesn’t check to make sure the element is on the page.  In WebDriver, an element can exist, but not be visible, and therefore not able to be interacted with.

If our “Next” button is hidden by default, and perhaps only shown after a user interacts with other parts of the page, we’ll need to wait for the element to be shown before we click it using the element.waitForVisible() method.

Let’s improve our clickNextButton() function:

/**
 * Clicks the button.
 *
 * @return {boolean}
 */
clickNextButton() {
    // Before we click, let's ensure the element is visible on the page.
    this.nextButton.waitForVisible();
    this.nextButton.click();
}

You must wait for an element to be enabled before you click or type.

There are times when you must wait for other conditions to be true, as well.  For example, we can’t call the setValue() method on an input element if it’s disabled, the same way a user can’t type in a disabled textbox.  We’ll use the element.waitForEnabled() accordingly.

Let’s improve our setEmailInputValue() function:

/**
 * Set the value for the element.
 *
 * @param {string} email
 * @return {boolean}
 */
setEmailInputValue(email) {
    this.emailInput.waitForVisible(); // Ensure the input is visible ...
    this.emailInput.waitForEnabled(); // as well as enabled.
    this.emailInput.setValue(email);
}

Above, we’ve now made the setEmailInputValue() more stable because our interaction method checks to be sure the textbox is both visible and enabled before it attempts to set the value.

Set custom timeouts when using waitFor*()

There is a default timeout set for your WebDriver session, but for certain operations, you may need to increase the timeout when waiting for elements.  In this case, simply specify the number of ms to wait as the first parameter, as shown below.

// Before we click, let's ensure the element is visible on the page.
this.nextButton.waitForVisible(20000); // Wait longer than usual for this element to become visible.

Scrolling to elements

There are also times when the element is off the page and must be scrolled to in order to click.  Consider using the element.scroll()function to ensure the element is within the viewport before interacting with it.

waitFor() all the things

There are plenty more waitFor*() functions built into WebDriver, just check out the API for a comprehensive list.  And should one of those convenient utility functions not fit your particular use case, you can further customize things by using the waitUntil() function to wait for any condition you like.

it('should wait until text has changed', function () {
    browser.waitUntil(function () {
      return browser.getText('#someText') === 'I am now different'
    }, 5000, 'expected text to be different after 5s');
});

Next up: disabling CSS animations

In my next post, I’ll cover how to disable CSS animations in your app to improve stability and reduce the number of random failures in WebDriver.io.