@applitools/driver 1.8.14 → 1.9.0
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/capabilities.js +9 -0
- package/dist/driver.js +53 -39
- package/dist/element.js +149 -65
- package/package.json +5 -5
- package/types/element.d.ts +10 -5
package/dist/capabilities.js
CHANGED
|
@@ -35,6 +35,7 @@ function parseCapabilities(capabilities, customConfig) {
|
|
|
35
35
|
if (info.isNative) {
|
|
36
36
|
info.pixelRatio = capabilities.pixelRatio;
|
|
37
37
|
info.statusBarHeight = capabilities.statBarHeight;
|
|
38
|
+
info.displaySize = extractDisplaySize(capabilities);
|
|
38
39
|
}
|
|
39
40
|
return info;
|
|
40
41
|
}
|
|
@@ -66,3 +67,11 @@ function isIOS(capabilities) {
|
|
|
66
67
|
function isAndroid(capabilities) {
|
|
67
68
|
return /Android/i.test(capabilities.platformName) || /Android/i.test(capabilities.browserName);
|
|
68
69
|
}
|
|
70
|
+
function extractDisplaySize(capabilities) {
|
|
71
|
+
if (!capabilities.deviceScreenSize)
|
|
72
|
+
return undefined;
|
|
73
|
+
const [width, height] = capabilities.deviceScreenSize.split('x');
|
|
74
|
+
if (Number.isNaN(Number(width)) || Number.isNaN(Number(height)))
|
|
75
|
+
return undefined;
|
|
76
|
+
return { width: Number(width), height: Number(height) };
|
|
77
|
+
}
|
package/dist/driver.js
CHANGED
|
@@ -144,72 +144,78 @@ class Driver {
|
|
|
144
144
|
this._currentContext = context;
|
|
145
145
|
}
|
|
146
146
|
async init() {
|
|
147
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v;
|
|
148
|
-
var
|
|
147
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w;
|
|
148
|
+
var _x, _y, _z, _0, _1, _2, _3, _4, _5, _6, _7, _8;
|
|
149
149
|
const capabilities = await ((_b = (_a = this._spec).getCapabilities) === null || _b === void 0 ? void 0 : _b.call(_a, this.target));
|
|
150
150
|
this._logger.log('Driver capabilities', capabilities);
|
|
151
151
|
const capabilitiesInfo = capabilities ? (0, capabilities_1.parseCapabilities)(capabilities, this._customConfig) : undefined;
|
|
152
152
|
const driverInfo = await ((_d = (_c = this._spec).getDriverInfo) === null || _d === void 0 ? void 0 : _d.call(_c, this.target));
|
|
153
153
|
this._driverInfo = { ...capabilitiesInfo, ...driverInfo };
|
|
154
154
|
if (this.isWeb) {
|
|
155
|
-
(_e = (
|
|
156
|
-
(_f = (
|
|
157
|
-
(_g = (
|
|
155
|
+
(_e = (_x = this._driverInfo).pixelRatio) !== null && _e !== void 0 ? _e : (_x.pixelRatio = await this.execute(snippets.getPixelRatio));
|
|
156
|
+
(_f = (_y = this._driverInfo).viewportScale) !== null && _f !== void 0 ? _f : (_y.viewportScale = await this.execute(snippets.getViewportScale));
|
|
157
|
+
(_g = (_z = this._driverInfo).userAgent) !== null && _g !== void 0 ? _g : (_z.userAgent = await this.execute(snippets.getUserAgent));
|
|
158
158
|
if (this._driverInfo.userAgent) {
|
|
159
159
|
const userAgentInfo = (0, user_agent_1.parseUserAgent)(this._driverInfo.userAgent);
|
|
160
160
|
this._driverInfo.browserName = (_h = userAgentInfo.browserName) !== null && _h !== void 0 ? _h : this._driverInfo.browserName;
|
|
161
161
|
this._driverInfo.browserVersion = (_j = userAgentInfo.browserVersion) !== null && _j !== void 0 ? _j : this._driverInfo.browserVersion;
|
|
162
162
|
if (this._driverInfo.isMobile) {
|
|
163
|
-
(_k = (
|
|
164
|
-
(_l = (
|
|
163
|
+
(_k = (_0 = this._driverInfo).platformName) !== null && _k !== void 0 ? _k : (_0.platformName = userAgentInfo.platformName);
|
|
164
|
+
(_l = (_1 = this._driverInfo).platformVersion) !== null && _l !== void 0 ? _l : (_1.platformVersion = userAgentInfo.platformVersion);
|
|
165
165
|
}
|
|
166
166
|
else {
|
|
167
167
|
this._driverInfo.platformName = (_m = userAgentInfo.platformName) !== null && _m !== void 0 ? _m : this._driverInfo.platformName;
|
|
168
168
|
this._driverInfo.platformVersion = (_o = userAgentInfo.platformVersion) !== null && _o !== void 0 ? _o : this._driverInfo.platformVersion;
|
|
169
169
|
}
|
|
170
170
|
}
|
|
171
|
-
(_p = (
|
|
172
|
-
(_q = (
|
|
171
|
+
(_p = (_2 = this._driverInfo).features) !== null && _p !== void 0 ? _p : (_2.features = {});
|
|
172
|
+
(_q = (_3 = this._driverInfo.features).allCookies) !== null && _q !== void 0 ? _q : (_3.allCookies = /chrome/i.test(this._driverInfo.browserName) && !this._driverInfo.isMobile);
|
|
173
173
|
}
|
|
174
174
|
else {
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
175
|
+
// this value always excludes the height of the navigation bar, and sometimes it also excludes the height of the status bar
|
|
176
|
+
let windowSize = await this._spec.getWindowSize(this.target);
|
|
177
|
+
(_r = (_4 = this._driverInfo).displaySize) !== null && _r !== void 0 ? _r : (_4.displaySize = windowSize);
|
|
178
178
|
if (this.isAndroid) {
|
|
179
179
|
// bar sizes could be extracted only on android
|
|
180
|
-
const barsSize = await ((
|
|
180
|
+
const barsSize = await ((_t = (_s = this._spec).getBarsSize) === null || _t === void 0 ? void 0 : _t.call(_s, this.target).catch(() => undefined));
|
|
181
181
|
if (barsSize) {
|
|
182
182
|
this._logger.log('Driver bars size', barsSize);
|
|
183
183
|
// navigation bar height is replaced with the width in landscape orientation on android (due to the bug in appium)
|
|
184
|
-
if (
|
|
184
|
+
if ((await this.getOrientation()) === 'landscape')
|
|
185
185
|
barsSize.navigationBarHeight = barsSize.navigationBarWidth;
|
|
186
186
|
// when status bar is overlapping content on android it returns status bar height equal to viewport height
|
|
187
|
-
if (barsSize.statusBarHeight
|
|
188
|
-
this._driverInfo.statusBarHeight = Math.max((
|
|
187
|
+
if (barsSize.statusBarHeight < this._driverInfo.displaySize.height) {
|
|
188
|
+
this._driverInfo.statusBarHeight = Math.max((_u = this._driverInfo.statusBarHeight) !== null && _u !== void 0 ? _u : 0, barsSize.statusBarHeight);
|
|
189
189
|
}
|
|
190
190
|
// when navigation bar is invisible on android it returns navigation bar height equal to viewport height
|
|
191
|
-
if (barsSize.navigationBarHeight
|
|
192
|
-
this._driverInfo.navigationBarHeight = Math.max((
|
|
193
|
-
}
|
|
194
|
-
else {
|
|
195
|
-
// if navigation bar height is bigger then display height he can use it to reset display height
|
|
196
|
-
displaySize.height = barsSize.navigationBarHeight / this.pixelRatio;
|
|
191
|
+
if (barsSize.navigationBarHeight < this._driverInfo.displaySize.height) {
|
|
192
|
+
this._driverInfo.navigationBarHeight = Math.max((_v = this._driverInfo.navigationBarHeight) !== null && _v !== void 0 ? _v : 0, barsSize.navigationBarHeight);
|
|
197
193
|
}
|
|
198
194
|
// bar heights have to be scaled on android
|
|
199
|
-
(
|
|
200
|
-
(
|
|
195
|
+
(_5 = this._driverInfo).statusBarHeight && (_5.statusBarHeight = this._driverInfo.statusBarHeight / this.pixelRatio);
|
|
196
|
+
(_6 = this._driverInfo).navigationBarHeight && (_6.navigationBarHeight = this._driverInfo.navigationBarHeight / this.pixelRatio);
|
|
201
197
|
}
|
|
198
|
+
windowSize = utils.geometry.scale(this._driverInfo.displaySize, 1 / this.pixelRatio);
|
|
199
|
+
(_7 = this._driverInfo).displaySize && (_7.displaySize = utils.geometry.scale(this._driverInfo.displaySize, 1 / this.pixelRatio));
|
|
202
200
|
}
|
|
203
201
|
// calculate viewport size
|
|
204
202
|
if (!this._driverInfo.viewportSize) {
|
|
205
|
-
this.
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
203
|
+
if (this.navigationBarHeight > 1) {
|
|
204
|
+
this._driverInfo.viewportSize = {
|
|
205
|
+
width: this._driverInfo.displaySize.width,
|
|
206
|
+
height: this._driverInfo.displaySize.height - this.statusBarHeight - this.navigationBarHeight,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
this._driverInfo.viewportSize = {
|
|
211
|
+
width: windowSize.width,
|
|
212
|
+
height: windowSize.height - this.statusBarHeight,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
209
215
|
}
|
|
210
216
|
// calculate safe area
|
|
211
217
|
if (this.isIOS && !this._driverInfo.safeArea) {
|
|
212
|
-
this._driverInfo.safeArea = { x: 0, y: 0, ...displaySize };
|
|
218
|
+
this._driverInfo.safeArea = { x: 0, y: 0, ...this._driverInfo.displaySize };
|
|
213
219
|
const topElement = await this.element({ type: '-ios class chain', selector: '**/XCUIElementTypeNavigationBar' });
|
|
214
220
|
if (topElement) {
|
|
215
221
|
const topRegion = await this._spec.getElementRegion(this.target, topElement.target);
|
|
@@ -230,7 +236,7 @@ class Driver {
|
|
|
230
236
|
: await helper_android_1.HelperAndroid.make({ spec: this._spec, driver: this, logger: this._logger });
|
|
231
237
|
}
|
|
232
238
|
if (this.isMobile) {
|
|
233
|
-
(
|
|
239
|
+
(_w = (_8 = this._driverInfo).orientation) !== null && _w !== void 0 ? _w : (_8.orientation = await this.getOrientation().catch(() => undefined));
|
|
234
240
|
}
|
|
235
241
|
this._logger.log('Combined driver info', this._driverInfo);
|
|
236
242
|
return this;
|
|
@@ -400,19 +406,22 @@ class Driver {
|
|
|
400
406
|
async normalizeRegion(region) {
|
|
401
407
|
if (this.isWeb || !utils.types.has(this._driverInfo, ['viewportSize', 'statusBarHeight']))
|
|
402
408
|
return region;
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
409
|
+
let normalizedRegion = region;
|
|
410
|
+
if (this.isAndroid) {
|
|
411
|
+
normalizedRegion = utils.geometry.scale(normalizedRegion, 1 / this.pixelRatio);
|
|
412
|
+
}
|
|
413
|
+
if (this.isIOS && utils.geometry.isIntersected(normalizedRegion, this._driverInfo.safeArea)) {
|
|
414
|
+
normalizedRegion = utils.geometry.intersect(normalizedRegion, this._driverInfo.safeArea);
|
|
415
|
+
}
|
|
416
|
+
normalizedRegion = utils.geometry.offsetNegative(normalizedRegion, {
|
|
408
417
|
x: this.isAndroid && this.orientation === 'landscape' && this.platformVersion > 7 ? this.navigationBarHeight : 0,
|
|
409
418
|
y: this.statusBarHeight,
|
|
410
419
|
});
|
|
411
|
-
if (
|
|
412
|
-
|
|
413
|
-
|
|
420
|
+
if (normalizedRegion.y < 0) {
|
|
421
|
+
normalizedRegion.height += normalizedRegion.y;
|
|
422
|
+
normalizedRegion.y = 0;
|
|
414
423
|
}
|
|
415
|
-
return
|
|
424
|
+
return normalizedRegion;
|
|
416
425
|
}
|
|
417
426
|
async getRegionInViewport(context, region) {
|
|
418
427
|
await context.focus();
|
|
@@ -508,8 +517,13 @@ class Driver {
|
|
|
508
517
|
throw new Error('Failed to set viewport size!');
|
|
509
518
|
}
|
|
510
519
|
async getDisplaySize() {
|
|
520
|
+
var _a;
|
|
511
521
|
if (this.isWeb && !this.isMobile)
|
|
512
522
|
return;
|
|
523
|
+
if ((_a = this._driverInfo) === null || _a === void 0 ? void 0 : _a.viewportSize) {
|
|
524
|
+
this._logger.log('Extracting display size from native driver using cached value');
|
|
525
|
+
return this._driverInfo.displaySize;
|
|
526
|
+
}
|
|
513
527
|
const size = await this._spec.getWindowSize(this.target);
|
|
514
528
|
const normalizedSize = this.isAndroid ? utils.geometry.scale(size, 1 / this.pixelRatio) : size;
|
|
515
529
|
this._logger.log('Extracted and normalized display size:', normalizedSize);
|
package/dist/element.js
CHANGED
|
@@ -88,20 +88,44 @@ class Element {
|
|
|
88
88
|
}
|
|
89
89
|
async contains(innerElement) {
|
|
90
90
|
const contains = await this.withRefresh(async () => {
|
|
91
|
+
var _a, _b, _c, _d, _e;
|
|
92
|
+
var _f;
|
|
91
93
|
innerElement = innerElement instanceof Element ? innerElement.target : innerElement;
|
|
92
94
|
if (this.driver.isWeb) {
|
|
93
95
|
this._logger.log('Checking if web element with selector', this.selector, 'contains element', innerElement);
|
|
94
96
|
return false; // TODO implement a snipped for web
|
|
95
97
|
}
|
|
96
98
|
else {
|
|
99
|
+
if ((_a = this._state.containedElements) === null || _a === void 0 ? void 0 : _a.has(innerElement))
|
|
100
|
+
return this._state.containedElements.get(innerElement);
|
|
97
101
|
this._logger.log('Checking if native element with selector', this.selector, 'contains element', innerElement);
|
|
98
102
|
// appium doesn't have a way to check if an element is contained in another element, so juristic applied
|
|
99
103
|
if (await this.equals(innerElement))
|
|
100
104
|
return false;
|
|
101
|
-
// if inner element region is
|
|
102
|
-
const
|
|
105
|
+
// 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
|
|
106
|
+
const contentRegion = await this.getAttribute('contentSize')
|
|
107
|
+
.then(data => {
|
|
108
|
+
const contentSize = JSON.parse(data);
|
|
109
|
+
return {
|
|
110
|
+
x: contentSize.left,
|
|
111
|
+
y: contentSize.top,
|
|
112
|
+
width: contentSize.width,
|
|
113
|
+
height: (this.driver.isAndroid ? contentSize.height : 0) + contentSize.scrollableOffset,
|
|
114
|
+
};
|
|
115
|
+
})
|
|
116
|
+
.catch(() => this._spec.getElementRegion(this.driver.target, this.target));
|
|
117
|
+
const contentSize = await ((_b = this.driver.helper) === null || _b === void 0 ? void 0 : _b.getContentSize(this));
|
|
118
|
+
const region = {
|
|
119
|
+
x: contentRegion.x,
|
|
120
|
+
y: contentRegion.y,
|
|
121
|
+
width: Math.max((_c = contentSize === null || contentSize === void 0 ? void 0 : contentSize.width) !== null && _c !== void 0 ? _c : 0, contentRegion.width),
|
|
122
|
+
height: Math.max((_d = contentSize === null || contentSize === void 0 ? void 0 : contentSize.height) !== null && _d !== void 0 ? _d : 0, contentRegion.height),
|
|
123
|
+
};
|
|
103
124
|
const innerRegion = await this._spec.getElementRegion(this.driver.target, innerElement);
|
|
104
|
-
|
|
125
|
+
const contains = utils.geometry.contains(region, innerRegion);
|
|
126
|
+
(_e = (_f = this._state).containedElements) !== null && _e !== void 0 ? _e : (_f.containedElements = new Map());
|
|
127
|
+
this._state.containedElements.set(innerElement, contains);
|
|
128
|
+
return contains;
|
|
105
129
|
}
|
|
106
130
|
});
|
|
107
131
|
this._logger.log('Element with selector', this.selector, contains ? 'contains' : `doesn't contain`, innerElement);
|
|
@@ -166,11 +190,10 @@ class Element {
|
|
|
166
190
|
else {
|
|
167
191
|
this._logger.log('Extracting content size of native element with selector', this.selector);
|
|
168
192
|
try {
|
|
169
|
-
const
|
|
193
|
+
const contentRegion = await this.getAttribute('contentSize')
|
|
170
194
|
.then(data => {
|
|
171
195
|
const contentSize = JSON.parse(data);
|
|
172
196
|
return {
|
|
173
|
-
touchPadding: contentSize.touchPadding,
|
|
174
197
|
x: contentSize.left,
|
|
175
198
|
y: contentSize.top,
|
|
176
199
|
width: contentSize.width,
|
|
@@ -178,7 +201,7 @@ class Element {
|
|
|
178
201
|
};
|
|
179
202
|
})
|
|
180
203
|
.catch(err => {
|
|
181
|
-
this._logger.
|
|
204
|
+
this._logger.warn(`Unable to get the attribute 'contentSize' due to the following error: '${err.message}'`);
|
|
182
205
|
return this._spec.getElementRegion(this.driver.target, this.target);
|
|
183
206
|
});
|
|
184
207
|
this._logger.log('Extracted native content size attribute', contentRegion);
|
|
@@ -188,22 +211,12 @@ class Element {
|
|
|
188
211
|
width: Math.max((_b = contentSize === null || contentSize === void 0 ? void 0 : contentSize.width) !== null && _b !== void 0 ? _b : 0, contentRegion.width),
|
|
189
212
|
height: Math.max((_c = contentSize === null || contentSize === void 0 ? void 0 : contentSize.height) !== null && _c !== void 0 ? _c : 0, contentRegion.height),
|
|
190
213
|
};
|
|
191
|
-
this._touchPadding = touchPadding !== null && touchPadding !== void 0 ? touchPadding : this._touchPadding;
|
|
192
|
-
this._logger.log('touchPadding', this._touchPadding);
|
|
193
214
|
if (this.driver.isAndroid) {
|
|
194
215
|
this._state.contentSize = utils.geometry.scale(this._state.contentSize, 1 / this.driver.pixelRatio);
|
|
195
216
|
}
|
|
196
217
|
if (contentRegion.y < this.driver.statusBarHeight) {
|
|
197
218
|
this._state.contentSize.height -= this.driver.statusBarHeight - contentRegion.y;
|
|
198
219
|
}
|
|
199
|
-
// android has a bug when after extracting 'contentSize' attribute the element is being scrolled by undetermined number of pixels
|
|
200
|
-
if (this.driver.isAndroid) {
|
|
201
|
-
this._logger.log('Stabilizing android scroll offset');
|
|
202
|
-
const originalScrollOffset = await this.getScrollOffset();
|
|
203
|
-
this._state.scrollOffset = { x: -1, y: -1 };
|
|
204
|
-
await this.scrollTo({ x: 0, y: 0 });
|
|
205
|
-
await this.scrollTo(originalScrollOffset);
|
|
206
|
-
}
|
|
207
220
|
return this._state.contentSize;
|
|
208
221
|
}
|
|
209
222
|
catch (err) {
|
|
@@ -216,8 +229,22 @@ class Element {
|
|
|
216
229
|
this._logger.log('Extracted content size', size);
|
|
217
230
|
return size;
|
|
218
231
|
}
|
|
232
|
+
async isPager() {
|
|
233
|
+
this._logger.log('Check if element with selector', this.selector, 'is scrollable by pages');
|
|
234
|
+
const isPager = await this.withRefresh(async () => {
|
|
235
|
+
if (this.driver.isAndroid) {
|
|
236
|
+
const className = await this.getAttribute('className');
|
|
237
|
+
return ['androidx.viewpager.widget.ViewPager'].includes(className);
|
|
238
|
+
}
|
|
239
|
+
else {
|
|
240
|
+
return false;
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
this._logger.log('Element scrollable by pages', isPager);
|
|
244
|
+
return isPager;
|
|
245
|
+
}
|
|
219
246
|
async isScrollable() {
|
|
220
|
-
this._logger.log('Check
|
|
247
|
+
this._logger.log('Check if element with selector', this.selector, 'is scrollable');
|
|
221
248
|
const isScrollable = await this.withRefresh(async () => {
|
|
222
249
|
if (this.driver.isWeb) {
|
|
223
250
|
return this.context.execute(snippets.isElementScrollable, [this]);
|
|
@@ -247,23 +274,22 @@ class Element {
|
|
|
247
274
|
});
|
|
248
275
|
}
|
|
249
276
|
async getTouchPadding() {
|
|
250
|
-
|
|
251
|
-
if (this._touchPadding == null) {
|
|
277
|
+
if (this._state.touchPadding == null) {
|
|
252
278
|
if (this.driver.isWeb)
|
|
253
|
-
this.
|
|
279
|
+
this._state.touchPadding = 0;
|
|
254
280
|
else if (this.driver.isIOS)
|
|
255
|
-
this.
|
|
281
|
+
this._state.touchPadding = 10;
|
|
256
282
|
else if (this.driver.isAndroid) {
|
|
257
|
-
const
|
|
258
|
-
.then(JSON.parse)
|
|
283
|
+
const touchPadding = await this.getAttribute('contentSize')
|
|
284
|
+
.then(value => JSON.parse(value).touchPadding)
|
|
259
285
|
.catch(err => {
|
|
260
|
-
this._logger.
|
|
286
|
+
this._logger.warn(`Unable to get the attribute 'contentSize' when looking up 'touchPadding' due to the following error: '${err.message}'`);
|
|
261
287
|
});
|
|
262
|
-
this.
|
|
263
|
-
this._logger.log('
|
|
288
|
+
this._state.touchPadding = touchPadding !== null && touchPadding !== void 0 ? touchPadding : 20;
|
|
289
|
+
this._logger.log('Touch padding set:', this._state.touchPadding);
|
|
264
290
|
}
|
|
265
291
|
}
|
|
266
|
-
return this.
|
|
292
|
+
return this._state.touchPadding;
|
|
267
293
|
}
|
|
268
294
|
async getText() {
|
|
269
295
|
const text = await this.withRefresh(async () => {
|
|
@@ -279,20 +305,41 @@ class Element {
|
|
|
279
305
|
return text;
|
|
280
306
|
}
|
|
281
307
|
async getAttribute(name) {
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
308
|
+
var _a;
|
|
309
|
+
// we assumes that attributes are not changed during the session
|
|
310
|
+
if ((_a = this._state.attributes) === null || _a === void 0 ? void 0 : _a[name])
|
|
311
|
+
return this._state.attributes[name];
|
|
312
|
+
const value = await this.withRefresh(async () => {
|
|
313
|
+
var _a;
|
|
314
|
+
var _b;
|
|
315
|
+
if (this.driver.isWeb) {
|
|
316
|
+
const properties = await this.context.execute(snippets.getElementProperties, [this, [name]]);
|
|
317
|
+
return properties[name];
|
|
318
|
+
}
|
|
319
|
+
else {
|
|
320
|
+
this._logger.log(`Extracting "${name}" attribute of native element with selector`, this.selector);
|
|
321
|
+
const value = await this._spec.getElementAttribute(this.driver.target, this.target, name);
|
|
322
|
+
(_a = (_b = this._state).attributes) !== null && _a !== void 0 ? _a : (_b.attributes = {});
|
|
323
|
+
this._state.attributes[name] = value;
|
|
324
|
+
if (this.driver.isAndroid && name === 'contentSize') {
|
|
325
|
+
// android has a bug when after extracting 'contentSize' attribute the element is being scrolled by undetermined number of pixels
|
|
326
|
+
this._logger.log('Stabilizing android scroll offset');
|
|
327
|
+
const originalScrollOffset = await this.getScrollOffset();
|
|
328
|
+
await this.scrollTo({ x: 0, y: 0 }, { force: true });
|
|
329
|
+
await this.scrollTo(originalScrollOffset);
|
|
330
|
+
}
|
|
331
|
+
return value;
|
|
332
|
+
}
|
|
333
|
+
});
|
|
334
|
+
this._logger.log(`Extracted element "${name}" attribute:`, value);
|
|
335
|
+
return value;
|
|
289
336
|
}
|
|
290
337
|
async setAttribute(name, value) {
|
|
291
338
|
if (this.driver.isWeb) {
|
|
292
339
|
await this.context.execute(snippets.setElementAttributes, [this, { [name]: value }]);
|
|
293
340
|
}
|
|
294
341
|
}
|
|
295
|
-
async scrollTo(offset) {
|
|
342
|
+
async scrollTo(offset, options) {
|
|
296
343
|
return this.withRefresh(async () => {
|
|
297
344
|
offset = utils.geometry.round({ x: Math.max(offset.x, 0), y: Math.max(offset.y, 0) });
|
|
298
345
|
if (this.driver.isWeb) {
|
|
@@ -304,75 +351,112 @@ class Element {
|
|
|
304
351
|
}
|
|
305
352
|
else {
|
|
306
353
|
const currentScrollOffset = await this.getScrollOffset();
|
|
307
|
-
if (utils.geometry.equals(offset, currentScrollOffset))
|
|
354
|
+
if (!(options === null || options === void 0 ? void 0 : options.force) && utils.geometry.equals(offset, currentScrollOffset))
|
|
308
355
|
return currentScrollOffset;
|
|
309
356
|
const contentSize = await this.getContentSize();
|
|
310
357
|
const scrollableRegion = await this.getClientRegion();
|
|
311
|
-
const effectiveRegion = this.driver.isAndroid
|
|
312
|
-
? utils.geometry.scale(scrollableRegion, this.driver.pixelRatio)
|
|
313
|
-
: scrollableRegion;
|
|
314
358
|
const maxOffset = {
|
|
315
359
|
x: Math.round(scrollableRegion.width * (contentSize.width / scrollableRegion.width - 1)),
|
|
316
360
|
y: Math.round(scrollableRegion.height * (contentSize.height / scrollableRegion.height - 1)),
|
|
317
361
|
};
|
|
318
362
|
const requiredOffset = { x: Math.min(offset.x, maxOffset.x), y: Math.min(offset.y, maxOffset.y) };
|
|
319
|
-
let
|
|
363
|
+
let effectiveRegion = scrollableRegion;
|
|
364
|
+
let remainingOffset = utils.geometry.equals(requiredOffset, { x: 0, y: 0 })
|
|
320
365
|
? { x: -maxOffset.x, y: -maxOffset.y } // if it has to be scrolled to the very beginning, then scroll maximum amount of pixels
|
|
321
366
|
: utils.geometry.offsetNegative(requiredOffset, currentScrollOffset);
|
|
322
367
|
if (this.driver.isAndroid) {
|
|
323
368
|
remainingOffset = utils.geometry.scale(remainingOffset, this.driver.pixelRatio);
|
|
369
|
+
effectiveRegion = utils.geometry.scale(scrollableRegion, this.driver.pixelRatio);
|
|
324
370
|
}
|
|
325
371
|
const actions = [];
|
|
326
372
|
const touchPadding = await this.getTouchPadding();
|
|
327
|
-
const
|
|
373
|
+
const isPager = await this.isPager();
|
|
374
|
+
const xPadding = Math.max(Math.floor(effectiveRegion.width * 0.07), touchPadding);
|
|
328
375
|
const yTrack = Math.floor(effectiveRegion.y + effectiveRegion.height / 2); // center
|
|
329
376
|
const xLeft = effectiveRegion.y + xPadding;
|
|
330
|
-
const xDirection = remainingOffset.
|
|
377
|
+
const xDirection = remainingOffset.x > 0 ? 'right' : 'left';
|
|
331
378
|
const xGap = xDirection === 'right' ? -touchPadding : touchPadding;
|
|
332
379
|
let xRemaining = Math.abs(remainingOffset.x);
|
|
380
|
+
if (isPager) {
|
|
381
|
+
const xPages = Math.floor(xRemaining / effectiveRegion.width);
|
|
382
|
+
xRemaining = (effectiveRegion.width - xPadding * 2) * xPages;
|
|
383
|
+
}
|
|
333
384
|
while (xRemaining > 0) {
|
|
334
385
|
const xRight = effectiveRegion.x + Math.min(xRemaining + xPadding, effectiveRegion.width - xPadding);
|
|
335
386
|
const [xStart, xEnd] = xDirection === 'right' ? [xRight, xLeft] : [xLeft, xRight];
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
387
|
+
if (isPager) {
|
|
388
|
+
actions.push([
|
|
389
|
+
{ action: 'press', y: yTrack, x: xStart },
|
|
390
|
+
// scroll through the page
|
|
391
|
+
{ action: 'wait', ms: 170 },
|
|
392
|
+
{ action: 'moveTo', y: yTrack, x: xEnd },
|
|
393
|
+
{ action: 'release' },
|
|
394
|
+
]);
|
|
395
|
+
}
|
|
396
|
+
else {
|
|
397
|
+
actions.push([
|
|
398
|
+
{ action: 'press', y: yTrack, x: xStart },
|
|
399
|
+
// move through scrolling gap (actual scrolling will be triggered only after that)
|
|
400
|
+
{ action: 'wait', ms: 100 },
|
|
401
|
+
{ action: 'moveTo', y: yTrack, x: xStart + xGap },
|
|
402
|
+
// perform actual scrolling
|
|
403
|
+
{ action: 'wait', ms: 100 },
|
|
404
|
+
{ action: 'moveTo', y: yTrack, x: xEnd + xGap },
|
|
405
|
+
// prevent inertial scrolling after release
|
|
406
|
+
{ action: 'wait', ms: 100 },
|
|
407
|
+
{ action: 'moveTo', y: yTrack + 1, x: xEnd + xGap },
|
|
408
|
+
{ action: 'release' },
|
|
409
|
+
]);
|
|
410
|
+
}
|
|
346
411
|
xRemaining -= xRight - xLeft;
|
|
347
412
|
}
|
|
348
|
-
const yPadding = Math.max(Math.floor(effectiveRegion.height * 0.
|
|
413
|
+
const yPadding = Math.max(Math.floor(effectiveRegion.height * 0.07), touchPadding);
|
|
349
414
|
const xTrack = Math.floor(effectiveRegion.x + 5); // a little bit off left border
|
|
350
415
|
const yBottom = effectiveRegion.y + effectiveRegion.height - yPadding;
|
|
351
416
|
const yDirection = remainingOffset.y > 0 ? 'down' : 'up';
|
|
352
417
|
const yGap = yDirection === 'down' ? -touchPadding : touchPadding;
|
|
353
418
|
let yRemaining = Math.abs(remainingOffset.y);
|
|
419
|
+
if (isPager) {
|
|
420
|
+
const yPages = Math.floor(yRemaining / effectiveRegion.height);
|
|
421
|
+
yRemaining = (effectiveRegion.height - yPadding * 2) * yPages;
|
|
422
|
+
}
|
|
354
423
|
while (yRemaining > 0) {
|
|
355
424
|
const yTop = Math.max(yBottom - yRemaining, effectiveRegion.y + yPadding);
|
|
356
425
|
const [yStart, yEnd] = yDirection === 'down' ? [yBottom, yTop] : [yTop, yBottom];
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
426
|
+
if (isPager) {
|
|
427
|
+
actions.push([
|
|
428
|
+
{ action: 'press', x: xTrack, y: yStart },
|
|
429
|
+
// scroll through the page
|
|
430
|
+
{ action: 'wait', ms: 170 },
|
|
431
|
+
{ action: 'moveTo', x: xTrack, y: yEnd },
|
|
432
|
+
{ action: 'release' },
|
|
433
|
+
]);
|
|
434
|
+
}
|
|
435
|
+
else {
|
|
436
|
+
actions.push([
|
|
437
|
+
{ action: 'press', x: xTrack, y: yStart },
|
|
438
|
+
// move through scrolling gap (actual scrolling will be triggered only after that)
|
|
439
|
+
{ action: 'wait', ms: 100 },
|
|
440
|
+
{ action: 'moveTo', x: xTrack, y: yStart + yGap },
|
|
441
|
+
// perform actual scrolling
|
|
442
|
+
{ action: 'wait', ms: 100 },
|
|
443
|
+
{ action: 'moveTo', x: xTrack, y: yEnd + yGap },
|
|
444
|
+
// prevent inertial scrolling after release
|
|
445
|
+
{ action: 'wait', ms: 100 },
|
|
446
|
+
{ action: 'moveTo', x: xTrack + 1, y: yEnd + yGap },
|
|
447
|
+
{ action: 'release' },
|
|
448
|
+
]);
|
|
449
|
+
}
|
|
367
450
|
yRemaining -= yBottom - yTop;
|
|
368
451
|
}
|
|
369
452
|
// ios actions should be executed one-by-one sequentially, otherwise the result isn't stable
|
|
370
|
-
|
|
453
|
+
// pages should be scrolled one-by-one as well
|
|
454
|
+
if (isPager || this.driver.isIOS) {
|
|
371
455
|
for (const action of actions) {
|
|
372
456
|
await this._spec.performAction(this.driver.target, action);
|
|
373
457
|
}
|
|
374
458
|
}
|
|
375
|
-
else {
|
|
459
|
+
else if (actions.length > 0) {
|
|
376
460
|
await this._spec.performAction(this.driver.target, [].concat(...actions));
|
|
377
461
|
}
|
|
378
462
|
const actualScrollableRegion = await this.getClientRegion();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@applitools/driver",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.0",
|
|
4
4
|
"description": "Applitools universal framework wrapper",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"applitools",
|
|
@@ -73,13 +73,13 @@
|
|
|
73
73
|
}
|
|
74
74
|
},
|
|
75
75
|
"dependencies": {
|
|
76
|
-
"@applitools/logger": "1.1.
|
|
76
|
+
"@applitools/logger": "1.1.10",
|
|
77
77
|
"@applitools/snippets": "2.2.3",
|
|
78
|
-
"@applitools/types": "1.4.
|
|
79
|
-
"@applitools/utils": "1.3.
|
|
78
|
+
"@applitools/types": "1.4.7",
|
|
79
|
+
"@applitools/utils": "1.3.6"
|
|
80
80
|
},
|
|
81
81
|
"devDependencies": {
|
|
82
|
-
"@applitools/bongo": "^2.1.
|
|
82
|
+
"@applitools/bongo": "^2.1.4",
|
|
83
83
|
"@types/mocha": "^9.1.1",
|
|
84
84
|
"@types/node": "^17.0.31",
|
|
85
85
|
"@typescript-eslint/eslint-plugin": "^5.22.0",
|
package/types/element.d.ts
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import type * as types from '@applitools/types';
|
|
2
2
|
import { type Logger } from '@applitools/logger';
|
|
3
3
|
import { type Context } from './context';
|
|
4
|
-
export declare type ElementState = {
|
|
4
|
+
export declare type ElementState<TElement> = {
|
|
5
5
|
contentSize?: types.Size;
|
|
6
6
|
scrollOffset?: types.Location;
|
|
7
7
|
transforms?: any;
|
|
8
|
+
attributes?: Record<string, string>;
|
|
9
|
+
touchPadding?: number;
|
|
10
|
+
containedElements?: Map<TElement, boolean>;
|
|
8
11
|
};
|
|
9
12
|
export declare class Element<TDriver, TContext, TElement, TSelector> {
|
|
10
13
|
private _target;
|
|
@@ -13,7 +16,6 @@ export declare class Element<TDriver, TContext, TElement, TSelector> {
|
|
|
13
16
|
private _index;
|
|
14
17
|
private _state;
|
|
15
18
|
private _originalOverflow;
|
|
16
|
-
private _touchPadding;
|
|
17
19
|
private _logger;
|
|
18
20
|
protected readonly _spec: types.SpecDriver<TDriver, TContext, TElement, TSelector>;
|
|
19
21
|
constructor(options: {
|
|
@@ -36,21 +38,24 @@ export declare class Element<TDriver, TContext, TElement, TSelector> {
|
|
|
36
38
|
getRegion(): Promise<types.Region>;
|
|
37
39
|
getClientRegion(): Promise<types.Region>;
|
|
38
40
|
getContentSize(): Promise<types.Size>;
|
|
41
|
+
isPager(): Promise<boolean>;
|
|
39
42
|
isScrollable(): Promise<boolean>;
|
|
40
43
|
isRoot(): Promise<boolean>;
|
|
41
44
|
getTouchPadding(): Promise<number>;
|
|
42
45
|
getText(): Promise<string>;
|
|
43
46
|
getAttribute(name: string): Promise<string>;
|
|
44
47
|
setAttribute(name: string, value: string): Promise<void>;
|
|
45
|
-
scrollTo(offset: types.Location
|
|
48
|
+
scrollTo(offset: types.Location, options?: {
|
|
49
|
+
force: boolean;
|
|
50
|
+
}): Promise<types.Location>;
|
|
46
51
|
translateTo(offset: types.Location): Promise<types.Location>;
|
|
47
52
|
getScrollOffset(): Promise<types.Location>;
|
|
48
53
|
getTranslateOffset(): Promise<types.Location>;
|
|
49
54
|
getInnerOffset(): Promise<types.Location>;
|
|
50
55
|
click(): Promise<void>;
|
|
51
56
|
type(value: string): Promise<void>;
|
|
52
|
-
preserveState(): Promise<ElementState
|
|
53
|
-
restoreState(state?: ElementState): Promise<void>;
|
|
57
|
+
preserveState(): Promise<ElementState<TElement>>;
|
|
58
|
+
restoreState(state?: ElementState<TElement>): Promise<void>;
|
|
54
59
|
hideScrollbars(): Promise<void>;
|
|
55
60
|
restoreScrollbars(): Promise<void>;
|
|
56
61
|
refresh(freshElement?: TElement): Promise<boolean>;
|