UNPKG

nightwatch

Version:

Easy to use Node.js based end-to-end testing solution for web applications using the W3C WebDriver API.

260 lines (224 loc) 8.04 kB
const Utils = require('../../utils'); const {AssertionRunner} = require('../../assertion'); const {LocateStrategy, Command: ElementCommand} = require('../../element'); /*! * Base class for waitForElement commands. It provides a command * method and element* methods to be overridden by subclasses * * @constructor */ class WaitForElement extends ElementCommand { get retryOnSuccess() { return false; } static get rejectNodeOnAbortFailure() { return true; } static get isTraceable() { return true; } static isElementVisible(result) { return result.value === true; } constructor(opts) { super(opts); this.expectedValue = 'found'; } validateArgsCount() { if (LocateStrategy.isValid(this.args[0]) && (Utils.isString(this.args[1]) || ElementCommand.isSelectorObject(this.args[1]))) { this.setStrategyFromArgs(); } } setupActions() { const validate = (result) => this.isResultSuccess(result); const successHandler = (result) => this.elementFound(result); this.executor .queueAction({ action: () => this.findElement({cacheElementId: false}), retryOnSuccess: this.retryOnSuccess, retryOnFailure: !this.retryOnSuccess, validate, successHandler, errorHandler: (err) => { if (err.name !== 'TimeoutError') { throw err; } const {response} = err; if (response && response.error instanceof Error) { if (response.error.name === 'NoSuchElementError') { return this.elementNotFound(response); } this.reporter.registerTestError(response.error); return this.fail(response, 'error while locating the element', this.expectedValue, 'Timed out while waiting for element <%s> for %d milliseconds'); } if (response && response.value) { return this.elementFound(response); } return this.elementNotFound(response); } }); } /*! * The public command function which will be called by the test runner. Arguments can be passed in a variety of ways. * * The custom message always is last and the callback is always before the message or last if a message is not passed. * * The second argument is always the time in milliseconds. The third argument can be either of: * - abortOnFailure: this can overwrite the default behaviour of aborting the test if the condition is not met within the specified time * - rescheduleInterval: this can overwrite the default polling interval (currently 500ms) * The above can be supplied also together, in which case the rescheduleInterval is specified before the abortOnFailure. * * Some of the multiple usage possibilities: * --------------------------------------------------------------------------- * - with no arguments; in this case a global default timeout is used * waitForElement('body'); * * - with a global default timeout and a callback * waitForElement('body', function() {}); * * - with a global default timeout, a callback, and a custom message * waitForElement('body', function() {}, 'test message'); * * - with a global default timeout a custom message * waitForElement('body', 'test message'); * * - with only the timeout * waitForElement('body', 500); * * - with a timeout and a custom message * waitForElement('body', 500, 'test message); * * - with a timeout and a callback * waitForElement('body', 500, function() { .. }); * * - with a timeout and a custom abortOnFailure * waitForElement('body', 500, true); * * - with a timeout, a custom abortOnFailure, and a custom message * waitForElement('body', 500, true, 'test message'); * * - with a timeout, a custom abortOnFailure, and a callback * waitForElement('body', 500, true, function() { .. }); * * - with a timeout, a custom abortOnFailure, a callback and a custom message * waitForElement('body', 500, true, function() { .. }, 'test message'); * * - with a timeout, a custom reschedule interval, and a callback * waitForElement('body', 500, 100, function() { .. }); * * - with a timeout, a custom rescheduleInterval, and a custom abortOnFailure * waitForElement('body', 500, 100, false); * * * @param {string} selector * @param {number|function|string} milliseconds * @param {function|boolean|string|number} callbackOrAbort * @returns {WaitForElement} */ setArguments() { super.setArguments(); let rescheduleInterval; //////////////////////////////////////////////////////////////////////////// // custom timeout value // // waitForElement('body', 100); //////////////////////////////////////////////////////////////////////////// if (Utils.isNumber(this.args[0])) { this.setMilliseconds(this.args[0]); } //////////////////////////////////////////////// // DEPRECATED // backwards compatibility //////////////////////////////////////////////// if (Utils.isBoolean(this.args[1])) { //////////////////////////////////////////////// // The command was called with a custom abortOnFailure: // // waitForElement('body', 500, false); //////////////////////////////////////////////// this.abortOnFailure = this.args[1]; // eslint-disable-next-line brace-style } //////////////////////////////////////////////// // The command was called with a custom timeout and rescheduleInterval: // // waitForElement('body', 500, 100); //////////////////////////////////////////////// else if (Utils.isNumber(this.args[1])) { rescheduleInterval = this.args[1]; //////////////////////////////////////////////// // The command was called with a custom timeout, rescheduleInterval, and custom abortOnFailure: // // waitForElement('body', 500, 100, false); // waitForElement('body', 500, 100, false, function() {}); //////////////////////////////////////////////// if (Utils.isBoolean(this.args[2])) { this.abortOnFailure = this.args[2]; } } if (rescheduleInterval) { this.setRescheduleInterval(rescheduleInterval); } } pass(result, defaultMsg, timeMs) { this.message = this.formatMessage(defaultMsg, timeMs); return this.assert({ result, passed: true, err: { expected: this.expectedValue } }); } fail(result, actual, expected, defaultMsg) { this.message = this.formatMessage(defaultMsg); return this.assert({ result, passed: false, err: { actual, expected } }); } assert({result, passed, err}) { this.elapsedTime = this.executor.elapsedTime; const {reporter} = this.client; const {elapsedTime, message, abortOnFailure, stackTrace} = this; const runner = new AssertionRunner({ passed, err, message, abortOnFailure, stackTrace, reporter, elapsedTime }); return runner.run(result) .catch(err => (err)) .then(err => { if (Utils.isObject(result.value) && !Array.isArray(result.value)) { result.value = [result.value]; } if (err instanceof Error) { err.abortOnFailure = this.abortOnFailure; err.waitFor = true; return this.complete(err, result); } return this.complete(null, result); }); } /** * @param {string} defaultMsg * @param {number} [timeMs] * @returns {string} */ formatMessage(defaultMsg, timeMs) { return Utils.format(this.message || defaultMsg, this.element.selector, timeMs || this.ms); } elementNotFound(result) { const defaultMsg = 'Timed out while waiting for element <%s> to be present for %d milliseconds.'; return this.fail(result, 'not found', this.expectedValue, defaultMsg); } } module.exports = WaitForElement;