UNPKG

nightwatch

Version:

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

285 lines (284 loc) 10.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const { By, RelativeBy, until } = require('selenium-webdriver'); const Element = require('./index.js'); const ElementsByRecursion = require('./locate/elements-by-recursion.js'); const SingleElementByRecursion = require('./locate/single-element-by-recursion.js'); const AVAILABLE_LOCATORS = { 'css selector': 'css', 'id': 'id', 'link text': 'linkText', 'name': 'name', 'partial link text': 'partialLinkText', 'tag name': 'tagName', 'xpath': 'xpath', 'className': 'className' }; class LocateElement { /** * @param {object|string} element * @return {By|RelativeBy} */ static create(element) { if (!element) { throw new Error(`Error while trying to locate element: missing element definition; got: "${element}".`); } const byInstance = LocateElement.locateInstanceOfBy(element); if (byInstance !== null) { return byInstance; } const elementInstance = LocateElement.createElementInstance(element); return By[AVAILABLE_LOCATORS[elementInstance.locateStrategy]](elementInstance.selector); } /** * @param {object} element * @return {By|RelativeBy|null} */ static locateInstanceOfBy(element) { if (element instanceof By) { return element; } if (element.by instanceof By) { return element.by; } if (element.value instanceof RelativeBy) { return element.value; } return null; } /** * @param {object|string} element * @return {Element} */ static createElementInstance(element) { if (typeof element != 'object' && typeof element != 'string') { throw new Error(`Invalid element definition type; expected string or object, but got: ${typeof element}.`); } let selector; let strategy; if (typeof element == 'object' && element.value && element.using) { selector = element.value; strategy = element.using; } else { selector = element; } return Element.createFromSelector(selector, strategy); } get api() { return this.nightwatchInstance.api; } get reporter() { return this.nightwatchInstance.reporter; } get settings() { return this.nightwatchInstance.settings; } get transport() { return this.nightwatchInstance.transport; } get desiredCapabilities() { return this.nightwatchInstance.session.desiredCapabilities; } constructor(nightwatchInstance) { this.nightwatchInstance = nightwatchInstance; } resolveElementRecursively({ element }) { return this.findElement({ element }).then(response => { const firstElement = element.selector[element.selector.length - 1]; firstElement.resolvedElement = response; const { selector, locateStrategy, name } = firstElement; const { status, value } = response; const WebdriverElementId = response && response.WebdriverElementId || null; const result = { selector, locateStrategy, name, WebdriverElementId, response: { status, value } }; if (!isNaN(firstElement.index)) { result.index = firstElement.index; } return result; }); } /** * Finds a single element by using the multiple locate elements, which instead of throwing an error returns an empty array * * @param {Element} element * @param {String} commandName * @param {String} id * @param {String} transportAction * @param {Boolean} [returnSingleElement] * @param {Boolean} [cacheElementId] * @return {Promise} */ async findElement({ element, commandName, id, transportAction = 'locateMultipleElements', returnSingleElement = true, cacheElementId = true }) { if (element && (element.webElement || element.webElementId)) { const elementId = await (element.webElement || element.webElementId).getId(); element.setResolvedElement(elementId); return { value: this.transport.toElement(elementId), status: 0 }; } if (element.resolvedElement && cacheElementId) { return Promise.resolve({ value: this.transport.toElement(element.resolvedElement), status: 0 }); } if (element.usingRecursion) { if (transportAction === 'locateSingleElement') { return this.findSingleElementUsingRecursion(element); } return this.findElementsUsingRecursion({ element, returnSingleElement }); } const result = await this.executeProtocolAction({ element, commandName, id, transportAction, cacheElementId }); return this.handleLocateElement({ result, element, returnSingleElement }); } locateMultipleElements(element) { const commandName = 'locateMultipleElements'; const transportAction = 'locateMultipleElements'; return this.executeProtocolAction({ element, commandName, transportAction }); } handleLocateElement({ result, element, returnSingleElement }) { const { status = 0, error } = result || {}; let { value } = result; if (error instanceof Error) { return { error, status: -1, value }; } let WebdriverElementId; const elementResult = this.transport.resolveElement(result, element, true); if (elementResult) { WebdriverElementId = this.transport.getElementId(elementResult); element.setResolvedElement(WebdriverElementId); } if (returnSingleElement) { value = elementResult; } return { value, status, WebdriverElementId }; } /** * @returns Promise({{ * value, * status, * result, * now * }}) */ executeProtocolAction(opts = {}) { const { id, element, transportAction, commandName, cacheElementId } = opts; const args = { id }; if (element instanceof By) { args.by = element; } else { args.using = element.locateStrategy; args.value = element.selector; } return this.sendElementsAction({ transportAction, args, element, cacheElementId }) .catch(err => { if (this.transport.invalidSessionError(err)) { return new Error(this.transport.getErrorMessage(err)); } throw err; }) .then(result => { if (this.transport.isResultSuccess(result) && Element.requiresFiltering(element)) { return this.filterElements(element, result); } return result; }) // Catch errors from filterElements and from invalid session .catch(error => { return { value: null, status: -1, error }; }); } async sendElementsAction({ transportAction, args, element, cacheElementId } = {}) { if (cacheElementId && transportAction === 'locateMultipleElements' && args && !(args.value instanceof RelativeBy)) { const timeout = element.timeout || this.settings.globals.waitForConditionTimeout; const retryInterval = element.retryInterval || this.settings.globals.waitForConditionPollInterval; try { const results = await this.transport.driver.wait(until.elementsLocated(args), timeout, null, retryInterval); const { elementKey } = this.transport; const value = await Promise.all(results.map(async (webElement) => { const elementId = await webElement.getId(); return { [elementKey]: elementId }; })); return { status: 0, value }; } catch (err) { if (err.name !== 'TimeoutError') { throw err; } throw new NoSuchElementError({ element, ms: timeout }); } } return this.transport.executeProtocolAction(transportAction, args); } /** * Selects a subset of elements if the result requires filtering. * * @param {Element} element * @param {object} result * @return {*} */ filterElements(element, result) { let filtered = Element.applyFiltering(element, result.value); if (filtered) { result.value = filtered; return result; } const errorResult = this.transport.getElementNotFoundResult(result); throw new Error(`Element ${element.toString()} not found.${errorResult.message ? (' ' + errorResult.message) : ''}`); } /** * @param {Element} element * @param {Boolean} returnSingleElement * @return {Promise} */ findElementsUsingRecursion({ element, returnSingleElement = true }) { let recursion = new ElementsByRecursion(this.nightwatchInstance); return recursion.locateElements({ element, returnSingleElement }); } findSingleElementUsingRecursion(element) { let recursion = new SingleElementByRecursion(this.nightwatchInstance); return recursion.locateElement(element.selector); } } class NoSuchElementError extends Error { constructor({ element, ms, abortOnFailure, retries }) { super(); this.selector = element.selector; this.abortOnFailure = abortOnFailure; this.name = 'NoSuchElementError'; this.retries = retries; this.strategy = element.locateStrategy; this.message = `Timed out while waiting for element "${this.selector}" with "${this.strategy}" to be present for ${ms} milliseconds.`; } } module.exports = LocateElement; module.exports.NoSuchElementError = NoSuchElementError; module.exports.AVAILABLE_LOCATORS = AVAILABLE_LOCATORS;