UNPKG

webdriverio

Version:

Next-gen browser and mobile automation test framework for Node.js

439 lines (350 loc) 12.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getBrowserObject = getBrowserObject; exports.transformToCharString = transformToCharString; exports.parseCSS = parseCSS; exports.checkUnicode = checkUnicode; exports.findElement = findElement; exports.findElements = findElements; exports.verifyArgsAndStripIfElement = verifyArgsAndStripIfElement; exports.getElementRect = getElementRect; exports.getAbsoluteFilepath = getAbsoluteFilepath; exports.assertDirectoryExists = assertDirectoryExists; exports.validateUrl = validateUrl; exports.getScrollPosition = getScrollPosition; exports.hasElementId = hasElementId; exports.addLocatorStrategyHandler = addLocatorStrategyHandler; exports.containsHeaderObject = exports.updateCapabilities = exports.getAutomationProtocol = exports.isStub = exports.enhanceElementsArray = exports.getElementFromResponse = exports.getPrototype = void 0; var _fs = _interopRequireDefault(require("fs")); var _http = _interopRequireDefault(require("http")); var _path = _interopRequireDefault(require("path")); var _cssValue = _interopRequireDefault(require("css-value")); var _rgb2hex = _interopRequireDefault(require("rgb2hex")); var _getPort = _interopRequireDefault(require("get-port")); var _graphemeSplitter = _interopRequireDefault(require("grapheme-splitter")); var _logger = _interopRequireDefault(require("@wdio/logger")); var _lodash = _interopRequireDefault(require("lodash.isobject")); var _lodash2 = _interopRequireDefault(require("lodash.isplainobject")); var _url = require("url"); var _devtools = require("devtools"); var _constants = require("../constants"); var _findStrategy = require("./findStrategy"); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } const browserCommands = require('../commands/browser'); const elementCommands = require('../commands/element'); const log = (0, _logger.default)('webdriverio'); const INVALID_SELECTOR_ERROR = 'selector needs to be typeof `string` or `function`'; const scopes = { browser: browserCommands, element: elementCommands }; const applyScopePrototype = (prototype, scope) => { Object.entries(scopes[scope]).forEach(([commandName, command]) => { prototype[commandName] = { value: command }; }); }; const getPrototype = scope => { const prototype = { puppeteer: { value: null, writable: true }, _NOT_FIBER: { value: false, writable: true, configurable: true } }; applyScopePrototype(prototype, scope); prototype.strategies = { value: new Map() }; return prototype; }; exports.getPrototype = getPrototype; const getElementFromResponse = res => { if (!res) { return null; } if (res.ELEMENT) { return res.ELEMENT; } if (res[_constants.ELEMENT_KEY]) { return res[_constants.ELEMENT_KEY]; } return null; }; exports.getElementFromResponse = getElementFromResponse; function getBrowserObject(elem) { return elem.parent ? getBrowserObject(elem.parent) : elem; } function transformToCharString(value) { const ret = []; if (!Array.isArray(value)) { value = [value]; } for (const val of value) { if (typeof val === 'string') { ret.push(...checkUnicode(val)); } else if (typeof val === 'number') { const entry = `${val}`.split(''); ret.push(...entry); } else if (val && typeof val === 'object') { try { ret.push(...JSON.stringify(val).split('')); } catch (e) {} } else if (typeof val === 'boolean') { const entry = val ? 'true'.split('') : 'false'.split(''); ret.push(...entry); } } return ret; } function sanitizeCSS(value) { if (!value) { return value; } return value.trim().replace(/'/g, '').replace(/"/g, '').toLowerCase(); } function parseCSS(cssPropertyValue, cssProperty) { if (!cssPropertyValue) { return null; } let parsedValue = { property: cssProperty, value: cssPropertyValue.toLowerCase().trim() }; if (parsedValue.value.indexOf('rgb') === 0) { parsedValue.value = parsedValue.value.replace(/\s/g, ''); let color = parsedValue.value; parsedValue.parsed = (0, _rgb2hex.default)(parsedValue.value); parsedValue.parsed.type = 'color'; parsedValue.parsed[/[rgba]+/g.exec(color)[0]] = color; } else if (parsedValue.property === 'font-family') { let font = (0, _cssValue.default)(cssPropertyValue); let string = parsedValue.value; let value = cssPropertyValue.split(/,/).map(sanitizeCSS); parsedValue.value = sanitizeCSS(font[0].value || font[0].string); parsedValue.parsed = { value, type: 'font', string }; } else { try { parsedValue.parsed = (0, _cssValue.default)(cssPropertyValue); if (parsedValue.parsed.length === 1) { parsedValue.parsed = parsedValue.parsed[0]; } if (parsedValue.parsed.type && parsedValue.parsed.type === 'number' && parsedValue.parsed.unit === '') { parsedValue.value = parsedValue.parsed.value; } } catch (e) {} } return parsedValue; } function checkUnicode(value, isDevTools) { return Object.prototype.hasOwnProperty.call(_constants.UNICODE_CHARACTERS, value) ? isDevTools ? [value] : [_constants.UNICODE_CHARACTERS[value]] : new _graphemeSplitter.default().splitGraphemes(value); } function fetchElementByJSFunction(selector, scope) { if (!scope.elementId) { return scope.execute(selector); } const script = function (elem) { return selector.call(elem); }.toString().replace('selector', `(${selector.toString()})`); return getBrowserObject(scope).execute(`return (${script}).apply(null, arguments)`, scope); } async function findElement(selector) { if (typeof selector === 'string' || (0, _lodash2.default)(selector)) { const { using, value } = (0, _findStrategy.findStrategy)(selector, this.isW3C, this.isMobile); return this.elementId ? this.findElementFromElement(this.elementId, using, value) : this.findElement(using, value); } if (typeof selector === 'function') { const notFoundError = new Error(`Function selector "${selector.toString()}" did not return an HTMLElement`); let elem = await fetchElementByJSFunction(selector, this); elem = Array.isArray(elem) ? elem[0] : elem; return getElementFromResponse(elem) ? elem : notFoundError; } throw new Error(INVALID_SELECTOR_ERROR); } async function findElements(selector) { if (typeof selector === 'string' || (0, _lodash2.default)(selector)) { const { using, value } = (0, _findStrategy.findStrategy)(selector, this.isW3C, this.isMobile); return this.elementId ? this.findElementsFromElement(this.elementId, using, value) : this.findElements(using, value); } if (typeof selector === 'function') { let elems = await fetchElementByJSFunction(selector, this); elems = Array.isArray(elems) ? elems : [elems]; return elems.filter(elem => elem && getElementFromResponse(elem)); } throw new Error(INVALID_SELECTOR_ERROR); } function verifyArgsAndStripIfElement(args) { function verify(arg) { if ((0, _lodash.default)(arg) && arg.constructor.name === 'Element') { if (!arg.elementId) { throw new Error(`The element with selector "${arg.selector}" you trying to pass into the execute method wasn't found`); } return { [_constants.ELEMENT_KEY]: arg.elementId, ELEMENT: arg.elementId }; } return arg; } return !Array.isArray(args) ? verify(args) : args.map(verify); } async function getElementRect(scope) { const rect = await scope.getElementRect(scope.elementId); let defaults = { x: 0, y: 0, width: 0, height: 0 }; if (Object.keys(defaults).some(key => rect[key] == null)) { const rectJs = await getBrowserObject(scope).execute(function (el) { if (!el || !el.getBoundingClientRect) { return; } const { left, top, width, height } = el.getBoundingClientRect(); return { x: left + this.scrollX, y: top + this.scrollY, width, height }; }, scope); Object.keys(defaults).forEach(key => { if (rect[key] != null) { return; } if (typeof rectJs[key] === 'number') { rect[key] = Math.floor(rectJs[key]); } else { log.error('getElementRect', { rect, rectJs, key }); throw new Error('Failed to receive element rects via execute command'); } }); } return rect; } function getAbsoluteFilepath(filepath) { return filepath.startsWith('/') || filepath.startsWith('\\') || filepath.match(/^[a-zA-Z]:\\/) ? filepath : _path.default.join(process.cwd(), filepath); } function assertDirectoryExists(filepath) { if (!_fs.default.existsSync(_path.default.dirname(filepath))) { throw new Error(`directory (${_path.default.dirname(filepath)}) doesn't exist`); } } function validateUrl(url, origError) { try { const urlObject = new _url.URL(url); return urlObject.href; } catch (e) { if (origError) { throw origError; } return validateUrl(`http://${url}`, e); } } function getScrollPosition(scope) { return getBrowserObject(scope).execute('return { scrollX: this.pageXOffset, scrollY: this.pageYOffset };'); } async function hasElementId(element) { if (!element.elementId) { const method = element.isReactElement ? 'react$' : '$'; element.elementId = (await element.parent[method](element.selector)).elementId; } if (!element.elementId) { return false; } return true; } function addLocatorStrategyHandler(scope) { return (name, script) => { if (scope.strategies.get(name)) { throw new Error(`Strategy ${name} already exists`); } scope.strategies.set(name, script); }; } const enhanceElementsArray = (elements, parent, selector, foundWith = '$$', props = []) => { elements.parent = parent; elements.selector = selector; elements.foundWith = foundWith; elements.props = props; return elements; }; exports.enhanceElementsArray = enhanceElementsArray; const isStub = automationProtocol => automationProtocol === './protocol-stub'; exports.isStub = isStub; const getAutomationProtocol = async config => { if (config.automationProtocol) { return config.automationProtocol; } if (config.hostname || config.port || config.path || config.user && config.key) { return 'webdriver'; } if (config.capabilities && typeof config.capabilities.browserName === 'string' && !_devtools.SUPPORTED_BROWSER.includes(config.capabilities.browserName.toLowerCase())) { return 'webdriver'; } const driverEndpointHeaders = await new Promise(resolve => { const req = _http.default.request(_constants.DRIVER_DEFAULT_ENDPOINT, resolve); req.on('error', error => resolve({ error })); req.end(); }); if (driverEndpointHeaders.req && driverEndpointHeaders.req.agent) { driverEndpointHeaders.req.agent.destroy(); } if (driverEndpointHeaders && parseInt(driverEndpointHeaders.statusCode, 10) === 200) { return 'webdriver'; } return 'devtools'; }; exports.getAutomationProtocol = getAutomationProtocol; const updateCapabilities = async (params, automationProtocol) => { if (automationProtocol === 'webdriver' && params.capabilities.browserName === 'firefox') { if (!params.capabilities['moz:firefoxOptions']) { params.capabilities['moz:firefoxOptions'] = {}; } if (!params.capabilities['moz:firefoxOptions'].args) { params.capabilities['moz:firefoxOptions'].args = []; } if (!params.capabilities['moz:firefoxOptions'].args.includes(_constants.FF_REMOTE_DEBUG_ARG)) { params.capabilities['moz:firefoxOptions'].args.push(_constants.FF_REMOTE_DEBUG_ARG, (await (0, _getPort.default)()).toString()); } } }; exports.updateCapabilities = updateCapabilities; const containsHeaderObject = (base, match) => { for (const [key, value] of Object.entries(match)) { if (typeof base[key] === 'undefined' || base[key] !== value) { return false; } } return true; }; exports.containsHeaderObject = containsHeaderObject;