UNPKG

@applitools/driver

Version:

Applitools universal framework wrapper

583 lines (582 loc) 26.6 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.Context = void 0; const selector_1 = require("./selector"); const element_1 = require("./element"); const utils = __importStar(require("@applitools/utils")); const snippets = require('@applitools/snippets'); class Context { _isReference(reference) { return (utils.types.isInteger(reference) || utils.types.isString(reference) || (0, element_1.isElement)(reference, this._spec) || (0, selector_1.isSelector)(reference, this._spec)); } constructor(options) { var _a, _b, _c, _d; this._state = {}; /** * The purpose of this state is to make sure we don't search for the SRE multiple times. * This is unwanted to have but needed, search for `.setScrollingElement(settings.scrollRootElement ?? null)`, * I don't want to risk breaking it. * e.g in js/packages/core/src/classic/check.ts */ this._searchScrollingElement = true; this._spec = options.spec; if (options.context) { if ((_c = (_b = (_a = this._spec).isContext) === null || _b === void 0 ? void 0 : _b.call(_a, options.context)) !== null && _c !== void 0 ? _c : this._spec.isDriver(options.context)) { this._target = options.context; } else { throw new TypeError('Context constructor called with target context of unknown type'); } } if (this._isReference(options.reference)) { if (!options.parent) { throw new TypeError('Cannot construct child context without parent context'); } this._reference = options.reference; this._parent = options.parent; this._scrollingElement = options.scrollingElement; this._driver = options.driver || ((_d = this._parent) === null || _d === void 0 ? void 0 : _d.driver); } else if (!options.reference) { this._scrollingElement = options.scrollingElement; this._driver = options.driver; } else { throw new TypeError('Context constructor called with context reference of unknown type!'); } } get logger() { return this._driver.logger; } get target() { return this._target; } get driver() { return this._driver; } get parent() { var _a; return (_a = this._parent) !== null && _a !== void 0 ? _a : null; } get main() { var _a, _b; return (_b = (_a = this.parent) === null || _a === void 0 ? void 0 : _a.main) !== null && _b !== void 0 ? _b : this; } get path() { var _a, _b; return [...((_b = (_a = this.parent) === null || _a === void 0 ? void 0 : _a.path) !== null && _b !== void 0 ? _b : []), this]; } get isMain() { return this.main === this; } get isCurrent() { return this.driver.currentContext === this; } get isInitialized() { return Boolean(this._element) || this.isMain; } get isRef() { return !this._target; } async _findElements(selector, options = {}) { var _a; await this.focus(); const { parent, all, wait } = options; const environment = await this.driver.getEnvironment(); const transformedSelector = (0, selector_1.makeSelector)({ selector, spec: this._spec, environment }); let elements = []; if (wait) { if (this._spec.waitForSelector) { const element = await this._spec.waitForSelector(this.target, (0, selector_1.makeSelector)({ selector, spec: this._spec, environment }), parent, wait); if (element) elements = [element]; } else { let waiting = true; const timeout = setTimeout(() => (waiting = false), wait.timeout); while (waiting) { const element = await this._spec.findElement(this.target, (0, selector_1.makeSelector)({ selector, spec: this._spec, environment }), parent); if (element) { clearTimeout(timeout); elements = [element]; break; } await utils.general.sleep((_a = wait.interval) !== null && _a !== void 0 ? _a : 0); } } } else if (all) { elements = await this._spec.findElements(this.target, transformedSelector, parent); } else { const element = await this._spec.findElement(this.target, transformedSelector, parent); if (element) elements = [element]; } if ((0, selector_1.isComplexSelector)(selector, this._spec)) { const features = await this.driver.getFeatures(); if (elements.length > 0) { if (selector.child && !features.nestedSelectors) { elements = await elements.reduce((result, element) => { return result.then(async (result) => { return result.concat(await this._findElements(selector.child, { parent: element, all, wait })); }); }, Promise.resolve([])); } else if (selector.shadow && !features.nestedSelectors) { elements = await elements.reduce((result, element) => { return result.then(async (result) => { const root = await this._spec.executeScript(this.target, snippets.getShadowRoot, [ element, ]); return result.concat(root ? await this._findElements(selector.shadow, { parent: root, all, wait }) : []); }); }, Promise.resolve([])); } else if (selector.frame) { elements = await elements.reduce((result, element) => { return result.then(async (result) => { const context = await this.context(element); return result.concat(await context._findElements(selector.frame, { all, wait })); }); }, Promise.resolve([])); } } if (elements.length === 0 && selector.fallback) { elements = await this._findElements(selector.fallback, { parent }); } } return elements; } async init() { if (this.isInitialized) return this; if (!this._reference || !this.parent) { throw new TypeError('Cannot initialize context without a reference to the context element'); } await this.parent.focus(); this.logger.log('Context initialization'); if (utils.types.isInteger(this._reference)) { this.logger.log('Getting context element using index:', this._reference); const elements = await this.parent.elements('frame, iframe'); if (this._reference > elements.length) { throw new TypeError(`Context element with index ${this._reference} is not found`); } this._element = elements[this._reference]; } else if (utils.types.isString(this._reference) || (0, selector_1.isSelector)(this._reference, this._spec)) { if (utils.types.isString(this._reference)) { this.logger.log('Getting context element by name or id', this._reference); this._element = await this.parent .element(`iframe[name="${this._reference}"], iframe#${this._reference}`) .catch(() => null); } if (!this._element && (0, selector_1.isSelector)(this._reference, this._spec)) { this.logger.log('Getting context element by selector', this._reference); this._element = await this.parent.element(this._reference); } if (!this._element) { throw new TypeError(`Context element with name, id, or selector ${JSON.stringify(this._reference)}' is not found`); } } else if ((0, element_1.isElement)(this._reference, this._spec)) { this.logger.log('Initialize context from reference element', this._reference); this._element = await (0, element_1.makeElement)({ spec: this._spec, context: this.parent, element: this._reference, }); } else { throw new TypeError('Reference type does not supported'); } this._reference = null; return this; } async focus() { if (this.isCurrent) { return this; } else if (this.isMain) { await this.driver.switchToMainContext(); return this; } if (this.isRef) { await this.init(); } if (!this.parent.isCurrent) { await this.driver.switchTo(this); return this; } await this.parent.preserveInnerOffset(); if (this.parent.isMain) await this.parent.preserveContextRegions(); await this.preserveContextRegions(); this._target = await this._spec.childContext(this.parent.target, this._element.target); this.driver.updateCurrentContext(this); return this; } async equals(context) { if (context === this || (this.isMain && context === null)) return true; if (!this._element) return false; return this._element.equals(context instanceof Context ? (await context.getContextElement()) : context); } async context(reference) { if (reference instanceof Context) { if (reference.parent !== this && !(await this.equals(reference.parent))) { throw Error('Cannot attach a child context because it has a different parent'); } return reference; } else if (this._isReference(reference)) { return new Context({ spec: this._spec, parent: this, driver: this.driver, reference }); } else if (utils.types.has(reference, 'reference')) { if (reference.reference instanceof Context) return this; const parent = reference.parent ? await this.context(reference.parent) : this; return new Context({ spec: this._spec, parent, driver: this.driver, reference: reference.reference, scrollingElement: reference === null || reference === void 0 ? void 0 : reference.scrollingElement, }); } else { throw new Error('Cannot get context using reference of unknown type!'); } } async element(elementOrSelector) { if ((0, element_1.isElement)(elementOrSelector, this._spec)) { return (0, element_1.makeElement)({ spec: this._spec, context: this, element: elementOrSelector }); } else if (!(0, selector_1.isSelector)(elementOrSelector, this._spec)) { throw new TypeError('Cannot find element using argument of unknown type!'); } if (this.isRef) { return (0, element_1.makeElement)({ spec: this._spec, context: this, selector: elementOrSelector }); } this.logger.log('Finding element by selector: ', elementOrSelector); const [element] = await this._findElements(elementOrSelector, { all: false }); return element ? (0, element_1.makeElement)({ spec: this._spec, context: this, element, selector: elementOrSelector }) : null; } async elements(selectorOrElement) { if ((0, selector_1.isSelector)(selectorOrElement, this._spec)) { if (this.isRef) { return [await (0, element_1.makeElement)({ spec: this._spec, context: this, selector: selectorOrElement })]; } this.logger.log('Finding elements by selector: ', selectorOrElement); const elements = await this._findElements(selectorOrElement, { all: true }); return Promise.all(elements.map((element, index) => { return (0, element_1.makeElement)({ spec: this._spec, context: this, element, selector: selectorOrElement, index, }); })); } else if ((0, element_1.isElement)(selectorOrElement, this._spec)) { return [await (0, element_1.makeElement)({ spec: this._spec, context: this, element: selectorOrElement })]; } else if (selectorOrElement === undefined) { throw new TypeError('Cannot find undefined elements!'); } else { throw new TypeError('Cannot find elements using argument of unknown type!'); } } async waitFor(selector, options) { this.logger.log('Waiting for element by selector: ', selector, 'and options', options); const [element] = await this._findElements(selector, { wait: { state: 'exist', timeout: 10000, interval: 500, ...options }, }); return element ? (0, element_1.makeElement)({ spec: this._spec, context: this, element, selector }) : null; } async execute(script, arg) { await this.focus(); try { return await this._spec.executeScript(this.target, script, serialize.call(this, arg)); } catch (err) { this.logger.warn('Error during script execution with argument', arg); this.logger.error(err); throw err; } function serialize(value) { var _a, _b; if ((0, element_1.isElement)(value, this._spec)) { return (0, element_1.isElementInstance)(value) ? value.toJSON() : value; } else if (utils.types.isArray(value)) { return value.map(value => serialize.call(this, value)); } else if (utils.types.isObject(value)) { return Object.entries((_b = (_a = value.toJSON) === null || _a === void 0 ? void 0 : _a.call(value)) !== null && _b !== void 0 ? _b : value).reduce((serialized, [key, value]) => { return Object.assign(serialized, { [key]: serialize.call(this, value) }); }, {}); } else { return value; } } } async executePoll(script, arg) { this.logger.log('Executing poll script'); const { main: mainScript, poll: pollScript } = utils.types.isString(script) || utils.types.isFunction(script) ? { main: script, poll: script } : script; const { main: mainArg, poll: pollArg, executionTimeout = 60000, pollTimeout = 1000, } = !utils.types.has(arg, ['main', 'poll']) ? { main: arg, poll: arg } : arg; let isExecutionTimedOut = false; const executionTimer = setTimeout(() => (isExecutionTimedOut = true), executionTimeout); try { let response = deserialize(await this.execute(mainScript, mainArg)); let chunks = ''; while (!isExecutionTimedOut) { if (response.status === 'ERROR') { let errorMessage = response.error; try { errorMessage = JSON.stringify(errorMessage); } catch (err) { errorMessage = `Error during execute poll script: '${errorMessage}'. Addtionaly, the error is not serializable - ${err}`; } this.logger.error(`Error during execute poll script: '${errorMessage}'`); throw new Error(`Error during execute poll script: '${errorMessage}'`); } else if (response.status === 'SUCCESS') { return response.value; } else if (response.status === 'SUCCESS_CHUNKED') { chunks += response.value; if (response.done) return deserialize(chunks); } else if (response.status === 'WIP') { await utils.general.sleep(pollTimeout); } this.logger.log('Polling...'); response = deserialize(await this.execute(pollScript, pollArg)); } throw new Error('Poll script execution is timed out'); } finally { clearTimeout(executionTimer); } function deserialize(json) { try { return JSON.parse(json); } catch (err) { const firstChars = json.slice(0, 100); const lastChars = json.slice(-100); throw new Error(`Response is not a valid JSON string. length: ${json.length}, first 100 chars: "${firstChars}", last 100 chars: "${lastChars}". error: ${err}`); } } } async getContextElement() { var _a; if (this.isMain) return null; await this.init(); return (_a = this._element) !== null && _a !== void 0 ? _a : null; } async getScrollingElement() { if (!this._searchScrollingElement) return this._scrollingElement; if (!(0, element_1.isElementInstance)(this._scrollingElement)) { await this.focus(); if (this._scrollingElement) { this._scrollingElement = await this.element(this._scrollingElement); } else { const environment = await this.driver.getEnvironment(); if (environment.isWeb) { let selector; if (environment.isIOS && !environment.isEmulation) { selector = 'html'; this.logger.log(`Using hardcoded default scrolling element for Safari on iOS - "${selector}"`); } else { selector = await this.execute(snippets.getDocumentScrollingElement); this.logger.log(`Using dynamic default scrolling element - "${selector}"`); } this._scrollingElement = await this.element({ type: 'css', selector }); } else { this._scrollingElement = await this.element({ type: 'xpath', selector: '//*[@scrollable="true"]' }); } } if (this._scrollingElement === null) { this._searchScrollingElement = false; } } return this._scrollingElement; } async setScrollingElement(scrollingElement) { if (scrollingElement === undefined) return; else if (scrollingElement === null) this._scrollingElement = null; else { this._scrollingElement = await this.element(scrollingElement); } } async blurElement(element) { try { return await this.execute(snippets.blurElement, [element]); } catch (err) { this.logger.warn('Cannot blur element', element); this.logger.error(err); return null; } } async focusElement(element) { try { return await this.execute(snippets.focusElement, [element]); } catch (err) { this.logger.warn('Cannot focus element', element); this.logger.error(err); return null; } } async getRegion() { var _a, _b; if (this.isMain && this.isCurrent) { const viewportRegion = utils.geometry.region({ x: 0, y: 0 }, await this.driver.getViewportSize()); this._state.region = this._scrollingElement ? utils.geometry.region({ x: 0, y: 0 }, utils.geometry.intersect(viewportRegion, await this._scrollingElement.getRegion())) : viewportRegion; } else if ((_a = this.parent) === null || _a === void 0 ? void 0 : _a.isCurrent) { await this.init(); this._state.region = await ((_b = this._element) === null || _b === void 0 ? void 0 : _b.getRegion()); } return this._state.region; } async getClientRegion() { var _a, _b; if (this.isMain && this.isCurrent) { const viewportRegion = utils.geometry.region({ x: 0, y: 0 }, await this.driver.getViewportSize()); this._state.clientRegion = this._scrollingElement ? utils.geometry.region({ x: 0, y: 0 }, utils.geometry.intersect(viewportRegion, await this._scrollingElement.getClientRegion())) : viewportRegion; } else if ((_a = this.parent) === null || _a === void 0 ? void 0 : _a.isCurrent) { await this.init(); this._state.clientRegion = await ((_b = this._element) === null || _b === void 0 ? void 0 : _b.getClientRegion()); } return this._state.clientRegion; } async getScrollingRegion() { if (this.isCurrent) { const scrollingElement = await this.getScrollingElement(); this._state.scrollingRegion = await (scrollingElement === null || scrollingElement === void 0 ? void 0 : scrollingElement.getClientRegion()); } return this._state.scrollingRegion; } async getContentSize() { return this.execute(snippets.getDocumentSize); } async getInnerOffset() { if (this.isCurrent) { const scrollingElement = await this.getScrollingElement(); this._state.innerOffset = scrollingElement ? await scrollingElement.getInnerOffset() : { x: 0, y: 0 }; } return this._state.innerOffset; } async getLocationInMainContext() { return this.path.reduce((location, context) => { return location.then(async (location) => { return utils.geometry.offset(location, utils.geometry.location(await context.getClientRegion())); }); }, Promise.resolve({ x: 0, y: 0 })); } async getLocationInViewport() { var _a, _b; let location = utils.geometry.offsetNegative({ x: 0, y: 0 }, await this.getInnerOffset()); if (this.isMain) return location; let currentContext = this; while (currentContext) { const contextLocation = utils.geometry.location(await currentContext.getClientRegion()); const parentContextInnerOffset = (_b = (await ((_a = currentContext.parent) === null || _a === void 0 ? void 0 : _a.getInnerOffset()))) !== null && _b !== void 0 ? _b : { x: 0, y: 0 }; location = utils.geometry.offsetNegative(utils.geometry.offset(location, contextLocation), parentContextInnerOffset); currentContext = currentContext.parent; } return location; } async getRegionInViewport(region) { var _a, _b; this.logger.log('Converting context region to viewport region', region); if (region) region = utils.geometry.offsetNegative(region, await this.getInnerOffset()); else region = { x: 0, y: 0, width: Infinity, height: Infinity }; let currentContext = this; const environment = await this.driver.getEnvironment(); while (currentContext) { const contextRegion = await currentContext.getClientRegion(); // const contextScrollingRegion = await currentContext.getScrollingRegion() const parentContextInnerOffset = (_b = (await ((_a = currentContext.parent) === null || _a === void 0 ? void 0 : _a.getInnerOffset()))) !== null && _b !== void 0 ? _b : { x: 0, y: 0 }; // TODO revisit if (environment.isWeb || (!utils.geometry.equals(contextRegion, region) && utils.geometry.contains(contextRegion, region))) { this.logger.log('Intersecting context region', contextRegion, 'with context region', region); region = utils.geometry.intersect(contextRegion, utils.geometry.offset(region, contextRegion)); // region = utils.geometry.intersect(contextScrollingRegion, region) region = utils.geometry.offsetNegative(region, parentContextInnerOffset); } currentContext = currentContext.parent; } return region; } async getCookies() { var _a, _b; const environment = await this.driver.getEnvironment(); if (!environment.isWeb) return []; await this.focus(); const cookies = await ((_b = (_a = this._spec).getCookies) === null || _b === void 0 ? void 0 : _b.call(_a, this.target, true)); this.logger.log('Extracted context cookies', cookies); return cookies !== null && cookies !== void 0 ? cookies : []; } async preserveInnerOffset() { this._state.innerOffset = await this.getInnerOffset(); } async preserveContextRegions() { this._state.region = await this.getRegion(); this._state.clientRegion = await this.getClientRegion(); } } exports.Context = Context;