UNPKG

@applitools/driver

Version:

Applitools universal framework wrapper

929 lines (928 loc) 48.7 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; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.makeDriver = exports.isDriver = exports.isDriverInstance = exports.Driver = void 0; const logger_1 = require("@applitools/logger"); const context_1 = require("./context"); const selector_1 = require("./selector"); const buffer_1 = require("buffer"); const helper_ios_1 = require("./helper-ios"); const helper_android_1 = require("./helper-android"); const user_agent_1 = require("./user-agent"); const capabilities_1 = require("./capabilities"); const utils = __importStar(require("@applitools/utils")); const util_1 = require("util"); const snippets = require('@applitools/snippets'); const drivers = new WeakMap(); class Driver { constructor(options) { var _a, _b, _c, _d; this._state = {}; this._customConfig = {}; this._original = this; this._logger = (0, logger_1.makeLogger)({ logger: options.logger, format: { label: 'driver' } }); this._customConfig = (_a = options.customConfig) !== null && _a !== void 0 ? _a : {}; this._spec = options.spec; this._target = options.driver; if (!this._spec.isDriver(this._target)) { throw new TypeError('Driver constructor called with argument of unknown type!'); } if (!drivers.get(options.driver)) { drivers.set(options.driver, utils.general.guid()); } this._mainContext = new context_1.Context({ spec: this._spec, context: (_d = (_c = (_b = this._spec).extractContext) === null || _c === void 0 ? void 0 : _c.call(_b, this._target)) !== null && _d !== void 0 ? _d : this._target, driver: this, }); this._currentContext = this._mainContext; } [util_1.inspect.custom]() { return { driverInfo: this._driverInfo, environment: this._environment, viewport: this._viewport, helper: this._helper, state: this._state, customConfig: this._customConfig, features: this._features, }; } get logger() { return this._logger; } get target() { return this._target; } get guid() { return drivers.get(this.target); } get currentContext() { return this._currentContext; } get mainContext() { return this._mainContext; } updateLogger(logger) { this._logger = logger.extend({ label: 'driver' }); } updateCurrentContext(context) { this._currentContext = context; } async reloadPage() { if (this._spec.reload) await this._spec.reload(this.target); else await this.mainContext.execute(snippets.reloadPage).catch(() => null); const refreshThis = await this.refresh({ reset: false }); const scrollingElement = await this.mainContext.getScrollingElement(); await (scrollingElement === null || scrollingElement === void 0 ? void 0 : scrollingElement.refresh()); return refreshThis; } async refresh({ reset, name } = {}) { if (reset) { if (utils.general.getEnvValue('AVOID_DRIVER_STATE_REST', 'boolean')) { this._logger.log(`Skipping reset of the driver state`); } else { this._driverInfo = undefined; this._environment = undefined; this._viewport = undefined; this._features = undefined; this._helper = undefined; this._state = {}; } } if (name === 'NATIVE_APP') { return reset ? resetReference(this) : this; } const spec = this._spec; let currentContext = this.currentContext.target; let contextInfo; try { contextInfo = await getContextInfo(currentContext); } catch (err) { return reset ? resetReference(this) : this; } const path = []; if (spec.parentContext) { while (!contextInfo.isRoot) { currentContext = await spec.parentContext(currentContext); const contextReference = await findContextReference(currentContext, contextInfo); if (!contextReference) throw new Error('Unable to find out the chain of frames'); path.unshift(contextReference); contextInfo = await getContextInfo(currentContext); } } else { currentContext = await spec.mainContext(currentContext); path.push(...(await findContextPath(currentContext, contextInfo))); } this._currentContext = this._mainContext; await this.switchToChildContext(...path); return reset ? resetReference(this) : this; function resetReference(driver) { return new Proxy(driver._original, { get(driver, key, receiver) { if (key === '_original') return driver; return Reflect.get(driver, key, receiver); }, }); } async function getContextInfo(context) { const [documentElement, selector, isRoot, isCORS] = await spec.executeScript(context, snippets.getContextInfo); return { documentElement, selector, isRoot, isCORS }; } async function getChildContextsInfo(context) { const framesInfo = await spec.executeScript(context, snippets.getChildFramesInfo); return framesInfo.map(([contextElement, isCORS]) => ({ contextElement, isCORS, })); } async function isEqualElements(context, element1, element2) { return spec.executeScript(context, snippets.isEqualElements, [element1, element2]).catch(() => false); } async function findContextReference(context, contextInfo) { if (contextInfo.selector) { const contextElement = await spec.findElement(context, (0, selector_1.makeSelector)({ selector: { type: 'xpath', selector: contextInfo.selector }, spec, environment: { isWeb: true } })); if (contextElement) return contextElement; } for (const childContextInfo of await getChildContextsInfo(context)) { if (childContextInfo.isCORS !== contextInfo.isCORS) continue; const childContext = await spec.childContext(context, childContextInfo.contextElement); const contentDocument = await spec.findElement(childContext, (0, selector_1.makeSelector)({ selector: 'html', spec, environment: { isWeb: true } })); const isWantedContext = await isEqualElements(childContext, contentDocument, contextInfo.documentElement); await spec.parentContext(childContext); if (isWantedContext) return childContextInfo.contextElement; } return null; } async function findContextPath(context, contextInfo, contextPath = []) { const contentDocument = await spec.findElement(context, (0, selector_1.makeSelector)({ selector: 'html', spec, environment: { isWeb: true } })); if (await isEqualElements(context, contentDocument, contextInfo.documentElement)) { return contextPath; } for (const childContextInfo of await getChildContextsInfo(context)) { const childContext = await spec.childContext(context, childContextInfo.contextElement); const possibleContextPath = [...contextPath, childContextInfo.contextElement]; const wantedContextPath = await findContextPath(childContext, contextInfo, possibleContextPath); await spec.mainContext(context); if (wantedContextPath) return wantedContextPath; for (const contextElement of contextPath) { await spec.childContext(context, contextElement); } } } } async getDriverInfo({ force } = {}) { var _a, _b, _c; if (!this._driverInfo || force) { this._driverInfo = (_c = (await ((_b = (_a = this._spec).getDriverInfo) === null || _b === void 0 ? void 0 : _b.call(_a, this.target)))) !== null && _c !== void 0 ? _c : {}; this._logger.log('Extracted driver info', this._driverInfo); } return this._driverInfo; } async getCapabilities({ force } = {}) { var _a, _b, _c, _d, _e; if (((_a = this._driverInfo) === null || _a === void 0 ? void 0 : _a.capabilities) === undefined || force) { (_b = this._driverInfo) !== null && _b !== void 0 ? _b : (this._driverInfo = {}); this._driverInfo.capabilities = (_e = (await ((_d = (_c = this._spec).getCapabilities) === null || _d === void 0 ? void 0 : _d.call(_c, this.target)))) !== null && _e !== void 0 ? _e : null; this._logger.log('Extracted driver capabilities', this._driverInfo.capabilities); } return this._driverInfo.capabilities; } async getUserAgent({ force } = {}) { var _a, _b, _c, _d, _e; var _f; if (((_a = this._driverInfo) === null || _a === void 0 ? void 0 : _a.userAgent) === undefined || force) { (_b = this._driverInfo) !== null && _b !== void 0 ? _b : (this._driverInfo = {}); (_c = (_f = this._driverInfo).userAgent) !== null && _c !== void 0 ? _c : (_f.userAgent = (_d = (await this.currentContext.executePoll(snippets.getUserAgent, { main: undefined, poll: undefined, pollTimeout: 100, }))) !== null && _d !== void 0 ? _d : null); this._logger.log('Extracted user agent', this._driverInfo.userAgent); } return (_e = this._driverInfo.userAgent) !== null && _e !== void 0 ? _e : undefined; } async getUserAgentLegacy({ force } = {}) { const userAgent = await this.getUserAgent({ force }); return utils.types.isObject(userAgent) ? userAgent === null || userAgent === void 0 ? void 0 : userAgent.legacy : userAgent; } async getEnvironment() { var _a, _b, _c, _d; var _e; if (!this._environment) { const driverInfo = await this.getDriverInfo(); this._environment = { ...driverInfo.environment }; const capabilities = await this.getCapabilities(); const capabilitiesEnvironment = capabilities ? (0, capabilities_1.extractCapabilitiesEnvironment)(capabilities) : null; this._logger.log('Extracted capabilities environment', capabilitiesEnvironment); this._environment = { ...this._environment, ...capabilitiesEnvironment }; if (this._environment.isMobile) { const world = await this.getCurrentWorld(); this._environment.isNative = world === 'NATIVE_APP'; if (!!(world === null || world === void 0 ? void 0 : world.includes('WEBVIEW')) && !this._environment.browserName) { this._environment.isNative = true; this._environment.isWeb = true; } } (_a = (_e = this._environment).isWeb) !== null && _a !== void 0 ? _a : (_e.isWeb = !this._environment.isNative); if (this._environment.isWeb) { const userAgent = await this.getUserAgent(); const userAgentEnvironment = userAgent ? (0, user_agent_1.extractUserAgentEnvironment)(userAgent) : null; this._logger.log('Extracted user agent environment', userAgentEnvironment); this._environment = { ...this._environment, ...userAgentEnvironment, // NOTE: not really necessary, but some user agents for mobile devices (iPads) may return a wrong platform info ...(this._environment.isMobile ? { platformName: (_b = this._environment.platformName) !== null && _b !== void 0 ? _b : userAgentEnvironment === null || userAgentEnvironment === void 0 ? void 0 : userAgentEnvironment.platformName, platformVersion: (_c = this._environment.platformVersion) !== null && _c !== void 0 ? _c : userAgentEnvironment === null || userAgentEnvironment === void 0 ? void 0 : userAgentEnvironment.platformVersion, } : {}), }; } if (this._environment.browserName) { this._environment.isIE = /(internet explorer|ie)/i.test(this._environment.browserName); this._environment.isEdge = /edge/i.test(this._environment.browserName) && Number.parseInt(this._environment.browserVersion) >= 79; this._environment.isEdgeLegacy = /edge/i.test(this._environment.browserName) && Number.parseInt(this._environment.browserVersion) < 79; this._environment.isChrome = /chrome/i.test(this._environment.browserName); this._environment.isChromium = this._environment.isChrome || this._environment.isEdge; } if (this._environment.platformName) { this._environment.isWindows = /Windows/i.test(this._environment.platformName); this._environment.isMac = /mac\s?OS/i.test(this._environment.platformName); this._environment.isAndroid = /Android/i.test(this._environment.platformName); this._environment.isIOS = /iOS/i.test(this._environment.platformName); } if ((this._environment.isAndroid || this._environment.isIOS) && this._environment.isWeb && !this._environment.isMobile) { this._environment.isMobile = true; this._environment.isEmulation = true; } const driverUrl = (_d = (await this.getDriverUrl())) !== null && _d !== void 0 ? _d : ''; this._environment.isEC = this._environment.isECClient || utils.general.getEnvValue('IS_EC', 'boolean') || /exec-wus\.applitools\.com/.test(driverUrl); this._environment.isKobiton = /kobiton/.test(driverUrl); this._logger.log('Extracted environment', this._environment); } return this._environment; } async getViewport({ keepNavigationBar } = {}) { var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2; var _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18; if (!this._viewport) { const environment = await this.getEnvironment(); const driverInfo = await this.getDriverInfo(); this._viewport = { ...driverInfo.viewport }; if (environment.isMobile) { if (!this._viewport.orientation) { const orientation = await this.getOrientation(); if (orientation) this._viewport.orientation = orientation; } } if (environment.isNative) { const capabilities = await this.getCapabilities(); const capabilitiesViewport = capabilities ? (0, capabilities_1.extractCapabilitiesViewport)(capabilities) : null; this._logger.log('Extracted capabilities viewport', capabilitiesViewport); this._viewport = { ...capabilitiesViewport, ...this._viewport }; // this value always excludes the height of the navigation bar, and sometimes it also excludes the height of the status bar let windowSize = await this._spec.getWindowSize(this.target); (_a = (_3 = this._viewport).displaySize) !== null && _a !== void 0 ? _a : (_3.displaySize = windowSize); if (((_b = this._viewport.orientation) === null || _b === void 0 ? void 0 : _b.startsWith('landscape')) && this._viewport.displaySize.height > this._viewport.displaySize.width) { this._viewport.displaySize = utils.geometry.rotate(this._viewport.displaySize, 90); } if (environment.isAndroid) { (_c = (_4 = this._viewport).pixelRatio) !== null && _c !== void 0 ? _c : (_4.pixelRatio = 1); const { statusBar, navigationBar } = (_f = (await ((_e = (_d = this._spec).getSystemBars) === null || _e === void 0 ? void 0 : _e.call(_d, this.target).catch(() => undefined)))) !== null && _f !== void 0 ? _f : {}; if (statusBar === null || statusBar === void 0 ? void 0 : statusBar.visible) { this._logger.log('Driver status bar', statusBar); const statusBarSize = statusBar.height; // when status bar is overlapping content on android it returns status bar height equal to display height if (statusBarSize < this._viewport.displaySize.height) { this._viewport.statusBarSize = Math.max((_g = this._viewport.statusBarSize) !== null && _g !== void 0 ? _g : 0, statusBarSize); } } if ((navigationBar === null || navigationBar === void 0 ? void 0 : navigationBar.visible) && !keepNavigationBar) { this._logger.log('Driver navigation size', navigationBar); // if navigation bar is placed on the right side of the screen then the orientation is landscape-secondary if (navigationBar.x > 0) this._viewport.orientation = 'landscape-secondary'; // navigation bar size could be its height or width depending on screen orientation const navigationBarSize = navigationBar[((_h = this._viewport.orientation) === null || _h === void 0 ? void 0 : _h.startsWith('landscape')) ? 'width' : 'height']; // when navigation bar is invisible on android it returns navigation bar size equal to display size if (navigationBarSize < this._viewport.displaySize[((_j = this._viewport.orientation) === null || _j === void 0 ? void 0 : _j.startsWith('landscape')) ? 'width' : 'height']) { this._viewport.navigationBarSize = Math.max((_k = this._viewport.navigationBarSize) !== null && _k !== void 0 ? _k : 0, navigationBarSize); } else { this._viewport.navigationBarSize = 0; } } // bar sizes have to be scaled on android (_5 = this._viewport).statusBarSize && (_5.statusBarSize = this._viewport.statusBarSize / this._viewport.pixelRatio); (_6 = this._viewport).navigationBarSize && (_6.navigationBarSize = this._viewport.navigationBarSize / this._viewport.pixelRatio); windowSize = utils.geometry.scale(windowSize, 1 / this._viewport.pixelRatio); (_7 = this._viewport).displaySize && (_7.displaySize = utils.geometry.scale(this._viewport.displaySize, 1 / this._viewport.pixelRatio)); (_l = (_8 = this._viewport).navigationBarSize) !== null && _l !== void 0 ? _l : (_8.navigationBarSize = 0); } else if (environment.isIOS) { if (!this._viewport.pixelRatio || !this._viewport.statusBarSize) { try { const screen = await this.execute('mobile:deviceScreenInfo'); this._viewport.pixelRatio = (_o = (_m = screen.scale) !== null && _m !== void 0 ? _m : this._viewport.pixelRatio) !== null && _o !== void 0 ? _o : 3; this._viewport.statusBarSize = (_p = screen.statusBarSize) === null || _p === void 0 ? void 0 : _p.height; } catch (e) { this._logger.log(`Unable to extract device screen info - fallback to heuristic`, e); const scale2indicators = ['ipad', 'se', 'xr', 'iphone 11']; if (scale2indicators.some(indicator => { var _a, _b; return (_b = (_a = this._environment) === null || _a === void 0 ? void 0 : _a.deviceName) === null || _b === void 0 ? void 0 : _b.toLowerCase().includes(indicator); })) { this._viewport.pixelRatio = 2; } else { this._viewport.pixelRatio = 3; } } finally { (_q = (_9 = this._viewport).statusBarSize) !== null && _q !== void 0 ? _q : (_9.statusBarSize = 0); } } if ((_r = this._viewport.orientation) === null || _r === void 0 ? void 0 : _r.startsWith('landscape')) this._viewport.statusBarSize = 0; } (_s = (_10 = this._viewport).pixelRatio) !== null && _s !== void 0 ? _s : (_10.pixelRatio = 1); (_t = (_11 = this._viewport).statusBarSize) !== null && _t !== void 0 ? _t : (_11.statusBarSize = 0); // calculate viewport location (_u = (_12 = this._viewport).viewportLocation) !== null && _u !== void 0 ? _u : (_12.viewportLocation = { x: this._viewport.orientation === 'landscape' ? (_v = this._viewport.navigationBarSize) !== null && _v !== void 0 ? _v : 0 : 0, y: this._viewport.statusBarSize, }); // calculate viewport size if (!this._viewport.viewportSize) { this._viewport.viewportSize = { ...this._viewport.displaySize }; this._viewport.viewportSize.height -= this._viewport.statusBarSize; if (environment.isAndroid && !keepNavigationBar) { this._viewport.viewportSize[((_w = this._viewport.orientation) === null || _w === void 0 ? void 0 : _w.startsWith('landscape')) ? 'width' : 'height'] -= this._viewport.navigationBarSize; } } // calculate safe area if (!environment.isWeb && environment.isIOS && !this._viewport.safeArea) { this._viewport.safeArea = { x: 0, y: 0, ...this._viewport.displaySize }; const topElement = await this.element({ type: '-ios class chain', selector: '**/XCUIElementTypeNavigationBar', }); if (topElement) { const topRegion = await this._spec.getElementRegion(this.target, topElement.target); const topOffset = topRegion.y + topRegion.height; this._viewport.safeArea.y = topOffset; this._viewport.safeArea.height -= topOffset; } const bottomElement = await this.element({ type: '-ios class chain', selector: '**/XCUIElementTypeTabBar', }); if (bottomElement) { const bottomRegion = await this._spec.getElementRegion(this.target, bottomElement.target); const bottomOffset = bottomRegion.height; this._viewport.safeArea.height -= bottomOffset; } } } if (environment.isWeb) { const browserViewport = await this.execute(snippets.getViewport); (_x = (_13 = this._viewport).viewportSize) !== null && _x !== void 0 ? _x : (_13.viewportSize = browserViewport.viewportSize); (_y = (_14 = this._viewport).pixelRatio) !== null && _y !== void 0 ? _y : (_14.pixelRatio = browserViewport.pixelRatio); (_z = (_15 = this._viewport).viewportScale) !== null && _z !== void 0 ? _z : (_15.viewportScale = browserViewport.viewportScale); (_0 = (_16 = this._viewport).orientation) !== null && _0 !== void 0 ? _0 : (_16.orientation = browserViewport.orientation); } (_1 = (_17 = this._viewport).pixelRatio) !== null && _1 !== void 0 ? _1 : (_17.pixelRatio = 1); (_2 = (_18 = this._viewport).viewportScale) !== null && _2 !== void 0 ? _2 : (_18.viewportScale = 1); this._logger.log('Extracted viewport', this._viewport); } return this._viewport; } async getFeatures() { var _a; var _b; if (!this._features) { const driverInfo = await this.getDriverInfo(); this._features = { ...driverInfo.features }; const environment = await this.getEnvironment(); (_a = (_b = this._features).allCookies) !== null && _a !== void 0 ? _a : (_b.allCookies = environment.isChromium || !environment.isMobile); this._logger.log('Extracted driver features', this._features); } return this._features; } async getSessionId() { var _a; const driverInfo = await this.getDriverInfo(); return (_a = driverInfo.sessionId) !== null && _a !== void 0 ? _a : null; } async getDriverUrl() { var _a; const driverInfo = await this.getDriverInfo(); return (_a = driverInfo.driverServerUrl) !== null && _a !== void 0 ? _a : null; } async getHelper() { var _a, _b, _c; if (this._helper === undefined) { if (utils.general.getEnvValue('IGNORE_HELPER_LIB', 'boolean')) { this._logger.log(`Skipping helper lib extraction`); this._helper = null; } else { const environment = await this.getEnvironment(); this._logger.log(`Extracting helper for ${environment.isIOS ? 'ios' : 'android'}`); this._helper = environment.isIOS ? await helper_ios_1.HelperIOS.make({ spec: this._spec, driver: this }) : await helper_android_1.HelperAndroid.make({ spec: this._spec, driver: this }); this._logger.log(`Extracted helper of type ${(_a = this._helper) === null || _a === void 0 ? void 0 : _a.name}`); } } this._logger.log(`Returning helper for of type ${(_c = (_b = this._helper) === null || _b === void 0 ? void 0 : _b.name) !== null && _c !== void 0 ? _c : null}`); return this._helper; } async extractBrokerUrl() { var _a; var _b; const environment = await this.getEnvironment(); if (!environment.isNative) return null; this._logger.log('Broker url extraction is started'); (_a = (_b = this._state).nmlElement) !== null && _a !== void 0 ? _a : (_b.nmlElement = await this.waitFor({ type: 'accessibility id', selector: 'Applitools_View' }, { timeout: 10000 }).catch(() => null)); if (!this._state.nmlElement) { this._logger.log('Broker url extraction is failed due to absence of nml element'); return null; } try { let result; do { result = JSON.parse(await this._state.nmlElement.getText()); if (result.nextPath) { this._logger.log('Broker url extraction finished successfully with value', result.nextPath); return result.nextPath; } await utils.general.sleep(1000); } while (!result.error); this._logger.error('Broker url extraction has failed with error', result.error); return null; } catch (error) { this._logger.error('Broker url extraction has failed with error and will be retried', error); this._state.nmlElement = null; return this.extractBrokerUrl(); } } async getSessionMetadata() { var _a; // NOTE: not using this.getEnvironment() to not provoke environment extraction if it was not calculated yet. // It is faster to try to execute and fail then collect all of the environment information if (((_a = this._environment) === null || _a === void 0 ? void 0 : _a.isECClient) === false) return undefined; try { const metadata = await this.currentContext.execute('applitools:metadata'); this._logger.log('Extracted session metadata', metadata); return metadata; } catch (err) { this._logger.warn('Failed to extract session metadata due to the error', err); } } async getWorlds() { if (!this._spec.getWorlds || utils.general.getEnvValue('SKIP_DEPRECATED_JWP_COMMANDS', 'boolean')) return null; this._logger.log('Extracting worlds'); try { let worlds = []; for (let attempt = 0; worlds.length <= 1 && attempt < 3; ++attempt) { if (attempt > 0) await utils.general.sleep(500); worlds = await this._spec.getWorlds(this.target); } this._logger.log('Worlds were extracted', worlds); return worlds; } catch (error) { this._logger.warn('Worlds were not extracted due to the error', error); return null; } } async getCurrentWorld() { if (!this._spec.getCurrentWorld || utils.general.getEnvValue('SKIP_DEPRECATED_JWP_COMMANDS', 'boolean')) return null; try { this._logger.log('Extracting current world'); const current = await this._spec.getCurrentWorld(this.target); this._logger.log('Current world was extracted', current); return current; } catch (error) { this._logger.warn('Current world was not extracted due to the error', error); return null; } } async switchWorld(name) { name !== null && name !== void 0 ? name : (name = 'NATIVE_APP'); this._logger.log('Switching world to', name); if (!this._spec.switchWorld) { this._logger.error('Unable to switch world due to missed implementation'); throw new Error('Unable to switch world due to missed implementation'); } try { await this._spec.switchWorld(this.target, name); await this.refresh({ reset: true, name }); } catch (error) { this._logger.error('Unable to switch world due to the error', error); throw new Error(`Unable to switch world, the original error was: ${error.message}`); } } async switchTo(context) { if (await this.currentContext.equals(context)) { return (this._currentContext = context); } const currentPath = this.currentContext.path; const requiredPath = context.path; let diffIndex = -1; for (const [index, context] of requiredPath.entries()) { if (currentPath[index] && !(await currentPath[index].equals(context))) { diffIndex = index; break; } } if (diffIndex === 0) { throw new Error('Cannot switch to the context, because it has different main context'); } else if (diffIndex === -1) { if (currentPath.length === requiredPath.length) { // required and current paths are the same return this.currentContext; } else if (requiredPath.length > currentPath.length) { // current path is a sub-path of required path return this.switchToChildContext(...requiredPath.slice(currentPath.length)); } else if (currentPath.length - requiredPath.length <= requiredPath.length) { // required path is a sub-path of current path return this.switchToParentContext(currentPath.length - requiredPath.length); } else { // required path is a sub-path of current path await this.switchToMainContext(); return this.switchToChildContext(...requiredPath); } } else if (currentPath.length - diffIndex <= diffIndex) { // required path is different from current or they are partially intersected // chose an optimal way to traverse from current context to target context await this.switchToParentContext(currentPath.length - diffIndex); return this.switchToChildContext(...requiredPath.slice(diffIndex)); } else { await this.switchToMainContext(); return this.switchToChildContext(...requiredPath); } } async switchToMainContext() { const environment = await this.getEnvironment(); if (!environment.isWeb) throw new Error('Contexts are supported only for web drivers'); this._logger.log('Switching to the main context'); await this._spec.mainContext(this.currentContext.target); return (this._currentContext = this._mainContext); } async switchToParentContext(elevation = 1) { const environment = await this.getEnvironment(); if (!environment.isWeb) throw new Error('Contexts are supported only for web drivers'); this._logger.log('Switching to a parent context with elevation:', elevation); if (this.currentContext.path.length <= elevation) { return this.switchToMainContext(); } try { while (elevation > 0) { await this._spec.parentContext(this.currentContext.target); this._currentContext = this._currentContext.parent; elevation -= 1; } } catch (err) { this._logger.warn('Unable to switch to a parent context due to error', err); this._logger.log('Applying workaround to switch to the parent frame'); const path = this.currentContext.path.slice(1, -elevation); await this.switchToMainContext(); await this.switchToChildContext(...path); elevation = 0; } return this.currentContext; } async switchToChildContext(...references) { const environment = await this.getEnvironment(); if (!environment.isWeb) throw new Error('Contexts are supported only for web drivers'); this._logger.log('Switching to a child context with depth:', references.length); for (const reference of references) { if (reference === this.mainContext) continue; const context = await this.currentContext.context(reference); await context.focus(); } return this.currentContext; } async normalizeRegion(region) { const environment = await this.getEnvironment(); if (environment.isWeb) return region; const viewport = await this.getViewport(); let normalizedRegion = region; if (environment.isAndroid) { normalizedRegion = utils.geometry.scale(normalizedRegion, 1 / viewport.pixelRatio); } if (environment.isIOS && viewport.safeArea && utils.geometry.isIntersected(normalizedRegion, viewport.safeArea)) { normalizedRegion = utils.geometry.intersect(normalizedRegion, viewport.safeArea); } if (viewport.viewportLocation) { normalizedRegion = utils.geometry.offsetNegative(normalizedRegion, viewport.viewportLocation); } if (normalizedRegion.y < 0) { normalizedRegion.height += normalizedRegion.y; normalizedRegion.y = 0; } return normalizedRegion; } async getRegionInViewport(context, region) { await context.focus(); return context.getRegionInViewport(region); } async takeScreenshot() { const image = await this._spec.takeScreenshot(this.target); if (utils.types.isString(image)) { return buffer_1.Buffer.from(image.replace(/[\r\n]+/g, ''), 'base64'); } return image; } async getViewportSize() { var _a; const environment = await this.getEnvironment(); let size; if (environment.isNative && !environment.isWeb) { const viewport = await this.getViewport(); if (viewport.viewportSize) { this._logger.log('Extracting viewport size from native driver using cached value'); size = viewport.viewportSize; } else { this._logger.log('Extracting viewport size from native driver'); size = (await this.getDisplaySize()); size.height -= (_a = viewport.statusBarSize) !== null && _a !== void 0 ? _a : 0; } this._logger.log(`Rounding viewport size using`, this._customConfig.useCeilForViewportSize ? 'ceil' : 'round'); if (this._customConfig.useCeilForViewportSize) { size = utils.geometry.ceil(size); } else { size = utils.geometry.round(size); } } else if (this._spec.getViewportSize) { this._logger.log('Extracting viewport size from web driver using spec method'); size = await this._spec.getViewportSize(this.target); } else { this._logger.log('Extracting viewport size from web driver using js snippet'); const viewport = await this.mainContext.execute(snippets.getViewport); size = viewport.viewportSize; } this._logger.log('Extracted viewport size', size); return size; } async setViewportSize(size) { const environment = await this.getEnvironment(); if (environment.isMobile && !environment.isEmulation) return; if (this._spec.setViewportSize && (!this._spec.setWindowSize || environment.isChromium)) { this._logger.log('Setting viewport size to', size, 'using spec method'); await this._spec.setViewportSize(this.target, size); return; } this._logger.log('Setting viewport size to', size, 'using workaround'); const requiredViewportSize = size; let currentViewportSize = await this.getViewportSize(); if (utils.geometry.equals(currentViewportSize, requiredViewportSize)) return; let currentWindowSize = await this._spec.getWindowSize(this.target); this._logger.log('Extracted window size', currentWindowSize); let attempt = 0; while (attempt++ < 3) { const requiredWindowSize = { width: Math.max(0, currentWindowSize.width + (requiredViewportSize.width - currentViewportSize.width)), height: Math.max(0, currentWindowSize.height + (requiredViewportSize.height - currentViewportSize.height)), }; this._logger.log(`Attempt #${attempt} to set viewport size by setting window size to`, requiredWindowSize); await this._spec.setWindowSize(this.target, requiredWindowSize); const prevViewportSize = currentViewportSize; currentViewportSize = await this.getViewportSize(); if (utils.geometry.equals(currentViewportSize, prevViewportSize)) { currentViewportSize = await this.getViewportSize(); } currentWindowSize = requiredWindowSize; if (utils.geometry.equals(currentViewportSize, requiredViewportSize)) return; this._logger.log(`Attempt #${attempt} to set viewport size failed. Current viewport:`, currentViewportSize); } throw new Error('Failed to set viewport size!'); } async getDisplaySize() { var _a; const environment = await this.getEnvironment(); if (!environment.isNative) return undefined; const viewport = await this.getViewport(); if (viewport === null || viewport === void 0 ? void 0 : viewport.displaySize) { this._logger.log('Extracting display size from native driver using cached value', viewport.displaySize); return viewport.displaySize; } let size = await this._spec.getWindowSize(this.target); if (((_a = (await this.getOrientation())) === null || _a === void 0 ? void 0 : _a.startsWith('landscape')) && size.height > size.width) { size = { width: size.height, height: size.width }; } const normalizedSize = environment.isAndroid ? utils.geometry.scale(size, 1 / viewport.pixelRatio) : size; this._logger.log('Extracted and normalized display size:', normalizedSize); return normalizedSize; } async getOrientation() { var _a, _b, _c; const environment = await this.getEnvironment(); if (!environment.isMobile) return undefined; if (environment.isWeb) return (_a = this._viewport) === null || _a === void 0 ? void 0 : _a.orientation; if (environment.isAndroid) { if (utils.general.getEnvValue('AVOID_ADB_USAGE', 'boolean')) { this._logger.log(`Skipping device orientation extraction using adb command on android`); } else { this._logger.log('Extracting device orientation using adb command on android'); const rotation = await this.execute('mobile:shell', { command: "dumpsys window | grep 'mCurrentRotation' | cut -d = -f2", }) .then(rotation => { var _a; return (_a = rotation === null || rotation === void 0 ? void 0 : rotation.trim) === null || _a === void 0 ? void 0 : _a.call(rotation); }) .catch(() => null); if (rotation) { let orientation = undefined; if (rotation === 'ROTATION_0' || rotation === '0') orientation = 'portrait'; else if (rotation === 'ROTATION_90' || rotation === '3') orientation = 'landscape-secondary'; else if (rotation === 'ROTATION_180' || rotation === '2') orientation = 'portrait-secondary'; else if (rotation === 'ROTATION_270' || rotation === '1') orientation = 'landscape'; this._logger.log('Extracted device orientation:', orientation); return orientation; } } } this._logger.log('Extracting device orientation'); const orientation = await ((_c = (_b = this._spec).getOrientation) === null || _c === void 0 ? void 0 : _c.call(_b, this.target)); this._logger.log('Extracted device orientation:', orientation); return orientation; } async setOrientation(orientation) { const environment = await this.getEnvironment(); if (!environment.isMobile) return undefined; this._logger.log('Set device orientation:', orientation); await this._spec.setOrientation(this.target, orientation); } async getCookies() { var _a, _b, _c; const environment = await this.getEnvironment(); const features = await this.getFeatures(); if (environment.isNative || !features.allCookies) return []; try { const cookies = await ((_b = (_a = this._spec).getCookies) === null || _b === void 0 ? void 0 : _b.call(_a, this.target)); this._logger.log('Extracted driver cookies', cookies); return cookies !== null && cookies !== void 0 ? cookies : []; } catch (error) { this._logger.error('Error while extracting driver cookies', error); (_c = this._features) !== null && _c !== void 0 ? _c : (this._features = {}); this._features.allCookies = false; throw error; } } async getTitle() { var _a, _b, _c; const environment = await this.getEnvironment(); if (environment.isNative) return undefined; const title = (_c = (await ((_b = (_a = this._spec).getTitle) === null || _b === void 0 ? void 0 : _b.call(_a, this.target)))) !== null && _c !== void 0 ? _c : ''; this._logger.log('Extracted title:', title); return title; } async getUrl() { var _a, _b, _c; const environment = await this.getEnvironment(); if (environment.isNative) return undefined; const url = (_c = (await ((_b = (_a = this._spec).getUrl) === null || _b === void 0 ? void 0 : _b.call(_a, this.target)))) !== null && _c !== void 0 ? _c : ''; this._logger.log('Extracted url:', url); return url; } async element(selector) { return this.currentContext.element(selector); } async elements(selector) { return this.currentContext.elements(selector); } async waitFor(selector, options) { return this.currentContext.waitFor(selector, options); } async execute(script, arg) { return this.currentContext.execute(script, arg); } async executeUserFunction(func) { var _a, _b; if (await ((_b = (_a = this._spec).isUserFunction) === null || _b === void 0 ? void 0 : _b.call(_a, func))) { return this._spec.executeUserFunction(func); } else if (typeof func === 'function') { return func(); } else { throw new Error(`User function is not supported (${JSON.stringify(func)})`); } } async visit(url) { var _a, _b; await ((_b = (_a = this._spec).visit) === null || _b === void 0 ? void 0 : _b.call(_a, this.target, url)); } } exports.Driver = Driver; function isDriverInstance(driver) { return driver instanceof Driver; } exports.isDriverInstance = isDriverInstance; function isDriver(driver, spec) { var _a; return isDriverInstance(driver) || !!(spec === null || spec === void 0 ? void 0 : spec.isDriver(driver)) || !!((_a = spec === null || spec === void 0 ? void 0 : spec.isSecondaryDriver) === null || _a === void 0 ? void 0 : _a.call(spec, driver)); } exports.isDriver = isDriver; async function makeDriver(options) { var _a, _b, _c; let driver; if (options.driver instanceof Driver) { driver = options.driver; } else { options.driver = (_c = (await ((_b = (_a = options.spec) === null || _a === void 0 ? void 0 : _a.toDriver) === null || _b === void 0 ? void 0 : _b.call(_a, options.driver)))) !== null && _c !== void 0 ? _c : options.driver; driver = new Driver(options); } if (options.logger) driver.updateLogger(options.logger); return driver.refresh({ reset: options.reset }); } exports.makeDriver = makeDriver;