@applitools/driver 1.4.17-beta.2 → 1.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/element.js CHANGED
@@ -1,483 +1,483 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
5
- }) : (function(o, m, k, k2) {
6
- if (k2 === undefined) k2 = k;
7
- o[k2] = m[k];
8
- }));
9
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
10
- Object.defineProperty(o, "default", { enumerable: true, value: v });
11
- }) : function(o, v) {
12
- o["default"] = v;
13
- });
14
- var __importStar = (this && this.__importStar) || function (mod) {
15
- if (mod && mod.__esModule) return mod;
16
- var result = {};
17
- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
18
- __setModuleDefault(result, mod);
19
- return result;
20
- };
21
- var __rest = (this && this.__rest) || function (s, e) {
22
- var t = {};
23
- for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
24
- t[p] = s[p];
25
- if (s != null && typeof Object.getOwnPropertySymbols === "function")
26
- for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
27
- if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
28
- t[p[i]] = s[p[i]];
29
- }
30
- return t;
31
- };
32
- Object.defineProperty(exports, "__esModule", { value: true });
33
- exports.Element = void 0;
34
- const utils = __importStar(require("@applitools/utils"));
35
- const specUtils = __importStar(require("./spec-utils"));
36
- const snippets = require('@applitools/snippets');
37
- class Element {
38
- constructor(options) {
39
- var _a, _b, _c, _d, _e, _f;
40
- this._state = {};
41
- if (options.element instanceof Element)
42
- return options.element;
43
- this._spec = options.spec;
44
- if (options.context)
45
- this._context = options.context;
46
- if (options.logger)
47
- this._logger = options.logger;
48
- if (this._spec.isElement(options.element)) {
49
- this._target = (_c = (_b = (_a = this._spec).transformElement) === null || _b === void 0 ? void 0 : _b.call(_a, options.element)) !== null && _c !== void 0 ? _c : options.element;
50
- // Some frameworks contains information about the selector inside an element
51
- this._selector = (_d = options.selector) !== null && _d !== void 0 ? _d : (_f = (_e = this._spec).extractSelector) === null || _f === void 0 ? void 0 : _f.call(_e, options.element);
52
- this._index = options.index;
53
- }
54
- else if (specUtils.isSelector(this._spec, options.selector)) {
55
- this._selector = options.selector;
56
- }
57
- else {
58
- throw new TypeError('Element constructor called with argument of unknown type!');
59
- }
60
- }
61
- get target() {
62
- return this._target;
63
- }
64
- get selector() {
65
- return this._selector;
66
- }
67
- get context() {
68
- return this._context;
69
- }
70
- get driver() {
71
- return this.context.driver;
72
- }
73
- get isRef() {
74
- return this.context.isRef || !this.target;
75
- }
76
- async equals(element) {
77
- if (this.isRef)
78
- return false;
79
- element = element instanceof Element ? element.target : element;
80
- if (this.driver.isWeb) {
81
- return this._spec
82
- .executeScript(this.context.target, snippets.isEqualElements, [this.target, element])
83
- .catch(() => false);
84
- }
85
- else {
86
- return this._spec.isEqualElements(this.context.target, this.target, element);
87
- }
88
- }
89
- async init(context) {
90
- this._context = context;
91
- this._logger = context._logger;
92
- if (this._target)
93
- return this;
94
- if (this._selector) {
95
- const element = await this._context.element(this._selector);
96
- if (!element)
97
- throw new Error(`Cannot find element with selector ${JSON.stringify(this._selector)}`);
98
- this._target = element.target;
99
- return this;
100
- }
101
- }
102
- async getRegion() {
103
- const region = await this.withRefresh(async () => {
104
- if (this.driver.isWeb) {
105
- this._logger.log('Extracting region of web element with selector', this.selector);
106
- return this.context.execute(snippets.getElementRect, [this, false]);
107
- }
108
- else {
109
- this._logger.log('Extracting region of native element with selector', this.selector);
110
- const region = await this._spec.getElementRegion(this.driver.target, this.target);
111
- this._logger.log('Extracted native region', region);
112
- return this.driver.normalizeRegion(region);
113
- }
114
- });
115
- this._logger.log('Extracted region', region);
116
- return region;
117
- }
118
- async getClientRegion() {
119
- const region = await this.withRefresh(async () => {
120
- if (this.driver.isWeb) {
121
- this._logger.log('Extracting region of web element with selector', this.selector);
122
- return this.context.execute(snippets.getElementRect, [this, true]);
123
- }
124
- else {
125
- return this.getRegion();
126
- }
127
- });
128
- this._logger.log('Extracted client region', region);
129
- return region;
130
- }
131
- async getContentSize() {
132
- if (this._state.contentSize)
133
- return this._state.contentSize;
134
- const size = await this.withRefresh(async () => {
135
- var _a, _b, _c;
136
- if (this.driver.isWeb) {
137
- this._logger.log('Extracting content size of web element with selector', this.selector);
138
- return this.context.execute(snippets.getElementContentSize, [this]);
139
- }
140
- else {
141
- this._logger.log('Extracting content size of native element with selector', this.selector);
142
- try {
143
- const _d = await this.getAttribute('contentSize')
144
- .then(data => {
145
- const contentSize = JSON.parse(data);
146
- return {
147
- touchPadding: contentSize.touchPadding,
148
- x: contentSize.left,
149
- y: contentSize.top,
150
- width: contentSize.width,
151
- height: (this.driver.isAndroid ? contentSize.height : 0) + contentSize.scrollableOffset,
152
- };
153
- })
154
- .catch(() => {
155
- return this._spec.getElementRegion(this.driver.target, this.target);
156
- }), { touchPadding } = _d, contentRegion = __rest(_d, ["touchPadding"]);
157
- this._logger.log('Extracted native content size attribute', contentRegion);
158
- const contentSize = await ((_a = this.driver.helper) === null || _a === void 0 ? void 0 : _a.getContentSize(this));
159
- this._logger.log('Extracted native content size with helper library', contentSize);
160
- this._state.contentSize = {
161
- width: Math.max((_b = contentSize === null || contentSize === void 0 ? void 0 : contentSize.width) !== null && _b !== void 0 ? _b : 0, contentRegion.width),
162
- height: Math.max((_c = contentSize === null || contentSize === void 0 ? void 0 : contentSize.height) !== null && _c !== void 0 ? _c : 0, contentRegion.height),
163
- };
164
- this._touchPadding = touchPadding !== null && touchPadding !== void 0 ? touchPadding : this._touchPadding;
165
- if (this.driver.isAndroid) {
166
- this._state.contentSize = utils.geometry.scale(this._state.contentSize, 1 / this.driver.pixelRatio);
167
- }
168
- if (contentRegion.y < this.driver.statusBarHeight) {
169
- this._state.contentSize.height -= this.driver.statusBarHeight - contentRegion.y;
170
- }
171
- // android has a bug when after extracting 'contentSize' attribute the element is being scrolled by undetermined number of pixels
172
- if (this.driver.isAndroid) {
173
- this._logger.log('Stabilizing android scroll offset');
174
- const originalScrollOffset = await this.getScrollOffset();
175
- this._state.scrollOffset = { x: -1, y: -1 };
176
- await this.scrollTo({ x: 0, y: 0 });
177
- await this.scrollTo(originalScrollOffset);
178
- }
179
- return this._state.contentSize;
180
- }
181
- catch (err) {
182
- this._logger.warn('Failed to extract content size, extracting client size instead');
183
- this._logger.error(err);
184
- return utils.geometry.size(await this.getClientRegion());
185
- }
186
- }
187
- });
188
- this._logger.log('Extracted content size', size);
189
- return size;
190
- }
191
- async isScrollable() {
192
- this._logger.log('Check is element with selector', this.selector, 'is scrollable');
193
- const isScrollable = await this.withRefresh(async () => {
194
- if (this.driver.isWeb) {
195
- return this.context.execute(snippets.isElementScrollable, [this]);
196
- }
197
- else if (this.driver.isAndroid) {
198
- const data = JSON.parse(await this.getAttribute('scrollable'));
199
- return Boolean(data) || false;
200
- }
201
- else if (this.driver.isIOS) {
202
- const type = await this.getAttribute('type');
203
- return ['XCUIElementTypeScrollView', 'XCUIElementTypeTable', 'XCUIElementTypeCollectionView'].includes(type);
204
- }
205
- });
206
- this._logger.log('Element is scrollable', isScrollable);
207
- return isScrollable;
208
- }
209
- async isRoot() {
210
- // TODO replace with snippet
211
- return this.withRefresh(async () => {
212
- if (this.driver.isWeb) {
213
- const rootElement = await this.context.element({ type: 'css', selector: 'html' });
214
- return this.equals(rootElement);
215
- }
216
- else {
217
- return false;
218
- }
219
- });
220
- }
221
- async getTouchPadding() {
222
- var _a;
223
- if (this._touchPadding == null) {
224
- if (this.driver.isWeb)
225
- this._touchPadding = 0;
226
- else if (this.driver.isIOS)
227
- this._touchPadding = 10;
228
- else if (this.driver.isAndroid) {
229
- const data = await this.getAttribute('contentSize')
230
- .then(JSON.parse)
231
- .catch(() => null);
232
- this._touchPadding = (_a = data === null || data === void 0 ? void 0 : data.touchPadding) !== null && _a !== void 0 ? _a : 24;
233
- }
234
- }
235
- return this._touchPadding;
236
- }
237
- async getText() {
238
- const text = await this.withRefresh(async () => {
239
- if (this.driver.isWeb) {
240
- return '';
241
- }
242
- else {
243
- this._logger.log('Extracting text of native element with selector', this.selector);
244
- return this._spec.getElementText(this.driver.target, this.target);
245
- }
246
- });
247
- this._logger.log('Extracted element text', text);
248
- return text;
249
- }
250
- async getAttribute(name) {
251
- if (this.driver.isWeb) {
252
- const properties = await this.context.execute(snippets.getElementProperties, [this, [name]]);
253
- return properties[name];
254
- }
255
- else {
256
- return this._spec.getElementAttribute(this.driver.target, this.target, name);
257
- }
258
- }
259
- async setAttribute(name, value) {
260
- if (this.driver.isWeb) {
261
- await this.context.execute(snippets.setElementAttributes, [this, { [name]: value }]);
262
- }
263
- }
264
- async scrollTo(offset) {
265
- return this.withRefresh(async () => {
266
- offset = utils.geometry.round(offset);
267
- if (this.driver.isWeb) {
268
- let actualOffset = await this.context.execute(snippets.scrollTo, [this, offset]);
269
- // 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
270
- if (this.driver.isIOS)
271
- actualOffset = await this.getScrollOffset();
272
- return actualOffset;
273
- }
274
- else {
275
- const currentScrollOffset = await this.getScrollOffset();
276
- if (utils.geometry.equals(offset, currentScrollOffset))
277
- return currentScrollOffset;
278
- const contentSize = await this.getContentSize();
279
- const scrollableRegion = await this.getClientRegion();
280
- const effectiveRegion = this.driver.isAndroid
281
- ? utils.geometry.scale(scrollableRegion, this.driver.pixelRatio)
282
- : scrollableRegion;
283
- const maxOffset = {
284
- x: Math.round(scrollableRegion.width * (contentSize.width / scrollableRegion.width - 1)),
285
- y: Math.round(scrollableRegion.height * (contentSize.height / scrollableRegion.height - 1)),
286
- };
287
- const requiredOffset = { x: Math.min(offset.x, maxOffset.x), y: Math.min(offset.y, maxOffset.y) };
288
- let remainingOffset = offset.x === 0 && offset.y === 0
289
- ? { x: -maxOffset.x, y: -maxOffset.y } // if it has to be scrolled to the very beginning, then scroll maximum amount of pixels
290
- : utils.geometry.offsetNegative(requiredOffset, currentScrollOffset);
291
- if (this.driver.isAndroid) {
292
- remainingOffset = utils.geometry.scale(remainingOffset, this.driver.pixelRatio);
293
- }
294
- const actions = [];
295
- const touchPadding = await this.getTouchPadding();
296
- const xPadding = Math.max(Math.floor(effectiveRegion.width * 0.1), touchPadding);
297
- const yTrack = Math.floor(effectiveRegion.y + effectiveRegion.height / 2); // center
298
- const xLeft = effectiveRegion.y + xPadding;
299
- const xDirection = remainingOffset.y > 0 ? 'right' : 'left';
300
- const xGap = xDirection === 'right' ? -touchPadding : touchPadding;
301
- let xRemaining = Math.abs(remainingOffset.x);
302
- while (xRemaining > 0) {
303
- const xRight = effectiveRegion.x + Math.min(xRemaining + xPadding, effectiveRegion.width - xPadding);
304
- const [xStart, xEnd] = xDirection === 'right' ? [xRight, xLeft] : [xLeft, xRight];
305
- actions.push([
306
- { action: 'press', y: yTrack, x: xStart },
307
- { action: 'wait', ms: 100 },
308
- { action: 'moveTo', y: yTrack, x: xStart + xGap },
309
- { action: 'wait', ms: 100 },
310
- { action: 'moveTo', y: yTrack, x: xEnd + xGap },
311
- { action: 'wait', ms: 100 },
312
- { action: 'moveTo', y: yTrack + 1, x: xEnd + xGap },
313
- { action: 'release' },
314
- ]);
315
- xRemaining -= xRight - xLeft;
316
- }
317
- const yPadding = Math.max(Math.floor(effectiveRegion.height * 0.1), touchPadding);
318
- const xTrack = Math.floor(effectiveRegion.x + 5); // a little bit off left border
319
- const yBottom = effectiveRegion.y + effectiveRegion.height - yPadding;
320
- const yDirection = remainingOffset.y > 0 ? 'down' : 'up';
321
- const yGap = yDirection === 'down' ? -touchPadding : touchPadding;
322
- let yRemaining = Math.abs(remainingOffset.y);
323
- while (yRemaining > 0) {
324
- const yTop = Math.max(yBottom - yRemaining, effectiveRegion.y + yPadding);
325
- const [yStart, yEnd] = yDirection === 'down' ? [yBottom, yTop] : [yTop, yBottom];
326
- actions.push([
327
- { action: 'press', x: xTrack, y: yStart },
328
- { action: 'wait', ms: 100 },
329
- { action: 'moveTo', x: xTrack, y: yStart + yGap },
330
- { action: 'wait', ms: 100 },
331
- { action: 'moveTo', x: xTrack, y: yEnd + yGap },
332
- { action: 'wait', ms: 100 },
333
- { action: 'moveTo', x: xTrack + 1, y: yEnd + yGap },
334
- { action: 'release' },
335
- ]);
336
- yRemaining -= yBottom - yTop;
337
- }
338
- // ios actions should be executed one-by-one sequentially, otherwise the result isn't stable
339
- if (this.driver.isIOS) {
340
- for (const action of actions) {
341
- await this._spec.performAction(this.driver.target, action);
342
- }
343
- }
344
- else {
345
- await this._spec.performAction(this.driver.target, [].concat(...actions));
346
- }
347
- const actualScrollableRegion = await this.getClientRegion();
348
- this._state.scrollOffset = utils.geometry.offsetNegative(requiredOffset, {
349
- x: scrollableRegion.x - actualScrollableRegion.x,
350
- y: scrollableRegion.y - actualScrollableRegion.y,
351
- });
352
- return this._state.scrollOffset;
353
- }
354
- });
355
- }
356
- async translateTo(offset) {
357
- offset = { x: Math.round(offset.x), y: Math.round(offset.y) };
358
- if (this.driver.isWeb) {
359
- return this.withRefresh(async () => this.context.execute(snippets.translateTo, [this, offset]));
360
- }
361
- else {
362
- throw new Error('Cannot apply css translate scrolling on non-web element');
363
- }
364
- }
365
- async getScrollOffset() {
366
- var _a;
367
- if (this.driver.isWeb) {
368
- return this.withRefresh(() => this.context.execute(snippets.getElementScrollOffset, [this]));
369
- }
370
- else {
371
- return (_a = this._state.scrollOffset) !== null && _a !== void 0 ? _a : { x: 0, y: 0 };
372
- }
373
- }
374
- async getTranslateOffset() {
375
- if (this.driver.isWeb) {
376
- return this.withRefresh(() => this.context.execute(snippets.getElementTranslateOffset, [this]));
377
- }
378
- else {
379
- throw new Error('Cannot apply css translate scrolling on non-web element');
380
- }
381
- }
382
- async getInnerOffset() {
383
- if (this.driver.isWeb) {
384
- return this.withRefresh(() => this.context.execute(snippets.getElementInnerOffset, [this]));
385
- }
386
- else {
387
- return this.getScrollOffset();
388
- }
389
- }
390
- async click() {
391
- await this._spec.click(this.context.target, this.target);
392
- }
393
- async type(value) {
394
- await this._spec.type(this.context.target, this.target, value);
395
- }
396
- async preserveState() {
397
- if (this.driver.isNative)
398
- return;
399
- // TODO create single js snippet
400
- const scrollOffset = await this.getScrollOffset();
401
- const transforms = await this.context.execute(snippets.getElementStyleProperties, [
402
- this,
403
- ['transform', '-webkit-transform'],
404
- ]);
405
- if (!utils.types.has(this._state, ['scrollOffset', 'transforms'])) {
406
- this._state.scrollOffset = scrollOffset;
407
- this._state.transforms = transforms;
408
- }
409
- return { scrollOffset, transforms };
410
- }
411
- async restoreState(state = this._state) {
412
- if (this.driver.isNative)
413
- return;
414
- if (state.scrollOffset)
415
- await this.scrollTo(state.scrollOffset);
416
- if (state.transforms)
417
- await this.context.execute(snippets.setElementStyleProperties, [this, state.transforms]);
418
- if (state === this._state) {
419
- this._state.scrollOffset = null;
420
- this._state.transforms = null;
421
- }
422
- }
423
- async hideScrollbars() {
424
- if (this.driver.isNative)
425
- return;
426
- if (this._originalOverflow)
427
- return;
428
- return this.withRefresh(async () => {
429
- const { overflow } = await this.context.execute(snippets.setElementStyleProperties, [this, { overflow: 'hidden' }]);
430
- this._originalOverflow = overflow;
431
- });
432
- }
433
- async restoreScrollbars() {
434
- if (this.driver.isNative)
435
- return;
436
- if (!this._originalOverflow)
437
- return;
438
- return this.withRefresh(async () => {
439
- await this.context.execute(snippets.setElementStyleProperties, [this, { overflow: this._originalOverflow }]);
440
- this._originalOverflow = null;
441
- });
442
- }
443
- async refresh(freshElement) {
444
- if (this._spec.isElement(freshElement)) {
445
- this._target = freshElement;
446
- return true;
447
- }
448
- if (!this._selector)
449
- return false;
450
- const element = this._index > 0
451
- ? await this.context.elements(this._selector).then(elements => elements[this._index])
452
- : await this.context.element(this._selector);
453
- if (element) {
454
- this._target = element.target;
455
- }
456
- return Boolean(element);
457
- }
458
- async withRefresh(operation) {
459
- if (!this._spec.isStaleElementError)
460
- return operation();
461
- try {
462
- const result = await operation();
463
- // Some frameworks could handle stale element reference error by itself or doesn't throw an error
464
- if (this._spec.isStaleElementError(result, this.selector)) {
465
- await this.refresh();
466
- return operation();
467
- }
468
- return result;
469
- }
470
- catch (err) {
471
- if (this._spec.isStaleElementError(err)) {
472
- const refreshed = await this.refresh();
473
- if (refreshed)
474
- return operation();
475
- }
476
- throw err;
477
- }
478
- }
479
- toJSON() {
480
- return this.target;
481
- }
482
- }
483
- exports.Element = Element;
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
5
+ }) : (function(o, m, k, k2) {
6
+ if (k2 === undefined) k2 = k;
7
+ o[k2] = m[k];
8
+ }));
9
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
10
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
11
+ }) : function(o, v) {
12
+ o["default"] = v;
13
+ });
14
+ var __importStar = (this && this.__importStar) || function (mod) {
15
+ if (mod && mod.__esModule) return mod;
16
+ var result = {};
17
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
18
+ __setModuleDefault(result, mod);
19
+ return result;
20
+ };
21
+ var __rest = (this && this.__rest) || function (s, e) {
22
+ var t = {};
23
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
24
+ t[p] = s[p];
25
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
26
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
27
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
28
+ t[p[i]] = s[p[i]];
29
+ }
30
+ return t;
31
+ };
32
+ Object.defineProperty(exports, "__esModule", { value: true });
33
+ exports.Element = void 0;
34
+ const utils = __importStar(require("@applitools/utils"));
35
+ const specUtils = __importStar(require("./spec-utils"));
36
+ const snippets = require('@applitools/snippets');
37
+ class Element {
38
+ constructor(options) {
39
+ var _a, _b, _c, _d, _e, _f;
40
+ this._state = {};
41
+ if (options.element instanceof Element)
42
+ return options.element;
43
+ this._spec = options.spec;
44
+ if (options.context)
45
+ this._context = options.context;
46
+ if (options.logger)
47
+ this._logger = options.logger;
48
+ if (this._spec.isElement(options.element)) {
49
+ this._target = (_c = (_b = (_a = this._spec).transformElement) === null || _b === void 0 ? void 0 : _b.call(_a, options.element)) !== null && _c !== void 0 ? _c : options.element;
50
+ // Some frameworks contains information about the selector inside an element
51
+ this._selector = (_d = options.selector) !== null && _d !== void 0 ? _d : (_f = (_e = this._spec).extractSelector) === null || _f === void 0 ? void 0 : _f.call(_e, options.element);
52
+ this._index = options.index;
53
+ }
54
+ else if (specUtils.isSelector(this._spec, options.selector)) {
55
+ this._selector = options.selector;
56
+ }
57
+ else {
58
+ throw new TypeError('Element constructor called with argument of unknown type!');
59
+ }
60
+ }
61
+ get target() {
62
+ return this._target;
63
+ }
64
+ get selector() {
65
+ return this._selector;
66
+ }
67
+ get context() {
68
+ return this._context;
69
+ }
70
+ get driver() {
71
+ return this.context.driver;
72
+ }
73
+ get isRef() {
74
+ return this.context.isRef || !this.target;
75
+ }
76
+ async equals(element) {
77
+ if (this.isRef)
78
+ return false;
79
+ element = element instanceof Element ? element.target : element;
80
+ if (this.driver.isWeb) {
81
+ return this._spec
82
+ .executeScript(this.context.target, snippets.isEqualElements, [this.target, element])
83
+ .catch(() => false);
84
+ }
85
+ else {
86
+ return this._spec.isEqualElements(this.context.target, this.target, element);
87
+ }
88
+ }
89
+ async init(context) {
90
+ this._context = context;
91
+ this._logger = context._logger;
92
+ if (this._target)
93
+ return this;
94
+ if (this._selector) {
95
+ const element = await this._context.element(this._selector);
96
+ if (!element)
97
+ throw new Error(`Cannot find element with selector ${JSON.stringify(this._selector)}`);
98
+ this._target = element.target;
99
+ return this;
100
+ }
101
+ }
102
+ async getRegion() {
103
+ const region = await this.withRefresh(async () => {
104
+ if (this.driver.isWeb) {
105
+ this._logger.log('Extracting region of web element with selector', this.selector);
106
+ return this.context.execute(snippets.getElementRect, [this, false]);
107
+ }
108
+ else {
109
+ this._logger.log('Extracting region of native element with selector', this.selector);
110
+ const region = await this._spec.getElementRegion(this.driver.target, this.target);
111
+ this._logger.log('Extracted native region', region);
112
+ return this.driver.normalizeRegion(region);
113
+ }
114
+ });
115
+ this._logger.log('Extracted region', region);
116
+ return region;
117
+ }
118
+ async getClientRegion() {
119
+ const region = await this.withRefresh(async () => {
120
+ if (this.driver.isWeb) {
121
+ this._logger.log('Extracting region of web element with selector', this.selector);
122
+ return this.context.execute(snippets.getElementRect, [this, true]);
123
+ }
124
+ else {
125
+ return this.getRegion();
126
+ }
127
+ });
128
+ this._logger.log('Extracted client region', region);
129
+ return region;
130
+ }
131
+ async getContentSize() {
132
+ if (this._state.contentSize)
133
+ return this._state.contentSize;
134
+ const size = await this.withRefresh(async () => {
135
+ var _a, _b, _c;
136
+ if (this.driver.isWeb) {
137
+ this._logger.log('Extracting content size of web element with selector', this.selector);
138
+ return this.context.execute(snippets.getElementContentSize, [this]);
139
+ }
140
+ else {
141
+ this._logger.log('Extracting content size of native element with selector', this.selector);
142
+ try {
143
+ const _d = await this.getAttribute('contentSize')
144
+ .then(data => {
145
+ const contentSize = JSON.parse(data);
146
+ return {
147
+ touchPadding: contentSize.touchPadding,
148
+ x: contentSize.left,
149
+ y: contentSize.top,
150
+ width: contentSize.width,
151
+ height: (this.driver.isAndroid ? contentSize.height : 0) + contentSize.scrollableOffset,
152
+ };
153
+ })
154
+ .catch(() => {
155
+ return this._spec.getElementRegion(this.driver.target, this.target);
156
+ }), { touchPadding } = _d, contentRegion = __rest(_d, ["touchPadding"]);
157
+ this._logger.log('Extracted native content size attribute', contentRegion);
158
+ const contentSize = await ((_a = this.driver.helper) === null || _a === void 0 ? void 0 : _a.getContentSize(this));
159
+ this._logger.log('Extracted native content size with helper library', contentSize);
160
+ this._state.contentSize = {
161
+ width: Math.max((_b = contentSize === null || contentSize === void 0 ? void 0 : contentSize.width) !== null && _b !== void 0 ? _b : 0, contentRegion.width),
162
+ height: Math.max((_c = contentSize === null || contentSize === void 0 ? void 0 : contentSize.height) !== null && _c !== void 0 ? _c : 0, contentRegion.height),
163
+ };
164
+ this._touchPadding = touchPadding !== null && touchPadding !== void 0 ? touchPadding : this._touchPadding;
165
+ if (this.driver.isAndroid) {
166
+ this._state.contentSize = utils.geometry.scale(this._state.contentSize, 1 / this.driver.pixelRatio);
167
+ }
168
+ if (contentRegion.y < this.driver.statusBarHeight) {
169
+ this._state.contentSize.height -= this.driver.statusBarHeight - contentRegion.y;
170
+ }
171
+ // android has a bug when after extracting 'contentSize' attribute the element is being scrolled by undetermined number of pixels
172
+ if (this.driver.isAndroid) {
173
+ this._logger.log('Stabilizing android scroll offset');
174
+ const originalScrollOffset = await this.getScrollOffset();
175
+ this._state.scrollOffset = { x: -1, y: -1 };
176
+ await this.scrollTo({ x: 0, y: 0 });
177
+ await this.scrollTo(originalScrollOffset);
178
+ }
179
+ return this._state.contentSize;
180
+ }
181
+ catch (err) {
182
+ this._logger.warn('Failed to extract content size, extracting client size instead');
183
+ this._logger.error(err);
184
+ return utils.geometry.size(await this.getClientRegion());
185
+ }
186
+ }
187
+ });
188
+ this._logger.log('Extracted content size', size);
189
+ return size;
190
+ }
191
+ async isScrollable() {
192
+ this._logger.log('Check is element with selector', this.selector, 'is scrollable');
193
+ const isScrollable = await this.withRefresh(async () => {
194
+ if (this.driver.isWeb) {
195
+ return this.context.execute(snippets.isElementScrollable, [this]);
196
+ }
197
+ else if (this.driver.isAndroid) {
198
+ const data = JSON.parse(await this.getAttribute('scrollable'));
199
+ return Boolean(data) || false;
200
+ }
201
+ else if (this.driver.isIOS) {
202
+ const type = await this.getAttribute('type');
203
+ return ['XCUIElementTypeScrollView', 'XCUIElementTypeTable', 'XCUIElementTypeCollectionView'].includes(type);
204
+ }
205
+ });
206
+ this._logger.log('Element is scrollable', isScrollable);
207
+ return isScrollable;
208
+ }
209
+ async isRoot() {
210
+ // TODO replace with snippet
211
+ return this.withRefresh(async () => {
212
+ if (this.driver.isWeb) {
213
+ const rootElement = await this.context.element({ type: 'css', selector: 'html' });
214
+ return this.equals(rootElement);
215
+ }
216
+ else {
217
+ return false;
218
+ }
219
+ });
220
+ }
221
+ async getTouchPadding() {
222
+ var _a;
223
+ if (this._touchPadding == null) {
224
+ if (this.driver.isWeb)
225
+ this._touchPadding = 0;
226
+ else if (this.driver.isIOS)
227
+ this._touchPadding = 10;
228
+ else if (this.driver.isAndroid) {
229
+ const data = await this.getAttribute('contentSize')
230
+ .then(JSON.parse)
231
+ .catch(() => null);
232
+ this._touchPadding = (_a = data === null || data === void 0 ? void 0 : data.touchPadding) !== null && _a !== void 0 ? _a : 24;
233
+ }
234
+ }
235
+ return this._touchPadding;
236
+ }
237
+ async getText() {
238
+ const text = await this.withRefresh(async () => {
239
+ if (this.driver.isWeb) {
240
+ return '';
241
+ }
242
+ else {
243
+ this._logger.log('Extracting text of native element with selector', this.selector);
244
+ return this._spec.getElementText(this.driver.target, this.target);
245
+ }
246
+ });
247
+ this._logger.log('Extracted element text', text);
248
+ return text;
249
+ }
250
+ async getAttribute(name) {
251
+ if (this.driver.isWeb) {
252
+ const properties = await this.context.execute(snippets.getElementProperties, [this, [name]]);
253
+ return properties[name];
254
+ }
255
+ else {
256
+ return this._spec.getElementAttribute(this.driver.target, this.target, name);
257
+ }
258
+ }
259
+ async setAttribute(name, value) {
260
+ if (this.driver.isWeb) {
261
+ await this.context.execute(snippets.setElementAttributes, [this, { [name]: value }]);
262
+ }
263
+ }
264
+ async scrollTo(offset) {
265
+ return this.withRefresh(async () => {
266
+ offset = utils.geometry.round(offset);
267
+ if (this.driver.isWeb) {
268
+ let actualOffset = await this.context.execute(snippets.scrollTo, [this, offset]);
269
+ // 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
270
+ if (this.driver.isIOS)
271
+ actualOffset = await this.getScrollOffset();
272
+ return actualOffset;
273
+ }
274
+ else {
275
+ const currentScrollOffset = await this.getScrollOffset();
276
+ if (utils.geometry.equals(offset, currentScrollOffset))
277
+ return currentScrollOffset;
278
+ const contentSize = await this.getContentSize();
279
+ const scrollableRegion = await this.getClientRegion();
280
+ const effectiveRegion = this.driver.isAndroid
281
+ ? utils.geometry.scale(scrollableRegion, this.driver.pixelRatio)
282
+ : scrollableRegion;
283
+ const maxOffset = {
284
+ x: Math.round(scrollableRegion.width * (contentSize.width / scrollableRegion.width - 1)),
285
+ y: Math.round(scrollableRegion.height * (contentSize.height / scrollableRegion.height - 1)),
286
+ };
287
+ const requiredOffset = { x: Math.min(offset.x, maxOffset.x), y: Math.min(offset.y, maxOffset.y) };
288
+ let remainingOffset = offset.x === 0 && offset.y === 0
289
+ ? { x: -maxOffset.x, y: -maxOffset.y } // if it has to be scrolled to the very beginning, then scroll maximum amount of pixels
290
+ : utils.geometry.offsetNegative(requiredOffset, currentScrollOffset);
291
+ if (this.driver.isAndroid) {
292
+ remainingOffset = utils.geometry.scale(remainingOffset, this.driver.pixelRatio);
293
+ }
294
+ const actions = [];
295
+ const touchPadding = await this.getTouchPadding();
296
+ const xPadding = Math.max(Math.floor(effectiveRegion.width * 0.1), touchPadding);
297
+ const yTrack = Math.floor(effectiveRegion.y + effectiveRegion.height / 2); // center
298
+ const xLeft = effectiveRegion.y + xPadding;
299
+ const xDirection = remainingOffset.y > 0 ? 'right' : 'left';
300
+ const xGap = xDirection === 'right' ? -touchPadding : touchPadding;
301
+ let xRemaining = Math.abs(remainingOffset.x);
302
+ while (xRemaining > 0) {
303
+ const xRight = effectiveRegion.x + Math.min(xRemaining + xPadding, effectiveRegion.width - xPadding);
304
+ const [xStart, xEnd] = xDirection === 'right' ? [xRight, xLeft] : [xLeft, xRight];
305
+ actions.push([
306
+ { action: 'press', y: yTrack, x: xStart },
307
+ { action: 'wait', ms: 100 },
308
+ { action: 'moveTo', y: yTrack, x: xStart + xGap },
309
+ { action: 'wait', ms: 100 },
310
+ { action: 'moveTo', y: yTrack, x: xEnd + xGap },
311
+ { action: 'wait', ms: 100 },
312
+ { action: 'moveTo', y: yTrack + 1, x: xEnd + xGap },
313
+ { action: 'release' },
314
+ ]);
315
+ xRemaining -= xRight - xLeft;
316
+ }
317
+ const yPadding = Math.max(Math.floor(effectiveRegion.height * 0.1), touchPadding);
318
+ const xTrack = Math.floor(effectiveRegion.x + 5); // a little bit off left border
319
+ const yBottom = effectiveRegion.y + effectiveRegion.height - yPadding;
320
+ const yDirection = remainingOffset.y > 0 ? 'down' : 'up';
321
+ const yGap = yDirection === 'down' ? -touchPadding : touchPadding;
322
+ let yRemaining = Math.abs(remainingOffset.y);
323
+ while (yRemaining > 0) {
324
+ const yTop = Math.max(yBottom - yRemaining, effectiveRegion.y + yPadding);
325
+ const [yStart, yEnd] = yDirection === 'down' ? [yBottom, yTop] : [yTop, yBottom];
326
+ actions.push([
327
+ { action: 'press', x: xTrack, y: yStart },
328
+ { action: 'wait', ms: 100 },
329
+ { action: 'moveTo', x: xTrack, y: yStart + yGap },
330
+ { action: 'wait', ms: 100 },
331
+ { action: 'moveTo', x: xTrack, y: yEnd + yGap },
332
+ { action: 'wait', ms: 100 },
333
+ { action: 'moveTo', x: xTrack + 1, y: yEnd + yGap },
334
+ { action: 'release' },
335
+ ]);
336
+ yRemaining -= yBottom - yTop;
337
+ }
338
+ // ios actions should be executed one-by-one sequentially, otherwise the result isn't stable
339
+ if (this.driver.isIOS) {
340
+ for (const action of actions) {
341
+ await this._spec.performAction(this.driver.target, action);
342
+ }
343
+ }
344
+ else {
345
+ await this._spec.performAction(this.driver.target, [].concat(...actions));
346
+ }
347
+ const actualScrollableRegion = await this.getClientRegion();
348
+ this._state.scrollOffset = utils.geometry.offsetNegative(requiredOffset, {
349
+ x: scrollableRegion.x - actualScrollableRegion.x,
350
+ y: scrollableRegion.y - actualScrollableRegion.y,
351
+ });
352
+ return this._state.scrollOffset;
353
+ }
354
+ });
355
+ }
356
+ async translateTo(offset) {
357
+ offset = { x: Math.round(offset.x), y: Math.round(offset.y) };
358
+ if (this.driver.isWeb) {
359
+ return this.withRefresh(async () => this.context.execute(snippets.translateTo, [this, offset]));
360
+ }
361
+ else {
362
+ throw new Error('Cannot apply css translate scrolling on non-web element');
363
+ }
364
+ }
365
+ async getScrollOffset() {
366
+ var _a;
367
+ if (this.driver.isWeb) {
368
+ return this.withRefresh(() => this.context.execute(snippets.getElementScrollOffset, [this]));
369
+ }
370
+ else {
371
+ return (_a = this._state.scrollOffset) !== null && _a !== void 0 ? _a : { x: 0, y: 0 };
372
+ }
373
+ }
374
+ async getTranslateOffset() {
375
+ if (this.driver.isWeb) {
376
+ return this.withRefresh(() => this.context.execute(snippets.getElementTranslateOffset, [this]));
377
+ }
378
+ else {
379
+ throw new Error('Cannot apply css translate scrolling on non-web element');
380
+ }
381
+ }
382
+ async getInnerOffset() {
383
+ if (this.driver.isWeb) {
384
+ return this.withRefresh(() => this.context.execute(snippets.getElementInnerOffset, [this]));
385
+ }
386
+ else {
387
+ return this.getScrollOffset();
388
+ }
389
+ }
390
+ async click() {
391
+ await this._spec.click(this.context.target, this.target);
392
+ }
393
+ async type(value) {
394
+ await this._spec.type(this.context.target, this.target, value);
395
+ }
396
+ async preserveState() {
397
+ if (this.driver.isNative)
398
+ return;
399
+ // TODO create single js snippet
400
+ const scrollOffset = await this.getScrollOffset();
401
+ const transforms = await this.context.execute(snippets.getElementStyleProperties, [
402
+ this,
403
+ ['transform', '-webkit-transform'],
404
+ ]);
405
+ if (!utils.types.has(this._state, ['scrollOffset', 'transforms'])) {
406
+ this._state.scrollOffset = scrollOffset;
407
+ this._state.transforms = transforms;
408
+ }
409
+ return { scrollOffset, transforms };
410
+ }
411
+ async restoreState(state = this._state) {
412
+ if (this.driver.isNative)
413
+ return;
414
+ if (state.scrollOffset)
415
+ await this.scrollTo(state.scrollOffset);
416
+ if (state.transforms)
417
+ await this.context.execute(snippets.setElementStyleProperties, [this, state.transforms]);
418
+ if (state === this._state) {
419
+ this._state.scrollOffset = null;
420
+ this._state.transforms = null;
421
+ }
422
+ }
423
+ async hideScrollbars() {
424
+ if (this.driver.isNative)
425
+ return;
426
+ if (this._originalOverflow)
427
+ return;
428
+ return this.withRefresh(async () => {
429
+ const { overflow } = await this.context.execute(snippets.setElementStyleProperties, [this, { overflow: 'hidden' }]);
430
+ this._originalOverflow = overflow;
431
+ });
432
+ }
433
+ async restoreScrollbars() {
434
+ if (this.driver.isNative)
435
+ return;
436
+ if (!this._originalOverflow)
437
+ return;
438
+ return this.withRefresh(async () => {
439
+ await this.context.execute(snippets.setElementStyleProperties, [this, { overflow: this._originalOverflow }]);
440
+ this._originalOverflow = null;
441
+ });
442
+ }
443
+ async refresh(freshElement) {
444
+ if (this._spec.isElement(freshElement)) {
445
+ this._target = freshElement;
446
+ return true;
447
+ }
448
+ if (!this._selector)
449
+ return false;
450
+ const element = this._index > 0
451
+ ? await this.context.elements(this._selector).then(elements => elements[this._index])
452
+ : await this.context.element(this._selector);
453
+ if (element) {
454
+ this._target = element.target;
455
+ }
456
+ return Boolean(element);
457
+ }
458
+ async withRefresh(operation) {
459
+ if (!this._spec.isStaleElementError)
460
+ return operation();
461
+ try {
462
+ const result = await operation();
463
+ // Some frameworks could handle stale element reference error by itself or doesn't throw an error
464
+ if (this._spec.isStaleElementError(result, this.selector)) {
465
+ await this.refresh();
466
+ return operation();
467
+ }
468
+ return result;
469
+ }
470
+ catch (err) {
471
+ if (this._spec.isStaleElementError(err)) {
472
+ const refreshed = await this.refresh();
473
+ if (refreshed)
474
+ return operation();
475
+ }
476
+ throw err;
477
+ }
478
+ }
479
+ toJSON() {
480
+ return this.target;
481
+ }
482
+ }
483
+ exports.Element = Element;