appium-xcuitest-driver 10.12.1 → 10.13.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/CHANGELOG.md +12 -0
- package/build/lib/commands/context.d.ts +130 -161
- package/build/lib/commands/context.d.ts.map +1 -1
- package/build/lib/commands/context.js +122 -107
- package/build/lib/commands/context.js.map +1 -1
- package/build/lib/commands/execute.js +1 -1
- package/build/lib/commands/execute.js.map +1 -1
- package/build/lib/commands/general.js +1 -1
- package/build/lib/commands/general.js.map +1 -1
- package/build/lib/commands/gesture.d.ts +103 -119
- package/build/lib/commands/gesture.d.ts.map +1 -1
- package/build/lib/commands/gesture.js +98 -138
- package/build/lib/commands/gesture.js.map +1 -1
- package/build/lib/commands/pcap.d.ts +1 -1
- package/build/lib/commands/pcap.d.ts.map +1 -1
- package/build/lib/commands/performance.d.ts +1 -1
- package/build/lib/commands/performance.d.ts.map +1 -1
- package/build/lib/commands/record-audio.d.ts +2 -1
- package/build/lib/commands/record-audio.d.ts.map +1 -1
- package/build/lib/commands/recordscreen.d.ts +2 -1
- package/build/lib/commands/recordscreen.d.ts.map +1 -1
- package/build/lib/commands/screenshots.d.ts.map +1 -1
- package/build/lib/commands/screenshots.js +3 -5
- package/build/lib/commands/screenshots.js.map +1 -1
- package/build/lib/commands/timeouts.js +1 -1
- package/build/lib/commands/timeouts.js.map +1 -1
- package/build/lib/commands/web.d.ts +199 -202
- package/build/lib/commands/web.d.ts.map +1 -1
- package/build/lib/commands/web.js +206 -174
- package/build/lib/commands/web.js.map +1 -1
- package/build/lib/driver.d.ts +2 -1
- package/build/lib/driver.d.ts.map +1 -1
- package/build/lib/driver.js +10 -4
- package/build/lib/driver.js.map +1 -1
- package/build/lib/execute-method-map.d.ts.map +1 -1
- package/build/lib/execute-method-map.js +0 -1
- package/build/lib/execute-method-map.js.map +1 -1
- package/lib/commands/{context.js → context.ts} +172 -145
- package/lib/commands/execute.js +1 -1
- package/lib/commands/general.js +1 -1
- package/lib/commands/{gesture.js → gesture.ts} +225 -183
- package/lib/commands/screenshots.js +3 -5
- package/lib/commands/timeouts.js +1 -1
- package/lib/commands/{web.js → web.ts} +305 -263
- package/lib/driver.ts +11 -4
- package/lib/execute-method-map.ts +0 -1
- package/npm-shrinkwrap.json +14 -89
- package/package.json +2 -2
|
@@ -74,6 +74,7 @@ const support_1 = require("appium/support");
|
|
|
74
74
|
const asyncbox_1 = require("asyncbox");
|
|
75
75
|
const bluebird_1 = __importStar(require("bluebird"));
|
|
76
76
|
const lodash_1 = __importDefault(require("lodash"));
|
|
77
|
+
const utils_1 = require("../utils");
|
|
77
78
|
const IPHONE_TOP_BAR_HEIGHT = 71;
|
|
78
79
|
const IPHONE_SCROLLED_TOP_BAR_HEIGHT = 41;
|
|
79
80
|
const IPHONE_X_SCROLLED_OFFSET = 55;
|
|
@@ -113,10 +114,12 @@ const TAB_BAR_POSITION_TOP = 'top';
|
|
|
113
114
|
const TAB_BAR_POSITION_BOTTOM = 'bottom';
|
|
114
115
|
const TAB_BAR_POSSITIONS = [TAB_BAR_POSITION_TOP, TAB_BAR_POSITION_BOTTOM];
|
|
115
116
|
/**
|
|
116
|
-
*
|
|
117
|
+
* Sets the current web frame context.
|
|
118
|
+
*
|
|
119
|
+
* @param frame - Frame identifier (number, string, or null to return to default content)
|
|
117
120
|
* @group Mobile Web Only
|
|
118
|
-
* @
|
|
119
|
-
* @
|
|
121
|
+
* @throws {errors.NotImplementedError} If not in a web context
|
|
122
|
+
* @throws {errors.NoSuchFrameError} If the specified frame is not found
|
|
120
123
|
*/
|
|
121
124
|
async function setFrame(frame) {
|
|
122
125
|
if (!this.isWebContext()) {
|
|
@@ -144,11 +147,12 @@ async function setFrame(frame) {
|
|
|
144
147
|
}
|
|
145
148
|
}
|
|
146
149
|
/**
|
|
147
|
-
*
|
|
150
|
+
* Gets the value of a CSS property for an element.
|
|
151
|
+
*
|
|
152
|
+
* @param propertyName - Name of the CSS property
|
|
153
|
+
* @param el - Element to get the property from
|
|
148
154
|
* @group Mobile Web Only
|
|
149
|
-
* @
|
|
150
|
-
* @param {Element | string} el
|
|
151
|
-
* @returns {Promise<string>}
|
|
155
|
+
* @throws {errors.NotImplementedError} If not in a web context
|
|
152
156
|
*/
|
|
153
157
|
async function getCssProperty(propertyName, el) {
|
|
154
158
|
if (!this.isWebContext()) {
|
|
@@ -158,11 +162,11 @@ async function getCssProperty(propertyName, el) {
|
|
|
158
162
|
return await this.executeAtom('get_value_of_css_property', [atomsElement, propertyName]);
|
|
159
163
|
}
|
|
160
164
|
/**
|
|
161
|
-
*
|
|
165
|
+
* Submits the form that contains the specified element.
|
|
162
166
|
*
|
|
163
|
-
* @param
|
|
167
|
+
* @param el - The element ID or element object
|
|
164
168
|
* @group Mobile Web Only
|
|
165
|
-
* @
|
|
169
|
+
* @throws {errors.NotImplementedError} If not in a web context
|
|
166
170
|
*/
|
|
167
171
|
async function submit(el) {
|
|
168
172
|
if (!this.isWebContext()) {
|
|
@@ -172,48 +176,55 @@ async function submit(el) {
|
|
|
172
176
|
await this.executeAtom('submit', [atomsElement]);
|
|
173
177
|
}
|
|
174
178
|
/**
|
|
175
|
-
*
|
|
179
|
+
* Refreshes the current page.
|
|
180
|
+
*
|
|
176
181
|
* @group Mobile Web Only
|
|
182
|
+
* @throws {errors.NotImplementedError} If not in a web context
|
|
177
183
|
*/
|
|
178
184
|
async function refresh() {
|
|
179
185
|
if (!this.isWebContext()) {
|
|
180
186
|
throw new driver_1.errors.NotImplementedError();
|
|
181
187
|
}
|
|
182
|
-
await
|
|
188
|
+
await this.remote.execute('window.location.reload()');
|
|
183
189
|
}
|
|
184
190
|
/**
|
|
185
|
-
*
|
|
191
|
+
* Gets the current page URL.
|
|
192
|
+
*
|
|
186
193
|
* @group Mobile Web Only
|
|
187
|
-
* @
|
|
194
|
+
* @throws {errors.NotImplementedError} If not in a web context
|
|
188
195
|
*/
|
|
189
196
|
async function getUrl() {
|
|
190
197
|
if (!this.isWebContext()) {
|
|
191
198
|
throw new driver_1.errors.NotImplementedError();
|
|
192
199
|
}
|
|
193
|
-
return await
|
|
200
|
+
return await this.remote.execute('window.location.href');
|
|
194
201
|
}
|
|
195
202
|
/**
|
|
196
|
-
*
|
|
203
|
+
* Gets the current page title.
|
|
204
|
+
*
|
|
197
205
|
* @group Mobile Web Only
|
|
198
|
-
* @
|
|
206
|
+
* @throws {errors.NotImplementedError} If not in a web context
|
|
199
207
|
*/
|
|
200
208
|
async function title() {
|
|
201
209
|
if (!this.isWebContext()) {
|
|
202
210
|
throw new driver_1.errors.NotImplementedError();
|
|
203
211
|
}
|
|
204
|
-
return await
|
|
212
|
+
return await this.remote.execute('window.document.title');
|
|
205
213
|
}
|
|
206
214
|
/**
|
|
207
|
-
*
|
|
215
|
+
* Gets all cookies for the current page.
|
|
216
|
+
*
|
|
217
|
+
* Cookie values are automatically URI-decoded.
|
|
218
|
+
*
|
|
208
219
|
* @group Mobile Web Only
|
|
209
|
-
* @
|
|
220
|
+
* @throws {errors.NotImplementedError} If not in a web context
|
|
210
221
|
*/
|
|
211
222
|
async function getCookies() {
|
|
212
223
|
if (!this.isWebContext()) {
|
|
213
224
|
throw new driver_1.errors.NotImplementedError();
|
|
214
225
|
}
|
|
215
226
|
// get the cookies from the remote debugger, or an empty object
|
|
216
|
-
const { cookies } = await
|
|
227
|
+
const { cookies } = await this.remote.getCookies();
|
|
217
228
|
// the value is URI encoded, so decode it safely
|
|
218
229
|
return cookies.map((cookie) => {
|
|
219
230
|
if (!lodash_1.default.isEmpty(cookie.value)) {
|
|
@@ -230,10 +241,13 @@ async function getCookies() {
|
|
|
230
241
|
});
|
|
231
242
|
}
|
|
232
243
|
/**
|
|
233
|
-
*
|
|
244
|
+
* Sets a cookie for the current page.
|
|
245
|
+
*
|
|
246
|
+
* If the cookie's path is not specified, it defaults to '/'.
|
|
247
|
+
*
|
|
248
|
+
* @param cookie - Cookie object to set
|
|
234
249
|
* @group Mobile Web Only
|
|
235
|
-
* @
|
|
236
|
-
* @returns {Promise<void>}
|
|
250
|
+
* @throws {errors.NotImplementedError} If not in a web context
|
|
237
251
|
*/
|
|
238
252
|
async function setCookie(cookie) {
|
|
239
253
|
if (!this.isWebContext()) {
|
|
@@ -257,10 +271,13 @@ async function setCookie(cookie) {
|
|
|
257
271
|
await this.executeAtom('execute_script', [script, []]);
|
|
258
272
|
}
|
|
259
273
|
/**
|
|
260
|
-
*
|
|
261
|
-
*
|
|
262
|
-
*
|
|
274
|
+
* Deletes a cookie by name.
|
|
275
|
+
*
|
|
276
|
+
* If the cookie is not found, the operation is silently ignored.
|
|
277
|
+
*
|
|
278
|
+
* @param cookieName - Name of the cookie to delete
|
|
263
279
|
* @group Mobile Web Only
|
|
280
|
+
* @throws {errors.NotImplementedError} If not in a web context
|
|
264
281
|
*/
|
|
265
282
|
async function deleteCookie(cookieName) {
|
|
266
283
|
if (!this.isWebContext()) {
|
|
@@ -275,9 +292,10 @@ async function deleteCookie(cookieName) {
|
|
|
275
292
|
await _deleteCookie.bind(this)(cookie);
|
|
276
293
|
}
|
|
277
294
|
/**
|
|
278
|
-
*
|
|
295
|
+
* Deletes all cookies for the current page.
|
|
296
|
+
*
|
|
279
297
|
* @group Mobile Web Only
|
|
280
|
-
* @
|
|
298
|
+
* @throws {errors.NotImplementedError} If not in a web context
|
|
281
299
|
*/
|
|
282
300
|
async function deleteCookies() {
|
|
283
301
|
if (!this.isWebContext()) {
|
|
@@ -287,9 +305,10 @@ async function deleteCookies() {
|
|
|
287
305
|
await bluebird_1.default.all(cookies.map((cookie) => _deleteCookie.bind(this)(cookie)));
|
|
288
306
|
}
|
|
289
307
|
/**
|
|
290
|
-
*
|
|
291
|
-
*
|
|
292
|
-
* @
|
|
308
|
+
* Caches a web element for later use.
|
|
309
|
+
*
|
|
310
|
+
* @param el - Element to cache
|
|
311
|
+
* @returns The cached element wrapper
|
|
293
312
|
*/
|
|
294
313
|
function cacheWebElement(el) {
|
|
295
314
|
if (!lodash_1.default.isPlainObject(el)) {
|
|
@@ -306,17 +325,18 @@ function cacheWebElement(el) {
|
|
|
306
325
|
return support_1.util.wrapElement(cacheId);
|
|
307
326
|
}
|
|
308
327
|
/**
|
|
309
|
-
*
|
|
310
|
-
*
|
|
311
|
-
* @
|
|
328
|
+
* Recursively caches all web elements in a response object.
|
|
329
|
+
*
|
|
330
|
+
* @param response - Response object that may contain web elements
|
|
331
|
+
* @returns Response with cached element wrappers
|
|
312
332
|
*/
|
|
313
333
|
function cacheWebElements(response) {
|
|
314
|
-
const toCached = (
|
|
334
|
+
const toCached = (v) => (lodash_1.default.isArray(v) || lodash_1.default.isPlainObject(v)) ? this.cacheWebElements(v) : v;
|
|
315
335
|
if (lodash_1.default.isArray(response)) {
|
|
316
336
|
return response.map(toCached);
|
|
317
337
|
}
|
|
318
338
|
else if (lodash_1.default.isPlainObject(response)) {
|
|
319
|
-
const result = { ...response, ...
|
|
339
|
+
const result = { ...response, ...this.cacheWebElement(response) };
|
|
320
340
|
return lodash_1.default.toPairs(result).reduce((acc, [key, value]) => {
|
|
321
341
|
acc[key] = toCached(value);
|
|
322
342
|
return acc;
|
|
@@ -325,35 +345,39 @@ function cacheWebElements(response) {
|
|
|
325
345
|
return response;
|
|
326
346
|
}
|
|
327
347
|
/**
|
|
328
|
-
*
|
|
329
|
-
*
|
|
330
|
-
* @
|
|
348
|
+
* Executes a Selenium atom script in the current web context.
|
|
349
|
+
*
|
|
350
|
+
* @param atom - Name of the atom to execute
|
|
351
|
+
* @param args - Arguments to pass to the atom
|
|
352
|
+
* @param alwaysDefaultFrame - If true, always use the default frame instead of current frames
|
|
331
353
|
* @privateRemarks This should return `Promise<T>` where `T` extends `unknown`, but that's going to cause a lot of things to break.
|
|
332
|
-
* @this {XCUITestDriver}
|
|
333
354
|
*/
|
|
334
355
|
async function executeAtom(atom, args, alwaysDefaultFrame = false) {
|
|
335
|
-
|
|
336
|
-
|
|
356
|
+
const frames = alwaysDefaultFrame === true ? [] : this.curWebFrames;
|
|
357
|
+
const promise = this.remote.executeAtom(atom, args, frames);
|
|
337
358
|
return await this.waitForAtom(promise);
|
|
338
359
|
}
|
|
339
360
|
/**
|
|
340
|
-
*
|
|
341
|
-
*
|
|
342
|
-
* @param
|
|
361
|
+
* Executes a Selenium atom script asynchronously.
|
|
362
|
+
*
|
|
363
|
+
* @param atom - Name of the atom to execute
|
|
364
|
+
* @param args - Arguments to pass to the atom
|
|
343
365
|
*/
|
|
344
366
|
async function executeAtomAsync(atom, args) {
|
|
345
367
|
// save the resolve and reject methods of the promise to be waited for
|
|
346
|
-
|
|
368
|
+
const promise = new bluebird_1.default((resolve, reject) => {
|
|
347
369
|
this.asyncPromise = { resolve, reject };
|
|
348
370
|
});
|
|
349
|
-
await
|
|
371
|
+
await this.remote.executeAtomAsync(atom, args, this.curWebFrames);
|
|
350
372
|
return await this.waitForAtom(promise);
|
|
351
373
|
}
|
|
352
374
|
/**
|
|
353
|
-
*
|
|
354
|
-
*
|
|
355
|
-
* @
|
|
356
|
-
* @
|
|
375
|
+
* Gets the atoms-compatible element representation.
|
|
376
|
+
*
|
|
377
|
+
* @template S - Element identifier type
|
|
378
|
+
* @param elOrId - Element or element ID
|
|
379
|
+
* @returns Atoms-compatible element object
|
|
380
|
+
* @throws {errors.StaleElementReferenceError} If the element is not in the cache
|
|
357
381
|
*/
|
|
358
382
|
function getAtomsElement(elOrId) {
|
|
359
383
|
const elId = support_1.util.unwrapElement(elOrId);
|
|
@@ -363,8 +387,10 @@ function getAtomsElement(elOrId) {
|
|
|
363
387
|
return { ELEMENT: this.webElementsCache.get(elId) };
|
|
364
388
|
}
|
|
365
389
|
/**
|
|
366
|
-
*
|
|
367
|
-
*
|
|
390
|
+
* Converts elements in an argument array to atoms-compatible format.
|
|
391
|
+
*
|
|
392
|
+
* @param args - Array of arguments that may contain elements
|
|
393
|
+
* @returns Array with elements converted to atoms format
|
|
368
394
|
*/
|
|
369
395
|
function convertElementsForAtoms(args = []) {
|
|
370
396
|
return args.map((arg) => {
|
|
@@ -383,28 +409,33 @@ function convertElementsForAtoms(args = []) {
|
|
|
383
409
|
});
|
|
384
410
|
}
|
|
385
411
|
/**
|
|
412
|
+
* Extracts the element ID from an element object.
|
|
386
413
|
*
|
|
387
|
-
* @param
|
|
388
|
-
* @returns
|
|
414
|
+
* @param element - Element object
|
|
415
|
+
* @returns Element ID if found, undefined otherwise
|
|
389
416
|
*/
|
|
390
417
|
function getElementId(element) {
|
|
391
418
|
return element?.ELEMENT || element?.[W3C_WEB_ELEMENT_IDENTIFIER];
|
|
392
419
|
}
|
|
393
420
|
/**
|
|
394
|
-
*
|
|
395
|
-
*
|
|
421
|
+
* Checks if an object has an element ID (type guard).
|
|
422
|
+
*
|
|
423
|
+
* @param element - Object to check
|
|
424
|
+
* @returns True if the object has an element ID
|
|
396
425
|
*/
|
|
397
426
|
function hasElementId(element) {
|
|
398
427
|
return (support_1.util.hasValue(element) &&
|
|
399
428
|
(support_1.util.hasValue(element.ELEMENT) || support_1.util.hasValue(element[W3C_WEB_ELEMENT_IDENTIFIER])));
|
|
400
429
|
}
|
|
401
430
|
/**
|
|
402
|
-
*
|
|
403
|
-
*
|
|
404
|
-
* @param
|
|
405
|
-
* @param
|
|
406
|
-
* @param
|
|
407
|
-
* @
|
|
431
|
+
* Finds one or more web elements using the specified strategy.
|
|
432
|
+
*
|
|
433
|
+
* @param strategy - Locator strategy (e.g., 'id', 'css selector')
|
|
434
|
+
* @param selector - Selector value
|
|
435
|
+
* @param many - If true, returns array of elements; if false, returns single element
|
|
436
|
+
* @param ctx - Optional context element to search within
|
|
437
|
+
* @returns Element or array of elements
|
|
438
|
+
* @throws {errors.NoSuchElementError} If element not found and many is false
|
|
408
439
|
*/
|
|
409
440
|
async function findWebElementOrElements(strategy, selector, many, ctx) {
|
|
410
441
|
const contextElement = lodash_1.default.isNil(ctx) ? null : this.getAtomsElement(ctx);
|
|
@@ -435,24 +466,30 @@ async function findWebElementOrElements(strategy, selector, many, ctx) {
|
|
|
435
466
|
return this.cacheWebElements(element);
|
|
436
467
|
}
|
|
437
468
|
/**
|
|
438
|
-
*
|
|
439
|
-
*
|
|
440
|
-
*
|
|
469
|
+
* Clicks at the specified web coordinates.
|
|
470
|
+
*
|
|
471
|
+
* Coordinates are automatically translated from web to native coordinates.
|
|
472
|
+
*
|
|
473
|
+
* @param x - X coordinate in web space
|
|
474
|
+
* @param y - Y coordinate in web space
|
|
441
475
|
*/
|
|
442
476
|
async function clickWebCoords(x, y) {
|
|
443
477
|
const { x: translatedX, y: translatedY } = await this.translateWebCoords(x, y);
|
|
444
478
|
await this.mobileTap(translatedX, translatedY);
|
|
445
479
|
}
|
|
446
480
|
/**
|
|
447
|
-
*
|
|
448
|
-
*
|
|
481
|
+
* Determines if the current Safari session is running on an iPhone.
|
|
482
|
+
*
|
|
483
|
+
* The result is cached after the first call.
|
|
484
|
+
*
|
|
485
|
+
* @returns True if running on iPhone, false otherwise
|
|
449
486
|
*/
|
|
450
487
|
async function getSafariIsIphone() {
|
|
451
488
|
if (lodash_1.default.isBoolean(this._isSafariIphone)) {
|
|
452
489
|
return this._isSafariIphone;
|
|
453
490
|
}
|
|
454
491
|
try {
|
|
455
|
-
const userAgent =
|
|
492
|
+
const userAgent = await this.execute('return navigator.userAgent');
|
|
456
493
|
this._isSafariIphone = userAgent.toLowerCase().includes('iphone');
|
|
457
494
|
}
|
|
458
495
|
catch (err) {
|
|
@@ -462,12 +499,15 @@ async function getSafariIsIphone() {
|
|
|
462
499
|
return this._isSafariIphone ?? true;
|
|
463
500
|
}
|
|
464
501
|
/**
|
|
465
|
-
*
|
|
466
|
-
*
|
|
502
|
+
* Gets the device size from Safari's perspective.
|
|
503
|
+
*
|
|
504
|
+
* Returns normalized dimensions (width <= height).
|
|
505
|
+
*
|
|
506
|
+
* @returns Device size with width and height
|
|
467
507
|
*/
|
|
468
508
|
async function getSafariDeviceSize() {
|
|
469
509
|
const script = 'return {height: window.screen.availHeight * window.devicePixelRatio, width: window.screen.availWidth * window.devicePixelRatio};';
|
|
470
|
-
const { width, height } =
|
|
510
|
+
const { width, height } = await this.execute(script);
|
|
471
511
|
const [normHeight, normWidth] = height > width ? [height, width] : [width, height];
|
|
472
512
|
return {
|
|
473
513
|
width: normWidth,
|
|
@@ -475,8 +515,11 @@ async function getSafariDeviceSize() {
|
|
|
475
515
|
};
|
|
476
516
|
}
|
|
477
517
|
/**
|
|
478
|
-
*
|
|
479
|
-
*
|
|
518
|
+
* Determines if the current device has a notch (iPhone X and later).
|
|
519
|
+
*
|
|
520
|
+
* The result is cached after the first call.
|
|
521
|
+
*
|
|
522
|
+
* @returns True if device has a notch, false otherwise
|
|
480
523
|
*/
|
|
481
524
|
async function getSafariIsNotched() {
|
|
482
525
|
if (lodash_1.default.isBoolean(this._isSafariNotched)) {
|
|
@@ -497,7 +540,14 @@ async function getSafariIsNotched() {
|
|
|
497
540
|
return this._isSafariNotched ?? false;
|
|
498
541
|
}
|
|
499
542
|
/**
|
|
500
|
-
*
|
|
543
|
+
* Calculates and applies extra offset for web coordinate translation.
|
|
544
|
+
*
|
|
545
|
+
* Takes into account Safari UI elements like tab bars, smart app banners, and device notches.
|
|
546
|
+
* Modifies wvPos and realDims in place.
|
|
547
|
+
*
|
|
548
|
+
* @param wvPos - WebView position object (modified in place)
|
|
549
|
+
* @param realDims - Real dimensions object (modified in place)
|
|
550
|
+
* @throws {errors.InvalidArgumentError} If Safari tab bar position is invalid
|
|
501
551
|
*/
|
|
502
552
|
async function getExtraTranslateWebCoordsOffset(wvPos, realDims) {
|
|
503
553
|
let topOffset = 0;
|
|
@@ -505,7 +555,7 @@ async function getExtraTranslateWebCoordsOffset(wvPos, realDims) {
|
|
|
505
555
|
const isIphone = await this.getSafariIsIphone();
|
|
506
556
|
// No need to check whether the Smart App Banner or Tab Bar is visible or not
|
|
507
557
|
// if already defined by nativeWebTapTabBarVisibility or nativeWebTapSmartAppBannerVisibility in settings.
|
|
508
|
-
const { nativeWebTapTabBarVisibility, nativeWebTapSmartAppBannerVisibility, safariTabBarPosition = support_1.util.compareVersions(
|
|
558
|
+
const { nativeWebTapTabBarVisibility, nativeWebTapSmartAppBannerVisibility, safariTabBarPosition = support_1.util.compareVersions(this.opts.platformVersion, '>=', '15.0') &&
|
|
509
559
|
isIphone
|
|
510
560
|
? TAB_BAR_POSITION_BOTTOM
|
|
511
561
|
: TAB_BAR_POSITION_TOP, } = this.settings.getSettings();
|
|
@@ -524,7 +574,7 @@ async function getExtraTranslateWebCoordsOffset(wvPos, realDims) {
|
|
|
524
574
|
const isNotched = isIphone && (await this.getSafariIsNotched());
|
|
525
575
|
const orientation = realDims.h > realDims.w ? 'PORTRAIT' : 'LANDSCAPE';
|
|
526
576
|
const notchOffset = isNotched
|
|
527
|
-
? support_1.util.compareVersions(
|
|
577
|
+
? support_1.util.compareVersions(this.opts.platformVersion, '=', '13.0')
|
|
528
578
|
? IPHONE_X_NOTCH_OFFSET_IOS_13
|
|
529
579
|
: IPHONE_X_NOTCH_OFFSET_IOS
|
|
530
580
|
: 0;
|
|
@@ -573,10 +623,11 @@ async function getExtraTranslateWebCoordsOffset(wvPos, realDims) {
|
|
|
573
623
|
realDims.h -= topOffset + bottomOffset;
|
|
574
624
|
}
|
|
575
625
|
/**
|
|
576
|
-
*
|
|
577
|
-
*
|
|
578
|
-
* @param
|
|
579
|
-
* @
|
|
626
|
+
* Calculates additional offset for native web tap based on smart app banner visibility.
|
|
627
|
+
*
|
|
628
|
+
* @param isIphone - Whether the device is an iPhone
|
|
629
|
+
* @param bannerVisibility - Banner visibility setting ('visible', 'invisible', or 'detect')
|
|
630
|
+
* @returns Additional offset in pixels
|
|
580
631
|
*/
|
|
581
632
|
async function getExtraNativeWebTapOffset(isIphone, bannerVisibility) {
|
|
582
633
|
let offset = 0;
|
|
@@ -587,7 +638,7 @@ async function getExtraNativeWebTapOffset(isIphone, bannerVisibility) {
|
|
|
587
638
|
}
|
|
588
639
|
else if (bannerVisibility === DETECT) {
|
|
589
640
|
// try to see if there is an Smart App Banner
|
|
590
|
-
const banners =
|
|
641
|
+
const banners = await this.findNativeElementOrElements('accessibility id', 'Close app download offer', true);
|
|
591
642
|
if (banners?.length) {
|
|
592
643
|
offset += isIphone
|
|
593
644
|
? IPHONE_WEB_COORD_SMART_APP_BANNER_OFFSET
|
|
@@ -598,9 +649,11 @@ async function getExtraNativeWebTapOffset(isIphone, bannerVisibility) {
|
|
|
598
649
|
return offset;
|
|
599
650
|
}
|
|
600
651
|
/**
|
|
601
|
-
*
|
|
602
|
-
*
|
|
603
|
-
*
|
|
652
|
+
* Performs a native tap on a web element.
|
|
653
|
+
*
|
|
654
|
+
* Attempts to use a simple native tap first, falling back to coordinate-based tapping if needed.
|
|
655
|
+
*
|
|
656
|
+
* @param el - Element to tap
|
|
604
657
|
*/
|
|
605
658
|
async function nativeWebTap(el) {
|
|
606
659
|
const atomsElement = this.getAtomsElement(el);
|
|
@@ -610,20 +663,23 @@ async function nativeWebTap(el) {
|
|
|
610
663
|
return;
|
|
611
664
|
}
|
|
612
665
|
this.log.warn('Unable to do simple native web tap. Attempting to convert coordinates');
|
|
613
|
-
const [size, coordinates] =
|
|
614
|
-
/** @type {[import('@appium/types').Size, import('@appium/types').Position]} */ (await bluebird_1.default.Promise.all([
|
|
666
|
+
const [size, coordinates] = await bluebird_1.default.Promise.all([
|
|
615
667
|
this.executeAtom('get_size', [atomsElement]),
|
|
616
668
|
this.executeAtom('get_top_left_coordinates', [atomsElement]),
|
|
617
|
-
])
|
|
669
|
+
]);
|
|
618
670
|
const { width, height } = size;
|
|
619
671
|
const { x, y } = coordinates;
|
|
620
672
|
await this.clickWebCoords(x + width / 2, y + height / 2);
|
|
621
673
|
}
|
|
622
674
|
/**
|
|
623
|
-
*
|
|
624
|
-
*
|
|
625
|
-
*
|
|
626
|
-
*
|
|
675
|
+
* Translates web coordinates to native screen coordinates.
|
|
676
|
+
*
|
|
677
|
+
* Uses calibration data if available, otherwise falls back to legacy algorithm.
|
|
678
|
+
*
|
|
679
|
+
* @param x - X coordinate in web space
|
|
680
|
+
* @param y - Y coordinate in web space
|
|
681
|
+
* @returns Translated position in native coordinates
|
|
682
|
+
* @throws {Error} If no WebView is found or if translation fails
|
|
627
683
|
*/
|
|
628
684
|
async function translateWebCoords(x, y) {
|
|
629
685
|
this.log.debug(`Translating web coordinates (${JSON.stringify({ x, y })}) to native coordinates`);
|
|
@@ -632,7 +688,7 @@ async function translateWebCoords(x, y) {
|
|
|
632
688
|
const { offsetX, offsetY, pixelRatioX, pixelRatioY } = this.webviewCalibrationResult;
|
|
633
689
|
const cmd = '(function () {return {innerWidth: window.innerWidth, innerHeight: window.innerHeight, ' +
|
|
634
690
|
'outerWidth: window.outerWidth, outerHeight: window.outerHeight}; })()';
|
|
635
|
-
const wvDims = await
|
|
691
|
+
const wvDims = await this.remote.execute(cmd);
|
|
636
692
|
// https://tripleodeon.com/2011/12/first-understand-your-screen/
|
|
637
693
|
const shouldApplyPixelRatio = wvDims.innerWidth > wvDims.outerWidth
|
|
638
694
|
|| wvDims.innerHeight > wvDims.outerHeight;
|
|
@@ -646,21 +702,20 @@ async function translateWebCoords(x, y) {
|
|
|
646
702
|
`Invoke 'mobile: calibrateWebToRealCoordinatesTranslation' to change that.`);
|
|
647
703
|
}
|
|
648
704
|
// absolutize web coords
|
|
649
|
-
/** @type {import('@appium/types').Element|undefined|string} */
|
|
650
705
|
let webview;
|
|
651
706
|
try {
|
|
652
|
-
webview =
|
|
707
|
+
webview = await (0, asyncbox_1.retryInterval)(5, 100, async () => await this.findNativeElementOrElements('class name', 'XCUIElementTypeWebView', false));
|
|
653
708
|
}
|
|
654
709
|
catch { }
|
|
655
710
|
if (!webview) {
|
|
656
711
|
throw new Error(`No WebView found. Unable to translate web coordinates for native web tap.`);
|
|
657
712
|
}
|
|
658
713
|
webview = support_1.util.unwrapElement(webview);
|
|
659
|
-
const rect =
|
|
714
|
+
const rect = await this.proxyCommand(`/element/${webview}/rect`, 'GET');
|
|
660
715
|
const wvPos = { x: rect.x, y: rect.y };
|
|
661
716
|
const realDims = { w: rect.width, h: rect.height };
|
|
662
717
|
const cmd = '(function () { return {w: window.innerWidth, h: window.innerHeight}; })()';
|
|
663
|
-
const wvDims = await
|
|
718
|
+
const wvDims = await this.remote.execute(cmd);
|
|
664
719
|
// keep track of implicit wait, and set locally to 0
|
|
665
720
|
// https://github.com/appium/appium/issues/14988
|
|
666
721
|
const implicitWaitMs = this.implicitWaitMs;
|
|
@@ -695,15 +750,20 @@ async function translateWebCoords(x, y) {
|
|
|
695
750
|
return newCoords;
|
|
696
751
|
}
|
|
697
752
|
/**
|
|
698
|
-
*
|
|
699
|
-
*
|
|
753
|
+
* Checks if an alert is currently present.
|
|
754
|
+
*
|
|
755
|
+
* @returns True if an alert is present, false otherwise
|
|
700
756
|
*/
|
|
701
757
|
async function checkForAlert() {
|
|
702
758
|
return lodash_1.default.isString(await this.getAlertText());
|
|
703
759
|
}
|
|
704
760
|
/**
|
|
705
|
-
*
|
|
706
|
-
*
|
|
761
|
+
* Waits for an atom promise to resolve, monitoring for alerts during execution.
|
|
762
|
+
*
|
|
763
|
+
* @param promise - Promise returned by atom execution
|
|
764
|
+
* @returns The result of the atom execution
|
|
765
|
+
* @throws {errors.UnexpectedAlertOpenError} If an alert appears during execution
|
|
766
|
+
* @throws {errors.TimeoutError} If the atom execution times out
|
|
707
767
|
*/
|
|
708
768
|
async function waitForAtom(promise) {
|
|
709
769
|
const timer = new support_1.timing.Timer().start();
|
|
@@ -775,21 +835,23 @@ async function waitForAtom(promise) {
|
|
|
775
835
|
}
|
|
776
836
|
}
|
|
777
837
|
/**
|
|
778
|
-
*
|
|
779
|
-
*
|
|
838
|
+
* Performs browser navigation (back, forward, etc.) using history API.
|
|
839
|
+
*
|
|
840
|
+
* @param navType - Navigation type (e.g., 'back', 'forward')
|
|
780
841
|
*/
|
|
781
842
|
async function mobileWebNav(navType) {
|
|
782
|
-
|
|
843
|
+
this.remote.allowNavigationWithoutReload = true;
|
|
783
844
|
try {
|
|
784
845
|
await this.executeAtom('execute_script', [`history.${navType}();`, null]);
|
|
785
846
|
}
|
|
786
847
|
finally {
|
|
787
|
-
|
|
848
|
+
this.remote.allowNavigationWithoutReload = false;
|
|
788
849
|
}
|
|
789
850
|
}
|
|
790
851
|
/**
|
|
791
|
-
*
|
|
792
|
-
*
|
|
852
|
+
* Gets the base URL for accessing WDA HTTP endpoints.
|
|
853
|
+
*
|
|
854
|
+
* @returns The base URL (e.g., 'http://127.0.0.1:8100')
|
|
793
855
|
*/
|
|
794
856
|
function getWdaLocalhostRoot() {
|
|
795
857
|
const remotePort = ((this.isRealDevice() ? this.opts.wdaRemotePort : null)
|
|
@@ -809,8 +871,8 @@ function getWdaLocalhostRoot() {
|
|
|
809
871
|
* The returned value could also be used to manually transform web coordinates
|
|
810
872
|
* to real devices ones in client scripts.
|
|
811
873
|
*
|
|
812
|
-
* @
|
|
813
|
-
* @
|
|
874
|
+
* @returns Calibration data with offset and pixel ratio information
|
|
875
|
+
* @throws {errors.NotImplementedError} If not in a web context
|
|
814
876
|
*/
|
|
815
877
|
async function mobileCalibrateWebToRealCoordinatesTranslation() {
|
|
816
878
|
if (!this.isWebContext()) {
|
|
@@ -818,12 +880,11 @@ async function mobileCalibrateWebToRealCoordinatesTranslation() {
|
|
|
818
880
|
}
|
|
819
881
|
const currentUrl = await this.getUrl();
|
|
820
882
|
await this.setUrl(`${this.getWdaLocalhostRoot()}/calibrate`);
|
|
821
|
-
const { width, height } =
|
|
883
|
+
const { width, height } = await this.proxyCommand('/window/rect', 'GET');
|
|
822
884
|
const [centerX, centerY] = [width / 2, height / 2];
|
|
823
885
|
const errorPrefix = 'Cannot determine web view coordinates offset. Are you in Safari context?';
|
|
824
|
-
const performCalibrationTap = async (
|
|
886
|
+
const performCalibrationTap = async (tapX, tapY) => {
|
|
825
887
|
await this.mobileTap(tapX, tapY);
|
|
826
|
-
/** @type {import('@appium/types').Position} */
|
|
827
888
|
let result;
|
|
828
889
|
try {
|
|
829
890
|
const title = await this.title();
|
|
@@ -855,30 +916,17 @@ async function mobileCalibrateWebToRealCoordinatesTranslation() {
|
|
|
855
916
|
// restore the previous url
|
|
856
917
|
await this.setUrl(currentUrl);
|
|
857
918
|
}
|
|
858
|
-
const result =
|
|
919
|
+
const result = this.webviewCalibrationResult;
|
|
859
920
|
return {
|
|
860
921
|
...result,
|
|
861
922
|
offsetX: Math.round(result.offsetX),
|
|
862
923
|
offsetY: Math.round(result.offsetY),
|
|
863
924
|
};
|
|
864
925
|
}
|
|
865
|
-
/**
|
|
866
|
-
* @typedef {Object} SafariOpts
|
|
867
|
-
* @property {object} preferences An object containing Safari settings to be updated.
|
|
868
|
-
* The list of available setting names and their values could be retrieved by
|
|
869
|
-
* changing the corresponding Safari settings in the UI and then inspecting
|
|
870
|
-
* 'Library/Preferences/com.apple.mobilesafari.plist' file inside of
|
|
871
|
-
* com.apple.mobilesafari app container.
|
|
872
|
-
* The full path to the Mobile Safari's container could be retrieved from
|
|
873
|
-
* `xcrun simctl get_app_container <sim_udid> com.apple.mobilesafari data`
|
|
874
|
-
* command output.
|
|
875
|
-
* Use the `xcrun simctl spawn <sim_udid> defaults read <path_to_plist>` command
|
|
876
|
-
* to print the plist content to the Terminal.
|
|
877
|
-
*/
|
|
878
926
|
/**
|
|
879
927
|
* Updates Mobile Safari preferences on an iOS Simulator
|
|
880
928
|
*
|
|
881
|
-
* @param
|
|
929
|
+
* @param preferences - An object containing Safari settings to be updated.
|
|
882
930
|
* The list of available setting names and their values can be retrieved by changing the
|
|
883
931
|
* corresponding Safari settings in the UI and then inspecting
|
|
884
932
|
* `Library/Preferences/com.apple.mobilesafari.plist` file inside of the `com.apple.mobilesafari`
|
|
@@ -888,29 +936,27 @@ async function mobileCalibrateWebToRealCoordinatesTranslation() {
|
|
|
888
936
|
* the plist content to the Terminal.
|
|
889
937
|
*
|
|
890
938
|
* @group Simulator Only
|
|
891
|
-
* @
|
|
892
|
-
* @throws {
|
|
893
|
-
* @this {XCUITestDriver}
|
|
939
|
+
* @throws {Error} If run on a real device
|
|
940
|
+
* @throws {errors.InvalidArgumentError} If the preferences argument is invalid
|
|
894
941
|
*/
|
|
895
942
|
async function mobileUpdateSafariPreferences(preferences) {
|
|
896
|
-
|
|
897
|
-
throw new Error('This extension is only available for Simulator');
|
|
898
|
-
}
|
|
943
|
+
const simulator = utils_1.assertSimulator.call(this, 'Updating Safari preferences');
|
|
899
944
|
if (!lodash_1.default.isPlainObject(preferences)) {
|
|
900
945
|
throw new driver_1.errors.InvalidArgumentError('"preferences" argument must be a valid object');
|
|
901
946
|
}
|
|
902
947
|
this.log.debug(`About to update Safari preferences: ${JSON.stringify(preferences)}`);
|
|
903
|
-
await
|
|
948
|
+
await simulator.updateSafariSettings(preferences);
|
|
904
949
|
}
|
|
905
950
|
/**
|
|
906
|
-
*
|
|
907
|
-
*
|
|
908
|
-
* @
|
|
951
|
+
* Generates a timeout error with detailed information about atom execution failure.
|
|
952
|
+
*
|
|
953
|
+
* @param timer - Timer instance to get duration from
|
|
954
|
+
* @returns Timeout error with descriptive message
|
|
909
955
|
*/
|
|
910
956
|
async function generateAtomTimeoutError(timer) {
|
|
911
957
|
let message = (`The remote Safari debugger did not respond to the requested ` +
|
|
912
958
|
`command after ${timer.getDuration().asMilliSeconds}ms. `);
|
|
913
|
-
message += (await this.
|
|
959
|
+
message += (await this._remote?.isJavascriptExecutionBlocked()) ? (`It appears that JavaScript execution is blocked, ` +
|
|
914
960
|
`which could be caused by either a modal dialog obstructing the current page, ` +
|
|
915
961
|
`or a JavaScript routine monopolizing the event loop.`) : (`However, the debugger still responds to JavaScript commands, ` +
|
|
916
962
|
`which suggests that the provided atom script is taking too long to execute.`);
|
|
@@ -921,9 +967,12 @@ async function generateAtomTimeoutError(timer) {
|
|
|
921
967
|
return new driver_1.errors.TimeoutError(message);
|
|
922
968
|
}
|
|
923
969
|
/**
|
|
924
|
-
*
|
|
925
|
-
*
|
|
926
|
-
*
|
|
970
|
+
* Attempts to tap a web element using native element matching.
|
|
971
|
+
*
|
|
972
|
+
* Tries to find a native element by matching text content, then taps it directly.
|
|
973
|
+
*
|
|
974
|
+
* @param atomsElement - Atoms-compatible element to tap
|
|
975
|
+
* @returns True if the native tap was successful, false otherwise
|
|
927
976
|
*/
|
|
928
977
|
async function tapWebElementNatively(atomsElement) {
|
|
929
978
|
// try to get the text of the element, which will be accessible in the
|
|
@@ -943,10 +992,10 @@ async function tapWebElementNatively(atomsElement) {
|
|
|
943
992
|
}
|
|
944
993
|
const el = els[0];
|
|
945
994
|
// use tap because on iOS 11.2 and below `nativeClick` crashes WDA
|
|
946
|
-
const rect =
|
|
995
|
+
const rect = await this.proxyCommand(`/element/${support_1.util.unwrapElement(el)}/rect`, 'GET');
|
|
947
996
|
if (els.length > 1) {
|
|
948
997
|
const el2 = els[1];
|
|
949
|
-
const rect2 =
|
|
998
|
+
const rect2 = await this.proxyCommand(`/element/${support_1.util.unwrapElement(el2)}/rect`, 'GET');
|
|
950
999
|
if (rect.x !== rect2.x || rect.y !== rect2.y
|
|
951
1000
|
|| rect.width !== rect2.width || rect.height !== rect2.height) {
|
|
952
1001
|
// These 2 native elements are not referring to the same web element
|
|
@@ -964,8 +1013,10 @@ async function tapWebElementNatively(atomsElement) {
|
|
|
964
1013
|
return false;
|
|
965
1014
|
}
|
|
966
1015
|
/**
|
|
967
|
-
*
|
|
968
|
-
*
|
|
1016
|
+
* Validates if a value is a valid element identifier.
|
|
1017
|
+
*
|
|
1018
|
+
* @param id - Value to validate
|
|
1019
|
+
* @returns True if the value is a valid element identifier
|
|
969
1020
|
*/
|
|
970
1021
|
function isValidElementIdentifier(id) {
|
|
971
1022
|
if (!lodash_1.default.isString(id) && !lodash_1.default.isNumber(id)) {
|
|
@@ -980,12 +1031,12 @@ function isValidElementIdentifier(id) {
|
|
|
980
1031
|
return true;
|
|
981
1032
|
}
|
|
982
1033
|
/**
|
|
983
|
-
* Creates a JavaScript
|
|
1034
|
+
* Creates a JavaScript cookie string.
|
|
984
1035
|
*
|
|
985
|
-
* @param
|
|
986
|
-
* @param
|
|
987
|
-
* @param
|
|
988
|
-
* @returns
|
|
1036
|
+
* @param key - Cookie name
|
|
1037
|
+
* @param value - Cookie value
|
|
1038
|
+
* @param options - Cookie options (expires, path, domain, secure, httpOnly)
|
|
1039
|
+
* @returns Cookie string suitable for document.cookie
|
|
989
1040
|
*/
|
|
990
1041
|
function createJSCookie(key, value, options = {}) {
|
|
991
1042
|
return [
|
|
@@ -999,31 +1050,12 @@ function createJSCookie(key, value, options = {}) {
|
|
|
999
1050
|
].join('');
|
|
1000
1051
|
}
|
|
1001
1052
|
/**
|
|
1002
|
-
*
|
|
1003
|
-
*
|
|
1004
|
-
* @
|
|
1053
|
+
* Deletes a cookie via the remote debugger.
|
|
1054
|
+
*
|
|
1055
|
+
* @param cookie - Cookie object to delete
|
|
1005
1056
|
*/
|
|
1006
1057
|
async function _deleteCookie(cookie) {
|
|
1007
1058
|
const url = `http${cookie.secure ? 's' : ''}://${cookie.domain}${cookie.path}`;
|
|
1008
|
-
return await
|
|
1059
|
+
return await this.remote.deleteCookie(cookie.name, url);
|
|
1009
1060
|
}
|
|
1010
|
-
/**
|
|
1011
|
-
* @typedef {Object} CookieOptions
|
|
1012
|
-
* @property {string} [expires]
|
|
1013
|
-
* @property {string} [path]
|
|
1014
|
-
* @property {string} [domain]
|
|
1015
|
-
* @property {boolean} [secure]
|
|
1016
|
-
* @property {boolean} [httpOnly]
|
|
1017
|
-
*/
|
|
1018
|
-
/**
|
|
1019
|
-
* @typedef {import('../driver').XCUITestDriver} XCUITestDriver
|
|
1020
|
-
* @typedef {import('@appium/types').Rect} Rect
|
|
1021
|
-
*/
|
|
1022
|
-
/**
|
|
1023
|
-
* @template {string} [S=string]
|
|
1024
|
-
* @typedef {import('@appium/types').Element<S>} Element
|
|
1025
|
-
*/
|
|
1026
|
-
/**
|
|
1027
|
-
* @typedef {import('appium-remote-debugger').RemoteDebugger} RemoteDebugger
|
|
1028
|
-
*/
|
|
1029
1061
|
//# sourceMappingURL=web.js.map
|