@applitools/driver
Version:
Applitools universal framework wrapper
583 lines (582 loc) • 26.6 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
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;