@applitools/driver
Version:
Applitools universal framework wrapper
929 lines (928 loc) • 48.7 kB
JavaScript
"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;