I came to know that waiting for an element to be clickable improves the reliability of the test suite. I could not find such command in nightwatchjs documentation and could not find an easy way to create a custom command for it.
This stackoverflow answer highlights the main steps of the command. The .waitForElementClickable() does the following checks:
.Displayed property is true (which is essentially what visibilityOfElementLocated is checking for)..Enabled property is true (which is essentially what the elementToBeClickable is checking for).Is it possible to create such command in nightwatchjs?
Is it possible provide .waitForElementClickable() as part of native API of nightwatch?
Thanks.
You could write a custom command that combines waitForElementPresent, waitForElementVisible, and elementIdEnabled(http://nightwatchjs.org/api#elementIdEnabled)
A simple custom command would typically look like this:
module.exports.command = function (selector) {
this.waitForElementVisible(selector).click(selector);
return this;
};
A similar command might be added in future versions but for now there are no immediate plans for it.
Any chance someone could helpout with a fully working example of a custom command that composes the waitForElementPresent/visible and clickable? I've been struggling with it. I gave the source for _waitForElement a read and gave it a shot, but my isClickable function doesn't seem to inherit the protocol member as I was hoping.
require('babel-core/register');
const util = require('util');
const WaitForElementVisible = require('nightwatch/lib/api/element-commands/waitForElementVisible');
function WaitForElementClickable() {
WaitForElementVisible.call(this);
this.expectedValue = 'clickable';
}
util.inherits(WaitForElementClickable, WaitForElementVisible);
WaitForElementClickable.prototype.elementFound = function (result, now) {
return this.isClickable();
};
WaitForElementClickable.prototype.elementVisible = function (result, now) {
const defaultMsg = 'Element <%s> was clickable after %d milliseconds.';
return this.pass(result, defaultMsg, now - this.startTimer);
};
WaitForElementClickable.prototype.elementNotVisible = function (result, now) {
if (now - this.startTimer < this.ms) {
// element wasn't visible, schedule another check
this.reschedule('isClickable');
return this;
}
const defaultMsg = 'Timed out while waiting for element <%s> to be clickable for %d milliseconds.';
return this.fail(result, 'not clickable', this.expectedValue, defaultMsg);
};
/*!
* Will start checking if the element is clickable and if not re-schedule the check
* until the timeout expires or the condition has been met
*/
WaitForElementClickable.prototype.isClickable = () => {
const self = this;
// the issue first appears here -- protocol is undefined
this.protocol.elementIdDisplayed(this.element, (result) => {
const now = new Date().getTime();
if (result.status === 0 && result.value === true) {
// element was visible
return self.elementVisible(result, now);
}
if (result.status === -1 && result.errorStatus === 10) {
return self.checkElement();
}
return self.elementNotVisible(result, now);
});
};
module.exports = WaitForElementClickable;
@corydolphin the this isn't available in not bind-ed lambda. Use an old-style function instead: WaitForElementClickable.prototype.isClickable = function() {
Did you manage to get it reliably working?
I still fail to wait for an element to be clickable using:
.waitForElementPresent('a', 1000)
.waitForElementVisible('a', 1000)
.waitForElementClickable('a', 1000)
.click('a')
I've created a gist with a working solution. Feel free to use it.
Usage is just:
.waitForElementClickable('button', 1000)
.click('button')
It only makes sense on form controls.
Thanks for the inspiration @JamesBoon, I created a new version that works with nightwatch 1.0.17 (EDIT and 1.1.11): with the new class ES6 API.
Hi @beatfactor, would a PR including waitForElementClickable (as implemented in the gist above) would be welcomed?
Has there been any headway on making this feature apart of nightwatch's native API?
From v1.1 all element commands like .click() will automatically wait for the element to be present and will retry on stale element errors. It doesn't check for visibility but I suppose we could look into that as well.
Hi @beatfactor,
Thank you for your amazing work!
So far I failed to update my gist (see this comment) to make it work with nightwatch 1.1.
This is where I ended up
EDIT: see this gist for a working version:
let WaitForElementVisible = require('nightwatch/lib/api/element-commands/waitForElementVisible');
class WaitForElementClickable extends WaitForElementVisible {
constructor(opts) {
super(opts);
this.expectedValue = 'clickable';
}
protocolAction() {
return this.executeProtocolAction('isElementEnabled');
}
shouldRetryAction(elementVisible) {
return !elementVisible;
}
elementVisible(response) {
let defaultMsg = 'Element <%s> was clickable after %d milliseconds.';
return this.pass(response, defaultMsg, this.executor.elapsedTime);
}
elementNotVisible(response) {
let defaultMsg = 'Timed out while waiting for element <%s> to be clickable for %d milliseconds.';
return this.fail(response, 'not clickable', this.expectedValue, defaultMsg);
}
}
module.exports = WaitForElementClickable;
The main difference with waitForElementVisible is the protocolAction: isElementEnabled.
It' s a bit hacky but it seems to detected whether the element is enabled or not, except that the tests exit just after the WaitForElementClickable command (I see in the logs Element <button.e2e-confirm-add-user-button> was clickable after 2125 milliseconds.). Would you have any idea why?
NB: I have the same exit issue when I export directly WaitForElementVisible in my module (instead of WaitForElementClickable) module.exports = WaitForElementVisible;, I don't get why it doesn't behave like WaitForElementVisible.
@deterralba you could also use expect.element('<selector>').enabled. See also my comment here. The problem here is that custom commands need to emit a complete event when are done (when defined as a class) - due to some internal workings which will not be easy to change any time soon.
So you could either try to override the command method and emit the 'complete` inside or define a basic custom command and use the expect.enabled.
Thank you for your answer! I want to wait until an element is enabled, I don't see how I can do that easily with expect.element('<selector>').enabled.
Anyway, thanks to your help I updated my gist to emit the action at the end of the event it now works!
Would you accept adding .waitForElementClickable() to the API if I produce a PR? It only makes sense on form controls but it is quite useful to avoid using arbitrary .pause().
I think it should say waitForElementEnabled. But expect.element('<selector>').enabled does that, it first waits for the element to be present, then to be enabled using isElementEnabled action.
Besides, you can also do this with expect: expect.element('<selector>').not.enabled.
Most helpful comment
From v1.1 all element commands like
.click()will automatically wait for the element to be present and will retry on stale element errors. It doesn't check for visibility but I suppose we could look into that as well.