qairish83
qairish83

Reputation: 1

nightwatch js while loop

I'm new to node and nightwatch. Been working with Selenium for a number of years but my company has moved over to all things node. Anyway, in nightwatch I am trying to click a link while its visible and loop and keep clicking it until it is not. Here is what my code looks like. Any suggestions would be much appreciated!

"Cart Cleanup": function (browser) {
    browser
        .url(environment + '/ShoppingBag')
        .waitForElementVisible('div.cart-top-message', 190000)
        .pause(3000)
        .element('class name', 'delete-form', function (visible) {
            while (visible.status !== -1) {
                console.log(visible.status);
                browser
                    .useXpath() //NOW USING XPATH
                    .click('/html/body/div[5]/div[2]/div/div[2]/div[1]/div/div[3]/div[4]/div[2]/form[2]/input')
                    .useCss()
                    .pause(3000)
                    .element('class name', 'delete-form', function (visible2) {
                        visible = visible2
                    })
            }
        })
}

Upvotes: 0

Views: 4568

Answers (3)

return
return

Reputation: 301

Before providing a solution, some feedback on your current code:

  • your xpath is very long and prone to fails in the future. There is no need to put the absolut pate. You can replace it with something like //input[@class="..."], or other attribute inside the input tag, since you want to get the input tag.
  • try not to use pauselonger then 1000, only in exceptional situations

Now, regarding your problem, for clicking an element while it is visible, you can use isVisible method: https://nightwatchjs.org/api/#isVisible

function clickWhileVisible(browser) {
 browser.isVisible('css selector', '#your_element', ({ value }) => {
    if (value === true) {
        browser
            .click('#your_element')
            // you can increase/decrease pause if you want a pause between clicks
            .pause(500);
       clickWhileVisible(browser)
    } 
})

}

You can also add a retry mechanism, to make sure that it won't run forever:

function clickWhileVisible(browser, retry) {
 browser.isVisible('css selector', '#your_element', ({ value }) => {
    if (value === true && retry <=10) { //retries number can be modified
        browser
            .click('#your_element')
            .pause(500);
       clickWhileVisible(browser, retry+1)
    } 
})

Upvotes: 0

David Sinclair
David Sinclair

Reputation: 4407

This might help someone. I needed to loop a test over and over and had success with this:

const doThing = () => {
    browser.pause(20000);
    doThing();
};

doThing();

Upvotes: 0

user2954397
user2954397

Reputation: 71

The best way to solve this problem is to create a custom command. Nightwatch custom command documentation: http://nightwatchjs.org/guide#writing-custom-commands

In order to make it easier to solve problems such as these, I created a "waitUntil" custom command, which serves as a base for other custom commands:

// WaitUntil.js, in custom commands folder specified in nightwatch.json
var util = require('util');
var events = require('events');
var TIMEOUT_RETRY_INTERVAL = 100;

function waitUntil() {
    events.EventEmitter.call(this);
    this.startTimeInMilliseconds = null;
}

util.inherits(waitUntil, events.EventEmitter);

/**
 * The purpose of this command is to serve as a base for waitUntil_ commands. It will run the getActual function until
 * the predicate funciton returns true or the timeout is reached. At that point, the assertion funciton will be called.
 * @param getActual {Function} - should passe the found value to its callback. The callback will be passed as the only
 *      argument.
 * @param predicate {Function} - the wait will end when this returns true. The actual value is passed as the only
 *      argument.
 * @param assertion {Function} - the assertion to make. The assertion should pass when the predicate returns true. This
 *      function will be passed the actual value and the message.
 * @param timeoutInMilliseconds {number} - the number of milliseconds to wait before timing out and failing.
 * @param message {string} - the message to attach to the assertion. The elapsed time will be appended to this.
 * @returns custom command waitUntil, which can be accessed as browser.waitUntil(args);
 */
waitUntil.prototype.command = function (getActual, predicate, assertion, timeoutInMilliseconds, message) {
    message = message || 'waitUntil';
    this.startTimeInMilliseconds = new Date().getTime();
    var self = this;

    this.check(getActual, predicate, function (actual, loadedTimeInMilliseconds) {
        if (predicate(actual)) {
            message += ': true after '
                + (loadedTimeInMilliseconds - self.startTimeInMilliseconds) + ' ms.';
        } else {
            message += ': timed out after ' + timeoutInMilliseconds + ' ms.';
        }
        assertion(actual, message);
        self.emit('complete');
    }, timeoutInMilliseconds);

    return this;
};

waitUntil.prototype.check = function (getActual, predicate, callback, maxTimeInMilliseconds) {
    var self = this;
    getActual(function (result) {
        // If the argument passed to the callback is an object, it is assumed that the format is of the argument passed
        // to callbacks by the Nightwatch API, in which the object has a "value" attribute with the actual information.
        var resultValue;
        if (typeof result !== 'object') {
            resultValue = result;
        } else if (result.hasOwnProperty('value')) {
            resultValue = result.value;
        } else {
            self.error('Result object does not have a value.');
            return;
        }

        var now = new Date().getTime();
        if (predicate(resultValue)) {
            callback(resultValue, now);
        } else if (now - self.startTimeInMilliseconds < maxTimeInMilliseconds) {
            setTimeout(function () {
                self.check(getActual, predicate, callback, maxTimeInMilliseconds);
            }, TIMEOUT_RETRY_INTERVAL);
        } else {
            callback(resultValue, null);
        }
    });
};

module.exports = waitUntil;

Using this module, it is simple to create commands like waitUntilTrue, waitUntilEqual, etc. You can also create a "clickUntilNotVisible" command to solve your problem (Obviously, this could be combined with the above and simplified if this is your only use case):

// clickUntilNotVisible.js, in custom commands folder specified in nightwatch.json
exports.command = function (clickElementFunction, getVisibilityFunction, assertion, timeout, message) {
    var browser = this;

    function clickAndGetVisiblity (callback) {
        clickElementFunction();
        getVisibilityFunction(callback);
    }

    function isTrue (actual) {
        return !!actual;
    }

    return browser.waitUntil(clickAndGetVisiblity, isTrue, assertion, timeout, message);
};

Now that we have this clickUntilNotVisible command defined, we can tackle your problem:

function clickMyLink() {
    browser
        .useXpath() //NOW USING XPATH
        .click('/html/body/div[5]/div[2]/div/div[2]/div[1]/div/div[3]/div[4]/div[2]/form[2]/input')
        .useCss();
}

function isDeleteFormVisible(callback) {
    browser
        .pause(3000)
        .elements('class name', 'delete-form', function (result) {
            callback(result.status === 0 && result.value.length);
        });
}

function verifyDeleteFormIsNotVisible (actual, message) {
    browser.verify.ok(!actual, message);
}

module.exports = {
    "Cart Cleanup": function (browser) {
        browser
            .url(environment + '/ShoppingBag')
            .waitForElementVisible('div.cart-top-message', 190000)
            .pause(3000)
            .clickUntilNotVisible(clickMyLink, isDeleteFormVisible, verifyDeleteFormIsNotVisible, 190000);
    }
};

Note that this uses what I've done and might not be the most efficient way for you to solve this problem, but hopefully it will give you some idea of how to solve it yourself. Also, all this code, other than the waitUntil module, is untested, so it probably won't work without a bit of debugging.

Upvotes: 4

Related Questions