@applitools/spec-driver-webdriver
Version:
607 lines (606 loc) • 26.4 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.build = exports.switchWorld = exports.getWorlds = exports.getCurrentWorld = exports.takeScreenshot = exports.performAction = exports.visit = exports.getUrl = exports.getTitle = exports.getCookies = exports.getSystemBars = exports.setOrientation = exports.getOrientation = exports.setViewportSize = exports.setWindowSize = exports.getWindowSize = exports.getCapabilities = exports.getDriverInfo = exports.childContext = exports.parentContext = exports.mainContext = exports.click = exports.hover = exports.getElementText = exports.setElementText = exports.getElementAttribute = exports.getElementRegion = exports.findElements = exports.findElement = exports.executeBrowserCommands = exports.executeScript = exports.toSimpleCommonSelector = exports.toSelector = exports.toElement = exports.toDriver = exports.isStaleElementError = exports.isEqualElements = exports.isSelector = exports.isShadowRoot = exports.isSecondaryElement = exports.isElement = exports.isSecondaryDriver = exports.isDriver = exports.buildWebDriverProxyCapabilities = exports.getProxyConfiguration = void 0;
const url_1 = require("url");
const http_proxy_agent_1 = __importDefault(require("http-proxy-agent"));
const https_proxy_agent_1 = __importDefault(require("https-proxy-agent"));
const http_1 = __importDefault(require("http"));
const https_1 = __importDefault(require("https"));
const utils = __importStar(require("@applitools/utils"));
const path_1 = __importDefault(require("path"));
const fs_1 = __importDefault(require("fs"));
const LEGACY_ELEMENT_ID = 'ELEMENT';
const ELEMENT_ID = 'element-6066-11e4-a52e-4f735466cecf';
const SHADOW_ROOT_ID = 'shadow-6066-11e4-a52e-4f735466cecf';
const W3C_CAPABILITIES = ['platformName', 'platformVersion'];
const W3C_SECONDARY_CAPABILITIES = ['pageLoadStrategy'];
const W3C_SAFARI_CAPABILITIES = ['browserVersion', 'setWindowRect'];
const APPIUM_CAPABILITIES = ['appiumVersion', 'deviceType', 'deviceOrientation', 'deviceName', 'automationName'];
const LEGACY_APPIUM_CAPABILITIES = ['appium-version', 'device-type', 'device-orientation'];
const CHROME_CAPABILITIES = ['chrome', 'goog:chromeOptions'];
const MOBILE_BROWSER_NAMES = ['ipad', 'iphone', 'android'];
const ANDROID_PLATFORM_NAME = 'android';
const ANDROID_AUTOMATION_NAME = 'uiautomator2';
function extractElementId(element) {
var _a;
return ((_a = element[ELEMENT_ID]) !== null && _a !== void 0 ? _a : element[LEGACY_ELEMENT_ID]);
}
function extractShadowRootId(shadowRoot) {
return shadowRoot[SHADOW_ROOT_ID];
}
function extractEnvironment(capabilities) {
var _a, _b, _c, _d;
const isAppium = APPIUM_CAPABILITIES.some(capability => capabilities.hasOwnProperty(capability)) ||
APPIUM_CAPABILITIES.some(capability => capabilities.hasOwnProperty(`appium:${capability}`));
const isChrome = CHROME_CAPABILITIES.includes((_a = capabilities.browserName) === null || _a === void 0 ? void 0 : _a.toLowerCase());
const isW3C = isAppium ||
W3C_SECONDARY_CAPABILITIES.every(capability => capabilities.hasOwnProperty(capability)) ||
W3C_CAPABILITIES.every(capability => capabilities.hasOwnProperty(capability)) ||
W3C_SAFARI_CAPABILITIES.every(capability => capabilities.hasOwnProperty(capability));
const isMobile = capabilities.browserName === '' ||
isAppium ||
LEGACY_APPIUM_CAPABILITIES.some(capability => capabilities.hasOwnProperty(capability)) ||
MOBILE_BROWSER_NAMES.includes((_b = capabilities.browserName) === null || _b === void 0 ? void 0 : _b.toLowerCase());
const isAndroid = ((_c = capabilities.platformName) === null || _c === void 0 ? void 0 : _c.toLowerCase()) === ANDROID_PLATFORM_NAME ||
((_d = capabilities.automationName) === null || _d === void 0 ? void 0 : _d.toLowerCase()) === ANDROID_AUTOMATION_NAME;
return {
isAndroid,
isChrome,
isMobile,
isW3C,
};
}
function getPackageJson(packageName) {
const packagePath = require.resolve(packageName);
const packageRoot = packagePath.replace(new RegExp(`\\${path_1.default.sep}${packageName}\\${path_1.default.sep}.*$`), path_1.default.sep + packageName);
const packageJsonPath = path_1.default.join(packageRoot, 'package.json');
return JSON.parse(fs_1.default.readFileSync(packageJsonPath, 'utf-8'));
}
function getFrameworkMajorVersion() {
try {
const version = getPackageJson('webdriver').version;
return Number.parseInt(version);
}
catch {
return 99999; // if we failed to detect the version, let's assume it's a new version
}
}
function getWebDriver() {
var _a;
const webdriver = require('webdriver');
return (_a = webdriver.default) !== null && _a !== void 0 ? _a : webdriver;
}
// Exported for testing
function getProxyConfiguration() {
const httpProxy = utils.general.getEnvCaseInsensitive('HTTP_PROXY');
const httpsProxy = utils.general.getEnvCaseInsensitive('HTTPS_PROXY');
if (!httpProxy && !httpsProxy) {
return { hasProxy: false, httpUrl: null, httpsUrl: null };
}
return {
hasProxy: true,
httpUrl: new URL(httpProxy !== null && httpProxy !== void 0 ? httpProxy : httpsProxy),
httpsUrl: new URL(httpsProxy !== null && httpsProxy !== void 0 ? httpsProxy : httpProxy),
};
}
exports.getProxyConfiguration = getProxyConfiguration;
function getAgents(httpUrl, httpsUrl) {
const httpProxyOptions = { ...(0, url_1.parse)(httpUrl.href), rejectUnauthorized: false };
const httpAgent = (0, http_proxy_agent_1.default)(httpProxyOptions);
const httpsProxyOptions = { ...(0, url_1.parse)(httpsUrl.href), rejectUnauthorized: false };
const httpsAgent = (0, https_proxy_agent_1.default)(httpsProxyOptions);
httpsAgent.callback = utils.general.wrap(httpsAgent.callback.bind(httpsAgent), (fn, request, options, ...rest) => {
return fn(request, { ...options, rejectUnauthorized: false }, ...rest);
});
return { httpAgent, httpsAgent };
}
// Exported for testing
function buildWebDriverProxyCapabilities(proxyConfig, noProxy) {
if (proxyConfig.hasProxy && noProxy) {
const httpProxyPort = proxyConfig.httpUrl.port || (proxyConfig.httpUrl.protocol === 'https:' ? '443' : '80');
const httpsProxyPort = proxyConfig.httpsUrl.port || (proxyConfig.httpsUrl.protocol === 'https:' ? '443' : '80');
return {
proxyType: 'manual',
httpProxy: `${proxyConfig.httpUrl.hostname}:${httpProxyPort}`,
sslProxy: `${proxyConfig.httpsUrl.hostname}:${httpsProxyPort}`,
noProxy: noProxy,
};
}
else if (!proxyConfig.hasProxy && noProxy) {
// NO_PROXY exists but no HTTP_PROXY - configure WebDriver to use system proxy with bypass
return {
proxyType: 'system',
noProxy: noProxy,
};
}
return null;
}
exports.buildWebDriverProxyCapabilities = buildWebDriverProxyCapabilities;
function isDriver(driver) {
if (!driver)
return false;
return utils.types.instanceOf(driver, 'Browser');
}
exports.isDriver = isDriver;
function isSecondaryDriver(driver) {
if (!driver)
return false;
return utils.types.has(driver, ['sessionId', 'serverUrl']);
}
exports.isSecondaryDriver = isSecondaryDriver;
function isElement(element) {
if (!element)
return false;
return !!extractElementId(element);
}
exports.isElement = isElement;
function isSecondaryElement(element) {
if (!element)
return false;
return !!element.elementId;
}
exports.isSecondaryElement = isSecondaryElement;
function isShadowRoot(shadowRoot) {
if (!shadowRoot)
return false;
return !!extractShadowRootId(shadowRoot);
}
exports.isShadowRoot = isShadowRoot;
function isSelector(selector) {
if (!selector)
return false;
return utils.types.has(selector, ['using', 'value']);
}
exports.isSelector = isSelector;
function isEqualElements(_driver, element1, element2) {
if (!element1 || !element2)
return false;
const elementId1 = extractElementId(element1);
const elementId2 = extractElementId(element2);
return elementId1 === elementId2;
}
exports.isEqualElements = isEqualElements;
function isStaleElementError(error) {
if (!error)
return false;
const errOrResult = error.originalError || error;
return errOrResult instanceof Error && errOrResult.name === 'stale element reference';
}
exports.isStaleElementError = isStaleElementError;
function toDriver(driver) {
var _a, _b, _c;
let transformedDriver;
if (utils.types.has(driver, ['sessionId', 'serverUrl'])) {
const url = new URL(driver.serverUrl);
const environment = extractEnvironment(driver.capabilities);
const options = {
sessionId: driver.sessionId,
protocol: url.protocol ? url.protocol.replace(/:$/, '') : undefined,
hostname: url.hostname,
port: Number(url.port) || undefined,
path: url.pathname,
capabilities: driver.capabilities,
logLevel: 'silent',
...environment,
};
if (!options.port) {
if (options.protocol === 'http')
options.port = 80;
if (options.protocol === 'https')
options.port = 443;
}
if ((_a = driver.proxy) === null || _a === void 0 ? void 0 : _a.url) {
const proxyUrl = new URL(driver.proxy.url);
proxyUrl.username = (_b = driver.proxy.username) !== null && _b !== void 0 ? _b : proxyUrl.username;
proxyUrl.password = (_c = driver.proxy.password) !== null && _c !== void 0 ? _c : proxyUrl.password;
const { httpAgent, httpsAgent } = getAgents(proxyUrl, proxyUrl);
options.agent = { http: httpAgent, https: httpsAgent };
}
else {
const proxyConfig = getProxyConfiguration();
if (proxyConfig.hasProxy) {
const { httpAgent, httpsAgent } = getAgents(proxyConfig.httpUrl, proxyConfig.httpsUrl);
options.agent = { http: httpAgent, https: httpsAgent };
}
else {
const httpAgent = http_1.default.globalAgent;
const httpsAgent = new https_1.default.Agent({ rejectUnauthorized: false });
options.agent = { http: httpAgent, https: httpsAgent };
}
}
const WebDriver = getWebDriver();
transformedDriver = WebDriver.attachToSession(options);
transformedDriver.original = driver.original;
}
else {
// NOTE: this is needed to a rare case when this function is used not on the SecondaryDriver but on a derivative of the Driver
transformedDriver = Object.create(driver, {
original: { enumerable: false, get: () => driver },
});
}
if (utils.types.isFunction(transformedDriver, 'addCommand')) {
const command = getFrameworkMajorVersion() < 8 ? require('webdriver/build/command').default : require('webdriver').command;
transformedDriver.addCommand('_getWindowSize', command('GET', '/session/:sessionId/window/current/size', {
command: '_getWindowSize',
description: '',
ref: '',
parameters: [],
}));
transformedDriver.addCommand('_setWindowSize', command('POST', '/session/:sessionId/window/current/size', {
command: '_setWindowSize',
parameters: [
{ name: 'width', type: 'number', required: true, description: '' },
{ name: 'height', type: 'number', required: true, description: '' },
],
description: '',
ref: '',
}));
transformedDriver.addCommand('setWindowPosition', command('POST', '/session/:sessionId/window/current/position', {
command: 'setWindowPosition',
parameters: [
{ name: 'x', type: 'number', required: true, description: '' },
{ name: 'y', type: 'number', required: true, description: '' },
],
description: '',
ref: '',
}));
transformedDriver.addCommand('sendCommandAndGetResult', command('POST', '/session/:sessionId/chromium/send_command_and_get_result', {
command: 'sendCommandAndGetResult',
parameters: [
{ name: 'cmd', type: 'string', required: true, description: '' },
{ name: 'params', type: 'object', required: true, description: '' },
],
description: 'Send a command to the DevTools debugger and wait for the result.',
ref: '',
}));
}
return transformedDriver;
}
exports.toDriver = toDriver;
function toElement(element) {
const elementId = utils.types.has(element, 'elementId') ? element.elementId : extractElementId(element);
return { [ELEMENT_ID]: elementId, [LEGACY_ELEMENT_ID]: elementId };
}
exports.toElement = toElement;
function toSelector(selector) {
if (utils.types.has(selector, 'selector')) {
if (utils.types.has(selector, 'type') && selector.type && utils.types.isString(selector.selector)) {
return { using: selector.type === 'css' ? 'css selector' : selector.type, value: selector.selector };
}
else if (isSelector(selector.selector)) {
return selector.selector;
}
else {
selector = selector.selector;
}
}
if (utils.types.isString(selector)) {
return { using: 'css selector', value: selector };
}
return selector;
}
exports.toSelector = toSelector;
function toSimpleCommonSelector(selector) {
if (utils.types.has(selector, ['using', 'value'])) {
return { type: selector.using === 'css selector' ? 'css' : selector.using, selector: selector.value };
}
return selector;
}
exports.toSimpleCommonSelector = toSimpleCommonSelector;
async function executeScript(driver, script, arg) {
script = utils.types.isFunction(script) ? `return (${script}).apply(null, arguments)` : script;
// temporary solution for webdriver package not throwing on stale elements starting from Chrome 122
// https://github.com/webdriverio/webdriverio/blob/5df72f58475c4f580c57d5b805588c204c44c2af/packages/webdriver/src/request/index.ts#L258
const result = await driver.executeScript(script, [arg]);
if (result === null || result === void 0 ? void 0 : result.error) {
throw new Error(result.error);
}
return result;
}
exports.executeScript = executeScript;
async function executeBrowserCommands(driver, commands, logger) {
var _a;
let lastResponse;
for (const currentCommand of commands) {
lastResponse = await driver.sendCommandAndGetResult(currentCommand.command.toString(), (_a = currentCommand.params) !== null && _a !== void 0 ? _a : {});
logger === null || logger === void 0 ? void 0 : logger.debug(`executeBrowserCommands ${currentCommand.command}, params: ${currentCommand.params}, response: ${JSON.stringify(lastResponse)}`);
}
return lastResponse;
}
exports.executeBrowserCommands = executeBrowserCommands;
async function findElement(driver, selector, parent) {
const parentId = parent ? (isShadowRoot(parent) ? extractShadowRootId(parent) : extractElementId(parent)) : null;
try {
const element = parentId
? await driver.findElementFromElement(parentId, selector.using, selector.value)
: await driver.findElement(selector.using, selector.value);
return isElement(element) ? element : null;
}
catch (error) {
if (/element could not be located/i.test(error.message) ||
/cannot locate an element/i.test(error.message) ||
/wasn\'t found/i.test(error.message)) {
return null;
}
throw error;
}
}
exports.findElement = findElement;
async function findElements(driver, selector, parent) {
const parentId = parent ? (isShadowRoot(parent) ? extractShadowRootId(parent) : extractElementId(parent)) : null;
return parentId
? await driver.findElementsFromElement(parentId, selector.using, selector.value)
: await driver.findElements(selector.using, selector.value);
}
exports.findElements = findElements;
async function getElementRegion(driver, element) {
return driver.getElementRect(extractElementId(element));
}
exports.getElementRegion = getElementRegion;
async function getElementAttribute(driver, element, attr) {
return driver.getElementAttribute(extractElementId(element), attr);
}
exports.getElementAttribute = getElementAttribute;
async function setElementText(driver, element, text) {
await driver.elementClear(extractElementId(element));
await driver.elementSendKeys(extractElementId(element), text);
}
exports.setElementText = setElementText;
async function getElementText(driver, element) {
return driver.getElementText(extractElementId(element));
}
exports.getElementText = getElementText;
async function hover(driver, element) {
if (!driver.isW3C) {
try {
return await driver.moveToElement(extractElementId(element));
}
catch { }
}
await driver.performActions([
{
type: 'pointer',
id: 'mouse',
parameters: { pointerType: 'mouse' },
actions: [{ type: 'pointerMove', duration: 0, origin: element, x: 0, y: 0 }],
},
]);
}
exports.hover = hover;
async function click(driver, element) {
await driver.elementClick(extractElementId(element));
}
exports.click = click;
async function mainContext(driver) {
await driver.switchToFrame(null);
return driver;
}
exports.mainContext = mainContext;
async function parentContext(driver) {
await driver.switchToParentFrame();
return driver;
}
exports.parentContext = parentContext;
async function childContext(driver, element) {
await driver.switchToFrame(element);
return driver;
}
exports.childContext = childContext;
async function getDriverInfo(driver) {
var _a;
const driverServerUrl = `${driver.options.protocol}://${driver.options.hostname}${driver.options.port ? `:${driver.options.port}` : ''}${(_a = driver.options.path) !== null && _a !== void 0 ? _a : ''}`;
return { sessionId: driver.sessionId, driverServerUrl };
}
exports.getDriverInfo = getDriverInfo;
async function getCapabilities(driver) {
return driver.capabilities;
}
exports.getCapabilities = getCapabilities;
async function getWindowSize(driver) {
try {
const rect = await driver.getWindowRect();
return { width: rect.width, height: rect.height };
}
catch {
// for old versions of webdriver
return driver._getWindowSize();
}
}
exports.getWindowSize = getWindowSize;
async function setWindowSize(driver, size) {
try {
await driver.setWindowRect(0, 0, size.width, size.height);
}
catch {
// for old versions of webdriver
await driver.setWindowPosition(0, 0);
await driver._setWindowSize(size.width, size.height);
}
}
exports.setWindowSize = setWindowSize;
async function setViewportSize(driver, size) {
await driver.sendCommandAndGetResult('Emulation.setDeviceMetricsOverride', {
...size,
deviceScaleFactor: 0,
mobile: false,
});
}
exports.setViewportSize = setViewportSize;
async function getOrientation(browser) {
const orientation = await browser.getOrientation();
return orientation.toLowerCase();
}
exports.getOrientation = getOrientation;
async function setOrientation(browser, orientation) {
return await browser.setOrientation(orientation);
}
exports.setOrientation = setOrientation;
async function getSystemBars(browser) {
return browser.getSystemBars();
}
exports.getSystemBars = getSystemBars;
async function getCookies(driver, context) {
if (context)
return driver.getAllCookies().then(cookies => cookies.map(cookie => ({
...cookie,
sameSite: cookie.sameSite &&
{
// webdriver 7 returns the sameSite value in lowercase
strict: 'Strict',
lax: 'Lax',
none: 'None',
// webdriver 9 returns the sameSite value in CamelCase
Strict: 'Strict',
Lax: 'Lax',
None: 'None',
}[cookie.sameSite],
})));
const response = await driver.sendCommandAndGetResult('Network.getAllCookies', {});
const cookies = response.cookies;
return cookies.map((cookie) => {
const copy = { ...cookie, expiry: cookie.expires };
delete copy.expires;
delete copy.size;
delete copy.priority;
delete copy.session;
delete copy.sameParty;
delete copy.sourceScheme;
delete copy.sourcePort;
return copy;
});
}
exports.getCookies = getCookies;
async function getTitle(driver) {
return driver.getTitle();
}
exports.getTitle = getTitle;
async function getUrl(driver) {
return driver.getUrl();
}
exports.getUrl = getUrl;
async function visit(driver, url) {
await driver.navigateTo(url);
}
exports.visit = visit;
async function performAction(driver, steps) {
return driver.performActions(steps);
}
exports.performAction = performAction;
async function takeScreenshot(driver) {
return driver.takeScreenshot();
}
exports.takeScreenshot = takeScreenshot;
async function getCurrentWorld(driver) {
const world = await driver.getContext();
return utils.types.isString(world) ? world : world.id;
}
exports.getCurrentWorld = getCurrentWorld;
async function getWorlds(driver) {
const worlds = await driver.getContexts();
return worlds.map(world => (utils.types.isString(world) ? world : world.id));
}
exports.getWorlds = getWorlds;
async function switchWorld(driver, id) {
await driver.switchContext(id);
}
exports.switchWorld = switchWorld;
const browserOptionsNames = {
chrome: 'goog:chromeOptions',
firefox: 'moz:firefoxOptions',
};
/*
* Spawn a browser with a given configuration (INTERNAL USE ONLY)
*
* NOTE:
* This function is intended for internal use only. As a result it relies on some dev dependencies.
* When wiring the spec-driver up to an SDK and calling this function, if you don't have the same dev deps
* installed in the SDK, then this function will error.
*/
async function build(env) {
const WebDriver = getWebDriver();
const parseEnv = require('@applitools/test-utils/src/parse-env');
const { browser = '', capabilities, url, proxy, configurable = true, args = [], headless, logLevel = 'silent', } = parseEnv(env);
const options = {
capabilities: { browserName: browser, ...capabilities },
logLevel,
};
options.protocol = url.protocol ? url.protocol.replace(/:$/, '') : undefined;
options.hostname = url.hostname;
if (url.port)
options.port = Number(url.port);
else if (options.protocol === 'http')
options.port = 80;
else if (options.protocol === 'https')
options.port = 443;
options.path = url.pathname;
if (configurable) {
const browserOptionsName = browserOptionsNames[browser || options.capabilities.browserName];
if (browserOptionsName) {
const browserOptions = options.capabilities[browserOptionsName] || {};
browserOptions.args = [...(browserOptions.args || []), ...args];
if (headless)
browserOptions.args.push('headless');
options.capabilities[browserOptionsName] = browserOptions;
}
}
if (proxy) {
options.capabilities.proxy = {
proxyType: 'manual',
httpProxy: proxy.http || proxy.server,
sslProxy: proxy.https || proxy.server,
ftpProxy: proxy.ftp,
noProxy: proxy.bypass.join(','),
};
}
const proxyConfig = getProxyConfiguration();
const noProxy = utils.general.getEnvCaseInsensitive('NO_PROXY');
if (proxyConfig.hasProxy) {
const { httpAgent, httpsAgent } = getAgents(proxyConfig.httpUrl, proxyConfig.httpsUrl);
options.agent = { http: httpAgent, https: httpsAgent };
// Configure WebDriver proxy capabilities if NO_PROXY is present
const proxyCapabilities = buildWebDriverProxyCapabilities(proxyConfig, noProxy);
if (proxyCapabilities) {
options.capabilities.proxy = proxyCapabilities;
}
}
else if (noProxy) {
// NO_PROXY exists but no HTTP_PROXY - configure WebDriver to use system proxy with bypass
const proxyCapabilities = buildWebDriverProxyCapabilities(proxyConfig, noProxy);
if (proxyCapabilities) {
options.capabilities.proxy = proxyCapabilities;
}
options.agent = { https: require('https').Agent({ rejectUnauthorized: false }) };
}
else {
options.agent = { https: require('https').Agent({ rejectUnauthorized: false }) };
}
const driver = await WebDriver.newSession(options);
return [driver, () => driver.deleteSession()];
}
exports.build = build;