webdriverio
Version:
Next-gen browser and mobile automation test framework for Node.js
439 lines (350 loc) • 12.3 kB
JavaScript
;
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;