UNPKG

nightwatch

Version:

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

422 lines (328 loc) 10 kB
const EventEmitter = require('events'); const {WebElement} = require('selenium-webdriver'); const Utils = require('../utils'); const LocateStrategy = require('./strategy.js'); const Element = require('./index.js'); const {NoSuchElementError} = require('./locator.js'); const {Logger, PeriodicPromise, isDefined} = Utils; class ElementCommand extends EventEmitter { static isSelectorObject(obj) { return Utils.isObject(obj) && obj.selector; } static get ELEMENT_COMMAND_ARGS() { return 3; } get api() { return this.nightwatchInstance.api; } get client() { return this.nightwatchInstance; } get settings() { return this.nightwatchInstance.settings; } get transport() { return this.nightwatchInstance.transport; } get desiredCapabilities() { return this.nightwatchInstance.session.desiredCapabilities; } get reporter() { return this.nightwatchInstance.reporter; } get commandName() { return this.__commandName; } get defaultUsing() { return this.nightwatchInstance.locateStrategy || LocateStrategy.getDefault(); } get strategy() { return this.__strategy; } get elementLocator() { return this.nightwatchInstance.elementLocator; } get rescheduleInterval() { return this.__rescheduleIntervalMs; } get ms() { return this.__timeoutMs; } get retryOnSuccess() { return false; } get retryOnValidActionResult() { return false; } get callback() { return this.__callback; } get element() { return this.__element; } get selector() { return this.__selector; } get extraArgsCount() { return null; } get abortOnFailure() { return this.__abortOnFailure; } set abortOnFailure(value) { this.__abortOnFailure = Utils.convertBoolean(value); } get suppressNotFoundErrors() { return this.__suppressNotFoundErrors; } set suppressNotFoundErrors(value) { this.__suppressNotFoundErrors = Utils.convertBoolean(value); } /** * @override * @returns {*} */ protocolAction() { return Promise.resolve(); } isResultSuccess(result) { return this.transport.isResultSuccess(result) && result.value !== null; } protocolActionHandler() { return Promise.resolve(); } constructor({nightwatchInstance, commandName, args, requireValidation = true} = {}) { super(); this.nightwatchInstance = nightwatchInstance || this.client; //custom commands don't get the nightwatchInstance this.__commandName = commandName || this.commandFileName; this.transport.registerLastError(null); this.setStrategy(); this.suppressNotFoundErrors = false; this.abortOnFailure = this.api.globals.abortOnAssertionFailure; this.throwOnMultipleElementsReturned = this.api.globals.throwOnMultipleElementsReturned; this.elementId = null; this.args = Array.isArray(args) && args.slice(0) || this.commandArgs || []; this.setMilliseconds(); this.setRescheduleInterval(); this.setArguments(requireValidation); this.setOptionsFromSelector(); this.createElement(); this.setupExecutor(); this.setupActions(); } setupExecutor() { this.executor = new PeriodicPromise({ rescheduleInterval: this.rescheduleInterval, timeout: this.ms }); } setupActions() { } command() { return this.executor.run(); } /** * @override * @param [response] * @returns {*} */ elementFound(response) { if (response) { if (this.isSeleniumWebElement(response.value)) { this.webElement = response.value; } else if (isDefined(response.value)) { this.elementId = this.transport.getElementId(response.value); } else { this.elementId = response; } } return this.protocolAction(); } isSeleniumWebElement(value) { return (value instanceof WebElement); } /** * @override * @param err * @returns {ElementCommand} */ elementNotFound(err) { return this.complete(err, err.result); } elementLocateError(err) { if (err instanceof NoSuchElementError) { return this.elementNotFound(err); } Logger.error(err); return this.complete(err, {}); } async findElement({returnSingleElement = true, cacheElementId = true} = {}) { const {element, commandName, WebdriverElementId} = this; try { if (WebdriverElementId) { return { value: this.transport.toElement(WebdriverElementId), status: 0, result: {} }; } return await this.elementLocator.findElement({element, commandName, returnSingleElement, cacheElementId}); } catch (err) { if (err.name === 'ReferenceError' || err.name === 'TypeError') { throw err; } throw this.noSuchElementError(err); } } executeProtocolAction(actionName, args = []) { const {sessionId} = this; let {elementId, webElement, selector} = this; if (webElement) { elementId = webElement; } else if (selector instanceof WebElement) { elementId = selector; } return this.transport.executeProtocolAction({actionName, args: [elementId, ...args], sessionId}); } async complete(err, response) { try { await this.callback.call(this.api, response, this); if (err instanceof Error) { return err; } return response; } catch (cbErr) { return cbErr; } } validateArgsCount(requireValidation = true) { if (!requireValidation) { return this; } //////////////////////////////////////////////////////////////////////////// // if there is an element command such as getText, getAttribute etc., verify if the // expected number of args have been supplied //////////////////////////////////////////////////////////////////////////// if (this.args.length && !Utils.isFunction(this.args[this.args.length - 1])) { this.args.push(function() {}); } if (this.extraArgsCount !== null) { let expectedArgs = ElementCommand.ELEMENT_COMMAND_ARGS + this.extraArgsCount; let passedArgs = this.args.length; let rejectPromise; if (this.client.isES6AsyncTestcase) { expectedArgs = expectedArgs - 1; passedArgs = passedArgs - 1; rejectPromise = true; } if (passedArgs < expectedArgs - 1 || passedArgs > expectedArgs) { const error = new Error(`${this.commandName} method expects ${(expectedArgs - 1)} ` + `(or ${expectedArgs} if using implicit "css selector" strategy) arguments - ${passedArgs} given.`); error.rejectPromise = rejectPromise; throw error; } if (Utils.isString(this.args[0]) && (expectedArgs === passedArgs || ElementCommand.isSelectorObject(this.args[1]))) { this.setStrategyFromArgs(); } } } setStrategyFromArgs() { const strategy = this.args.shift(); LocateStrategy.validate(strategy, this.commandName); this.setStrategy(strategy); } setArguments(requireValidation) { this.validateArgsCount(requireValidation); this.__selector = this.args.shift(); this.setOutputMessage(); this.setCallback(); return this; } setOptionsFromSelector() { if (Utils.isObject(this.selector)) { const { abortOnFailure, retryInterval, message, timeout, locateStrategy, suppressNotFoundErrors, retryAction } = this.selector; if (!Utils.isUndefined(abortOnFailure)) { this.abortOnFailure = abortOnFailure; } if (!Utils.isUndefined(suppressNotFoundErrors)) { this.suppressNotFoundErrors = suppressNotFoundErrors; } if (!Utils.isUndefined(retryInterval)) { this.setRescheduleInterval(retryInterval); } if (message) { this.message = message; } if (locateStrategy) { this.setStrategy(locateStrategy); } if (!Utils.isUndefined(timeout)) { this.setMilliseconds(timeout); } if (!Utils.isUndefined(retryAction)) { this.__retryOnFailure = retryAction; } const {WebdriverElementId} = this.selector; if (WebdriverElementId) { this.WebdriverElementId = WebdriverElementId; } } return this; } setStrategy(val = this.defaultUsing) { this.__strategy = val; return this; } setOutputMessage() { this.message = null; if (this.args.length > 0) { if (Utils.isString(this.args[this.args.length - 1])) { this.message = this.args.pop(); } } return this; } setCallback() { let callback = null; if (this.args.length && Utils.isFunction(this.args[this.args.length - 1])) { callback = this.args.pop(); } this.__callback = callback || function() {}; return this; } /** * @returns {ElementCommand} */ createElement() { this.__element = Element.createFromSelector(this.selector, this.strategy); return this; } /** * Set the time in milliseconds to wait for the condition, accepting a given value or a globally defined default * * @param {number} [timeoutMs] */ setMilliseconds(timeoutMs = this.api.globals.waitForConditionTimeout) { try { Utils.enforceType(timeoutMs, Utils.PrimitiveTypes.NUMBER); } catch (err) { throw new Error('waitForElement expects second parameter to have a global default (waitForConditionTimeout) ' + 'to be specified if not passed as the second parameter'); } this.__timeoutMs = timeoutMs; return this; } setRescheduleInterval(intervalMs = this.api.globals.waitForConditionPollInterval) { Utils.enforceType(intervalMs, Utils.PrimitiveTypes.NUMBER); this.__rescheduleIntervalMs = intervalMs; return this; } noSuchElementError(err) { const {element, ms, abortOnFailure} = this; const {retries} = err; return new NoSuchElementError({element, ms, abortOnFailure, retries}); } } module.exports = ElementCommand;