Craig Rodrigues!

How to Refresh the Page While Waiting for an Element with Nightwatch

Nightwatch is a great library for UI testing, but it does lack one feature I recently needed - the ability to continually refresh a page waiting for an element to appear. Unfortunately, the application I am working on currently doesn't use polling or web sockets to allow for real-time updates, meaning that any changes that come through require a refresh of the browser page to show up.

Nightwatch has functions to wait for an element to be visible, but no functionality to refresh the page and check.

Here are basic steps I needed:

  1. Navigate to a URL
  2. Check if an element is visible.
  3. IF VISIBLE: Assert true that the element is there.
  4. IF NOT: Wait for N seconds then refresh the page.
  5. GO TO #2 and continue for a certain amount of time (ex: 5 minutes).

Nightwatch is a wrapper on top of Selenium/Web Driver. It operates by process commands in order using what they call the "Command Queue." Once you understand how it works, it's not too difficult to come up with a method that will achieve the steps I need above.

To learn all about the Command Queue read this.

We can create a custom command that can wait for an element/text to be visible. We can also add some options that allow someone to specify how long this test will run for before failing and we can also modify how long between each page refresh and check.

Here is what we are trying to do:

If our tests fail we need to retry our block of code. To inject commands into the command queue we must use Perform() and that function will allow us to recursively call our block of commands as many times as we want until we hit the timeout or until the element is visible after a refresh

Label A is the positive case, and B is the negative case. What we need to do is insert our commands over and over into the Command Queue.

NightwatchJS Refresh Until.jpg

Looking at the source code for Nightwatch, we can create a custom command using the same Node Emitter style they use.

const util = require('util');
const events = require('events');

// Waits for N rounds for some element to appear
function RefreshUntilElementVisible() {
    events.EventEmitter.call(this);
    this.currentRound = 0;
}

// Node event emitter style
util.inherits(refreshUntilElementVisible, events.EventEmitter);

/**
 * The last = {} bit just before the parameter signature closes means that if nothing gets passed in for this parameter,
 * we’re going to use an empty object as the default if no argument is passed in.
 * 
 * @arg {string} element
 * @arg {object} options
 * @param {number} options.maxRounds Number of times to refresh before we fail. Default = 10 rounds.
 * @param {number} options.timeout Timeout between refreshes in ms. Default = 30 seconds.
 */

refreshUntilElementVisible.prototype.command = function(element, {maxRounds = 10, timeout = 30000} = {}) {
    this.api.isVisible(element, (result) => {
        if (result) {
            this.api.assert(result, `Element visible after ${this.currentRound} rounds.`);
            this.emit('complete');
        } else if (this.currentRound < maxRounds) {
            console.log(`ROUND: ${this.currentRound} - ACTUAL: ${result}, EXPECTED: true`);

            this.api.pause(timeout)
                .refresh()
                .pause(1000) // Can replace with waitUntilElementVisible
                .perform((browser, done) => {
                    this.currentRound++;             

                    // Recursively call our command if we haven't found the element
                    this.command(element, {maxRounds, timeout});
                    done();
                })
        } else {
            this.api.assert.fail(result, true, `Element not visible after ${maxRounds} rounds.`);
            this.emit('complete');
        }
    })

    return this;
}

module.exports = refreshUntilElementVisible;

Simple enough and works beautifully. Can be modified and used to refresh and wait for anything.

Here's an example of using our new custom command in a test.

'SCENARIO: Table Updated': function(browser) {
    browser.url('http://www.myFakeShoppingCart.com')
        .waitForElementVisible('#purchase-complete')
        .end();
}

A Method for Solving Whiteboard Problems