UNPKG

@applitools/spec-driver-webdriver

Version:
607 lines (606 loc) 26.4 kB
"use strict"; 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;