@applitools/driver
Version:
Applitools universal framework wrapper
760 lines (759 loc) • 37.9 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.makeElement = exports.isElementReference = exports.isElement = exports.isElementInstance = exports.Element = void 0;
const selector_1 = require("./selector");
const utils = __importStar(require("@applitools/utils"));
const snippets = require('@applitools/snippets');
class Element {
constructor(options) {
var _a, _b, _c, _d, _e, _f;
this._state = {};
this._spec = options.spec;
this._context = options.context;
if (utils.types.has(options, 'element')) {
this._target = options.element;
if (this._spec.isElement(this._target)) {
// Some frameworks contains information about the selector inside an element
this._selector = (_a = options.selector) !== null && _a !== void 0 ? _a : (_c = (_b = this._spec).extractSelector) === null || _c === void 0 ? void 0 : _c.call(_b, options.element);
this._index = options.index;
}
}
else if ((0, selector_1.isSelector)(options.selector, this._spec)) {
this._selector = options.selector;
}
else {
throw new TypeError('Element constructor called with argument of unknown type!');
}
if ((0, selector_1.isSimpleCommonSelector)(this._selector) && !utils.types.isString(this._selector)) {
this._commonSelector = this._selector;
}
else if (this._selector && this._spec.toSimpleCommonSelector) {
this._commonSelector = this._spec.toSimpleCommonSelector((_f = (_e = (_d = this._spec).toSelector) === null || _e === void 0 ? void 0 : _e.call(_d, this._selector)) !== null && _f !== void 0 ? _f : this._selector);
}
else if (utils.types.isString(this._selector)) {
this._commonSelector = { selector: this._selector };
}
else {
this._commonSelector = null;
}
}
get logger() {
return this._context.logger;
}
get target() {
return this._target;
}
get selector() {
return this._selector;
}
get commonSelector() {
return this._commonSelector;
}
get index() {
return this._index;
}
get context() {
return this._context;
}
get driver() {
return this.context.driver;
}
get isRef() {
var _a;
return ((_a = this.context) === null || _a === void 0 ? void 0 : _a.isRef) || !this.target;
}
async equals(element) {
var _a, _b, _c;
if (this.isRef)
return false;
element = element instanceof Element ? element.target : element;
const environment = await this.driver.getEnvironment();
if (environment.isWeb) {
return this._spec
.executeScript(this.context.target, snippets.isEqualElements, [this.target, element])
.catch(() => false);
}
else {
return (_c = (_b = (_a = this._spec).isEqualElements) === null || _b === void 0 ? void 0 : _b.call(_a, this.context.target, this.target, element)) !== null && _c !== void 0 ? _c : false;
}
}
async contains(innerElement) {
const contains = await this.withRefresh(async () => {
var _a, _b;
var _c;
innerElement = innerElement instanceof Element ? innerElement.target : innerElement;
const environment = await this.driver.getEnvironment();
if (environment.isWeb) {
this.logger.log('Checking if web element with selector', this.selector, 'contains element', innerElement);
return false; // TODO implement a snipped for web
}
else {
if ((_a = this._state.containedElements) === null || _a === void 0 ? void 0 : _a.has(innerElement))
return this._state.containedElements.get(innerElement);
this.logger.log('Checking if native element with selector', this.selector, 'contains element', innerElement);
// appium doesn't have a way to check if an element is contained in another element, so juristic applied
if (await this.equals(innerElement))
return false;
// if the inner element region is contained in this element region, then it then could be assumed that the inner element is contained in this element
const contentRegion = await this.getContentRegion();
const innerRegion = await this._spec.getElementRegion(this.driver.target, innerElement);
const contains = utils.geometry.contains(contentRegion, innerRegion);
(_b = (_c = this._state).containedElements) !== null && _b !== void 0 ? _b : (_c.containedElements = new Map());
this._state.containedElements.set(innerElement, contains);
return contains;
}
});
this.logger.log('Element with selector', this.selector, contains ? 'contains' : `doesn't contain`, innerElement);
return contains;
}
async init(context) {
this._context = context;
if (this._target)
return this;
if (this._selector) {
const element = await this._context.element(this._selector);
if (!element)
throw new Error(`Cannot find element with selector ${JSON.stringify(this._selector)}`);
this._target = element.target;
}
return this;
}
async getRegion() {
const region = await this.withRefresh(async () => {
const environment = await this.driver.getEnvironment();
if (environment.isWeb) {
this.logger.log('Extracting region of web element with selector', this.selector);
return this.context.execute(snippets.getElementRect, [this, false]);
}
else {
this.logger.log('Extracting region of native element with selector', this.selector);
const region = await this._spec.getElementRegion(this.driver.target, this.target);
this.logger.log('Extracted native region', region);
const normalizedRegion = await this.driver.normalizeRegion(region);
// if element is a child of scrolling element, then region location should be adjusted
const scrollingElement = await this.context.getScrollingElement();
return (await (scrollingElement === null || scrollingElement === void 0 ? void 0 : scrollingElement.contains(this)))
? utils.geometry.offset(normalizedRegion, await scrollingElement.getScrollOffset())
: normalizedRegion;
}
});
this.logger.log('Extracted region', region);
return region;
}
async getClientRegion() {
const region = await this.withRefresh(async () => {
const environment = await this.driver.getEnvironment();
if (environment.isWeb) {
this.logger.log('Extracting region of web element with selector', this.selector);
return this.context.execute(snippets.getElementRect, [this, true]);
}
else {
return this.getRegion();
}
});
this.logger.log('Extracted client region', region);
return region;
}
async getContentRegion(options = {}) {
var _a, _b;
const environment = await this.driver.getEnvironment();
if (!environment.isNative)
return null;
this.logger.log('Extracting content region of native element with selector', this.selector);
const helper = await this.driver.getHelper();
let contentRegion = await (helper === null || helper === void 0 ? void 0 : helper.getContentRegion(this, options));
this.logger.log('Extracted content region using helper library', contentRegion);
if (!contentRegion || !environment.isAndroid) {
let attrContentRegion = null;
try {
const { left, top, width, height, scrollableOffset } = JSON.parse(await this.getAttribute('contentSize'));
if ([left, top, width, height, scrollableOffset].some(value => !utils.types.isNumber(value)))
throw new Error('Invalid contentSize');
attrContentRegion = {
x: left,
y: top,
width: width,
height: environment.isIOS ? Math.max(height, scrollableOffset) : height + scrollableOffset,
};
}
catch (err) {
this.logger.warn(`Unable to get the attribute 'contentSize' due to the following error: '${err.message}'`);
}
this.logger.log('Extracted content region using attribute', attrContentRegion);
// ios workaround
if (!attrContentRegion && environment.isIOS) {
try {
const type = await this.getAttribute('type');
if (type === 'XCUIElementTypeScrollView') {
const [child] = await this.driver.elements({
type: 'xpath',
selector: '//XCUIElementTypeScrollView[1]/*', // We cannot be sure that our element is the first one
});
if (child) {
const region = await this._spec.getElementRegion(this.driver.target, this.target);
const childRegion = await this._spec.getElementRegion(this.driver.target, child.target);
attrContentRegion = {
...region,
height: childRegion.y + childRegion.height - region.y,
};
}
}
}
catch (err) {
this.logger.warn(`Unable to calculate content region using iOS workaround due to the following error: '${err.message}'`);
}
this.logger.log('Extracted content region using iOS workaround', attrContentRegion);
}
if (attrContentRegion) {
contentRegion = {
x: attrContentRegion.x,
y: attrContentRegion.y,
width: Math.max((_a = contentRegion === null || contentRegion === void 0 ? void 0 : contentRegion.width) !== null && _a !== void 0 ? _a : 0, attrContentRegion.width),
height: Math.max((_b = contentRegion === null || contentRegion === void 0 ? void 0 : contentRegion.height) !== null && _b !== void 0 ? _b : 0, attrContentRegion.height),
};
}
}
return contentRegion !== null && contentRegion !== void 0 ? contentRegion : (await this._spec.getElementRegion(this.driver.target, this.target));
}
async getContentSize(options = {}) {
if (this._state.contentSize)
return this._state.contentSize;
const size = await this.withRefresh(async () => {
const environment = await this.driver.getEnvironment();
if (environment.isWeb) {
this.logger.log('Extracting content size of web element with selector', this.selector);
return this.context.execute(snippets.getElementContentSize, [this]);
}
else {
this.logger.log('Extracting content size of native element with selector', this.selector);
try {
const contentRegion = await this.getContentRegion(options);
this._state.contentSize = utils.geometry.size(contentRegion);
const viewport = await this.driver.getViewport();
if (environment.isAndroid) {
this._state.contentSize = utils.geometry.scale(this._state.contentSize, 1 / viewport.pixelRatio);
}
if (contentRegion.y < viewport.statusBarSize) {
this._state.contentSize.height -= viewport.statusBarSize - contentRegion.y;
}
return this._state.contentSize;
}
catch (err) {
this.logger.warn('Failed to extract content size, extracting client size instead');
this.logger.error(err);
return utils.geometry.size(await this.getClientRegion());
}
}
});
this.logger.log('Extracted content size', size);
return size;
}
async isPager() {
this.logger.log('Check if element with selector', this.selector, 'is scrollable by pages');
const isPager = await this.withRefresh(async () => {
const environment = await this.driver.getEnvironment();
if (environment.isAndroid) {
const className = await this.getAttribute('className');
return ['androidx.viewpager.widget.ViewPager'].includes(className);
}
else {
return false;
}
});
this.logger.log('Element scrollable by pages', isPager);
return isPager;
}
async isScrollable() {
this.logger.log('Check if element with selector', this.selector, 'is scrollable');
const isScrollable = await this.withRefresh(async () => {
const environment = await this.driver.getEnvironment();
if (environment.isWeb) {
return this.context.execute(snippets.isElementScrollable, [this]);
}
else if (environment.isAndroid) {
const data = JSON.parse(await this.getAttribute('scrollable'));
return Boolean(data) || false;
}
else if (environment.isIOS) {
const type = await this.getAttribute('type');
return ['XCUIElementTypeScrollView', 'XCUIElementTypeTable', 'XCUIElementTypeCollectionView'].includes(type);
}
});
this.logger.log('Element is scrollable', isScrollable);
return isScrollable;
}
async isRoot() {
// TODO replace with snippet
return this.withRefresh(async () => {
const environment = await this.driver.getEnvironment();
if (environment.isWeb) {
const rootElement = await this.context.element({ type: 'css', selector: 'html' });
return this.equals(rootElement);
}
else {
return false;
}
});
}
async getShadowRoot() {
const environment = await this.driver.getEnvironment();
if (!environment.isWeb)
return null;
return this._spec.executeScript(this.context.target, snippets.getShadowRoot, [this.target]);
}
async getTouchPadding() {
var _a;
var _b;
if (this._state.touchPadding == null) {
const environment = await this.driver.getEnvironment();
if (environment.isWeb)
this._state.touchPadding = 0;
else if (environment.isIOS)
this._state.touchPadding = 10;
else if (environment.isAndroid) {
const helper = await this.driver.getHelper();
if ((helper === null || helper === void 0 ? void 0 : helper.name) === 'android') {
this._state.touchPadding = await helper.getTouchPadding();
this.logger.log('Touch padding extracted using helper library', this._state.touchPadding);
}
if (!this._state.touchPadding) {
this._state.touchPadding = await this.getAttribute('contentSize')
.then(data => JSON.parse(data).touchPadding)
.catch(err => {
this.logger.warn(`Unable to get the attribute 'contentSize' when looking up 'touchPadding' due to the following error: '${err.message}'`);
});
this.logger.log('Touch padding extracted using attribute', this._state.touchPadding);
}
(_a = (_b = this._state).touchPadding) !== null && _a !== void 0 ? _a : (_b.touchPadding = 20);
this.logger.log('Touch padding set:', this._state.touchPadding);
}
}
return this._state.touchPadding;
}
async getText() {
const text = await this.withRefresh(async () => {
const environment = await this.driver.getEnvironment();
if (environment.isWeb) {
return '';
}
else {
this.logger.log('Extracting text of native element with selector', this.selector);
return this._spec.getElementText(this.context.target, this.target);
}
});
this.logger.log('Extracted element text', text);
return text;
}
async getAttribute(name) {
// we assumes that attributes are not changed during the session
if (utils.types.has(this._state.attributes, name)) {
if (this._state.attributes[name] instanceof Error)
throw this._state.attributes[name];
return this._state.attributes[name];
}
const value = await this.withRefresh(async () => {
var _a;
var _b;
const environment = await this.driver.getEnvironment();
if (environment.isWeb) {
const properties = await this.context.execute(snippets.getElementProperties, [this, [name]]);
return properties[name];
}
else {
this.logger.log(`Extracting "${name}" attribute of native element with selector`, this.selector);
(_a = (_b = this._state).attributes) !== null && _a !== void 0 ? _a : (_b.attributes = {});
try {
this._state.attributes[name] = await this._spec.getElementAttribute(this.driver.target, this.target, name);
return this._state.attributes[name];
}
catch (err) {
this._state.attributes[name] = err;
throw err;
}
finally {
if (environment.isAndroid && name === 'contentSize') {
// android has a bug when after extracting 'contentSize' attribute the element is being scrolled by undetermined number of pixels
this.logger.log('Stabilizing android scroll offset');
const originalScrollOffset = await this.getScrollOffset();
await this.scrollTo({ x: 0, y: 0 }, { force: true });
await this.scrollTo(originalScrollOffset);
}
}
}
});
this.logger.log(`Extracted element "${name}" attribute:`, value);
return value;
}
async setAttribute(name, value) {
const environment = await this.driver.getEnvironment();
if (environment.isWeb) {
await this.context.execute(snippets.setElementAttributes, [this, { [name]: value }]);
}
}
async scrollTo(offset, options) {
return this.withRefresh(async () => {
this.logger.log(`Scrolling to offset (${offset.x}, ${offset.y}) element with selector`, this.selector);
offset = utils.geometry.round({ x: Math.max(offset.x, 0), y: Math.max(offset.y, 0) });
const environment = await this.driver.getEnvironment();
if (environment.isWeb) {
let actualOffset = await this.context.execute(snippets.scrollTo, [this, offset]);
// iOS has an issue when scroll offset is read immediately after it is been set it will always return the exact value that was set
if (environment.isIOS)
actualOffset = await this.getScrollOffset();
return actualOffset;
}
else {
const currentScrollOffset = await this.getScrollOffset();
if (!(options === null || options === void 0 ? void 0 : options.force) && utils.geometry.equals(offset, currentScrollOffset))
return currentScrollOffset;
if (utils.geometry.equals(offset, { x: 0, y: 0 }) && environment.isAndroid) {
const helper = await this.driver.getHelper();
if ((helper === null || helper === void 0 ? void 0 : helper.name) === 'android') {
await helper.scrollToTop(this);
this._state.scrollOffset = offset;
return this._state.scrollOffset;
}
}
const contentSize = await this.getContentSize();
const scrollableRegion = await this.getClientRegion();
const maxOffset = {
x: Math.round(scrollableRegion.width * (contentSize.width / scrollableRegion.width - 1)),
y: Math.round(scrollableRegion.height * (contentSize.height / scrollableRegion.height - 1)),
};
const requiredOffset = { x: Math.min(offset.x, maxOffset.x), y: Math.min(offset.y, maxOffset.y) };
let effectiveRegion = scrollableRegion;
let remainingOffset = utils.geometry.equals(requiredOffset, { x: 0, y: 0 })
? { x: -maxOffset.x, y: -maxOffset.y } // if it has to be scrolled to the very beginning, then scroll maximum amount of pixels
: utils.geometry.offsetNegative(requiredOffset, currentScrollOffset);
if (environment.isAndroid) {
const viewport = await this.driver.getViewport();
remainingOffset = utils.geometry.round(utils.geometry.scale(remainingOffset, viewport.pixelRatio));
effectiveRegion = utils.geometry.round(utils.geometry.scale(effectiveRegion, viewport.pixelRatio));
}
const isPager = await this.isPager();
const touchPadding = await this.getTouchPadding();
const actions = [];
// horizontal scrolling
const xPadding = touchPadding + 3;
const yTrack = Math.floor(effectiveRegion.y + effectiveRegion.height / 2); // center
const xLeft = effectiveRegion.y + xPadding;
const xDirection = remainingOffset.x > 0 ? 'right' : 'left';
const xGap = xDirection === 'right' ? -touchPadding : touchPadding;
const xCompensation = xDirection === 'right' ? -1 : 1;
let xRemaining = Math.abs(remainingOffset.x);
if (isPager) {
const xPages = Math.floor(xRemaining / effectiveRegion.width);
xRemaining = (effectiveRegion.width - xPadding * 2) * xPages;
}
while (xRemaining > 0) {
const xRight = effectiveRegion.x + Math.min(xRemaining + xPadding, effectiveRegion.width - xPadding);
const [xStart, xEnd] = xDirection === 'right' ? [xRight, xLeft] : [xLeft, xRight];
if (isPager) {
actions.push([
{ action: 'press', y: yTrack, x: xStart },
// scroll through the page
{ action: 'wait', ms: 170 },
{ action: 'moveTo', y: yTrack, x: xEnd },
{ action: 'release' },
]);
}
else if (environment.isAndroid) {
actions.push([
// move through scrolling gap (actual scrolling will be triggered only after that)
{ action: 'press', y: yTrack, x: xStart - xGap },
{ action: 'wait', ms: 100 },
{ action: 'moveTo', y: yTrack, x: xStart + xCompensation },
{ action: 'wait', ms: 100 },
{ action: 'moveTo', y: yTrack, x: xStart },
// perform actual scrolling
{ action: 'wait', ms: 100 },
{ action: 'moveTo', y: yTrack, x: xEnd },
{ action: 'release' },
]);
}
else if (environment.isIOS) {
actions.push([
// move through scrolling gap (actual scrolling will be triggered only after that)
{ action: 'press', y: yTrack, x: xStart - xGap },
{ action: 'wait', ms: 100 },
{ action: 'moveTo', y: yTrack, x: xStart },
// perform actual scrolling
{ action: 'wait', ms: 100 },
{ action: 'moveTo', y: yTrack, x: xEnd },
// prevent inertial scrolling after release
{ action: 'wait', ms: 100 },
{ action: 'moveTo', y: yTrack + 1, x: xEnd },
{ action: 'release' },
]);
}
xRemaining -= xRight - xLeft;
}
// vertical scrolling
const yPadding = Math.max(Math.floor(effectiveRegion.height * 0.08), touchPadding + 3);
const xTrack = Math.floor(effectiveRegion.x + 5); // a little bit off left border
const yBottom = effectiveRegion.y + effectiveRegion.height - yPadding;
const yDirection = remainingOffset.y > 0 ? 'down' : 'up';
const yGap = yDirection === 'down' ? -touchPadding : touchPadding;
const yCompensation = yDirection === 'down' ? -1 : 1;
let yRemaining = Math.abs(remainingOffset.y);
if (isPager) {
const yPages = Math.floor(yRemaining / effectiveRegion.height);
yRemaining = (effectiveRegion.height - yPadding * 2) * yPages;
}
while (yRemaining > 0) {
const yTop = Math.max(yBottom - yRemaining, effectiveRegion.y + yPadding);
const [yStart, yEnd] = yDirection === 'down' ? [yBottom, yTop] : [yTop, yBottom];
if (isPager) {
actions.push([
{ action: 'press', x: xTrack, y: yStart },
// scroll through the page
{ action: 'wait', ms: 170 },
{ action: 'moveTo', x: xTrack, y: yEnd },
{ action: 'release' },
]);
}
else if (environment.isAndroid) {
actions.push([
// move through scrolling gap (actual scrolling will be triggered only after that)
{ action: 'press', x: xTrack, y: yStart - yGap },
{ action: 'wait', ms: 100 },
{ action: 'moveTo', x: xTrack, y: yStart + yCompensation },
{ action: 'wait', ms: 100 },
{ action: 'moveTo', x: xTrack, y: yStart },
// perform actual scrolling
{ action: 'wait', ms: 100 },
{ action: 'moveTo', x: xTrack, y: yEnd },
{ action: 'release' },
]);
}
else if (environment.isIOS) {
actions.push([
// move through scrolling gap (actual scrolling will be triggered only after that)
{ action: 'press', x: xTrack, y: yStart - yGap },
{ action: 'wait', ms: 100 },
{ action: 'moveTo', x: xTrack, y: yStart },
// perform actual scrolling
{ action: 'wait', ms: 100 },
{ action: 'moveTo', x: xTrack, y: yEnd },
// prevent inertial scrolling after release
{ action: 'wait', ms: 100 },
{ action: 'moveTo', x: xTrack + 1, y: yEnd },
{ action: 'release' },
]);
}
yRemaining -= yBottom - yTop;
}
// ios actions should be executed one-by-one sequentially, otherwise the result isn't stable
// pages should be scrolled one-by-one as well
if (isPager || environment.isIOS) {
for (const action of actions) {
await this._spec.performAction(this.driver.target, action);
}
}
else if (actions.length > 0) {
await this._spec.performAction(this.driver.target, actions.flat());
}
const actualScrollableRegion = await this.getClientRegion();
this._state.scrollOffset = utils.geometry.offsetNegative(requiredOffset, {
x: scrollableRegion.x - actualScrollableRegion.x,
y: scrollableRegion.y - actualScrollableRegion.y,
});
return this._state.scrollOffset;
}
});
}
async translateTo(offset) {
offset = { x: Math.round(offset.x), y: Math.round(offset.y) };
const environment = await this.driver.getEnvironment();
if (environment.isWeb) {
return this.withRefresh(async () => this.context.execute(snippets.translateTo, [this, offset]));
}
else {
throw new Error('Cannot apply css translate scrolling on non-web element');
}
}
async getScrollOffset() {
var _a;
const environment = await this.driver.getEnvironment();
if (environment.isWeb) {
return this.withRefresh(() => this.context.execute(snippets.getElementScrollOffset, [this]));
}
else {
return (_a = this._state.scrollOffset) !== null && _a !== void 0 ? _a : { x: 0, y: 0 };
}
}
async getTranslateOffset() {
const environment = await this.driver.getEnvironment();
if (environment.isWeb) {
return this.withRefresh(() => this.context.execute(snippets.getElementTranslateOffset, [this]));
}
else {
throw new Error('Cannot apply css translate scrolling on non-web element');
}
}
async getInnerOffset() {
const environment = await this.driver.getEnvironment();
if (environment.isWeb) {
return this.withRefresh(() => this.context.execute(snippets.getElementInnerOffset, [this]));
}
else {
return this.getScrollOffset();
}
}
async hover() {
var _a, _b;
this.logger.log(`Hovering on the element with selector`, this.selector);
await ((_b = (_a = this._spec).hover) === null || _b === void 0 ? void 0 : _b.call(_a, this.context.target, this.target));
}
async click() {
var _a, _b;
this.logger.log(`Clicking on the element with selector`, this.selector);
await ((_b = (_a = this._spec).click) === null || _b === void 0 ? void 0 : _b.call(_a, this.context.target, this.target));
}
async type(value) {
var _a, _b;
this.logger.log(`Typing text "${value}" in the element with selector`, this.selector);
await ((_b = (_a = this._spec).setElementText) === null || _b === void 0 ? void 0 : _b.call(_a, this.context.target, this.target, value));
}
async preserveState() {
const scrollOffset = await this.getScrollOffset();
const environment = await this.driver.getEnvironment();
const transforms = environment.isWeb
? await this.context.execute(snippets.getElementStyleProperties, [this, ['transform', '-webkit-transform']])
: null;
if (!utils.types.has(this._state, ['scrollOffset', 'transforms'])) {
this._state.scrollOffset = scrollOffset;
this._state.transforms = transforms;
}
return { scrollOffset, transforms };
}
async restoreState(state = this._state) {
if (state.scrollOffset)
await this.scrollTo(state.scrollOffset);
if (state.transforms)
await this.context.execute(snippets.setElementStyleProperties, [this, state.transforms]);
if (state === this._state) {
this._state.scrollOffset = undefined;
this._state.transforms = undefined;
}
}
async hideScrollbars() {
const environment = await this.driver.getEnvironment();
if (environment.isNative)
return;
if (this._originalOverflow)
return;
return this.withRefresh(async () => {
const { overflow } = await this.context.execute(snippets.setElementStyleProperties, [this, { overflow: 'hidden' }]);
this._originalOverflow = overflow;
});
}
async restoreScrollbars() {
const environment = await this.driver.getEnvironment();
if (environment.isNative)
return;
if (!this._originalOverflow)
return;
return this.withRefresh(async () => {
await this.context.execute(snippets.setElementStyleProperties, [this, { overflow: this._originalOverflow }]);
this._originalOverflow = null;
});
}
async refresh(freshTarget) {
if (this._spec.isElement(freshTarget)) {
this._target = freshTarget;
return true;
}
if (!this._selector)
return false;
const element = this._index > 0
? await this.context.elements(this._selector).then(elements => elements[this._index])
: await this.context.element(this._selector);
if (element) {
this._target = element.target;
}
return Boolean(element);
}
async withRefresh(operation) {
if (!this._spec.isStaleElementError)
return operation();
try {
const result = await operation();
// Some frameworks could handle stale element reference error by itself or doesn't throw an error
if (this._spec.isStaleElementError(result, this.selector)) {
const refreshed = await this.refresh();
if (!refreshed) {
throw result;
}
return operation();
}
return result;
}
catch (err) {
if (this._spec.isStaleElementError(err)) {
const refreshed = await this.refresh();
if (refreshed)
return operation();
}
throw err;
}
}
toJSON() {
return this.target;
}
}
exports.Element = Element;
function isElementInstance(element) {
return element instanceof Element;
}
exports.isElementInstance = isElementInstance;
function isElement(element, spec) {
var _a;
return isElementInstance(element) || !!(spec === null || spec === void 0 ? void 0 : spec.isElement(element)) || !!((_a = spec === null || spec === void 0 ? void 0 : spec.isSecondaryElement) === null || _a === void 0 ? void 0 : _a.call(spec, element));
}
exports.isElement = isElement;
function isElementReference(reference, spec) {
return isElement(reference, spec) || (0, selector_1.isSelector)(reference, spec);
}
exports.isElementReference = isElementReference;
async function makeElement(options) {
var _a, _b, _c;
let element;
if (utils.types.has(options, 'element')) {
if (options.element instanceof Element) {
element = options.element;
}
else {
options.element = (_c = (await ((_b = (_a = options.spec) === null || _a === void 0 ? void 0 : _a.toElement) === null || _b === void 0 ? void 0 : _b.call(_a, options.element)))) !== null && _c !== void 0 ? _c : options.element;
element = new Element(options);
}
}
else {
element = new Element(options);
}
return element;
}
exports.makeElement = makeElement;