Reputation: 1
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
Reputation: 301
Before providing a solution, some feedback on your current code:
//input[@class="..."]
, or other attribute inside the input tag, since you want to get the input tag.pause
longer then 1000
, only in exceptional situationsNow, 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
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
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