0

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
                    })
            }
        })
}
qairish83
  • 1
  • 1
  • 2

3 Answers3

4

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.

0

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();
David Sinclair
  • 4,187
  • 3
  • 17
  • 12
  • Not sure what is the reson for a 20000 pause, but I strongly suggest against using pause longer than 1000. – return Jun 07 '19 at 13:11
  • Ah, was just trying to show that you can use `browser` inside of the function and it seems to delay other actions. – David Sinclair Jun 07 '19 at 17:32
  • I see. I would also add that you need to pass the `browser` object to the function: `const doThing = browser => {browser.pause() etc}` – return Jun 09 '19 at 12:37
  • I do not think that is needed, because it is already provided outside of the function – David Sinclair Jun 09 '19 at 16:06
  • I understand, if it already has access to it, it's not needed, only if it's outside of it. – return Jun 09 '19 at 20:36
0

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)
    } 
})
return
  • 291
  • 1
  • 13