appium-xcuitest-driver 10.12.2 → 10.13.1

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.
Files changed (40) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/build/lib/commands/context.d.ts +130 -161
  3. package/build/lib/commands/context.d.ts.map +1 -1
  4. package/build/lib/commands/context.js +122 -107
  5. package/build/lib/commands/context.js.map +1 -1
  6. package/build/lib/commands/execute.js +1 -1
  7. package/build/lib/commands/execute.js.map +1 -1
  8. package/build/lib/commands/general.js +1 -1
  9. package/build/lib/commands/general.js.map +1 -1
  10. package/build/lib/commands/gesture.d.ts +103 -119
  11. package/build/lib/commands/gesture.d.ts.map +1 -1
  12. package/build/lib/commands/gesture.js +98 -138
  13. package/build/lib/commands/gesture.js.map +1 -1
  14. package/build/lib/commands/screenshots.d.ts.map +1 -1
  15. package/build/lib/commands/screenshots.js +3 -5
  16. package/build/lib/commands/screenshots.js.map +1 -1
  17. package/build/lib/commands/timeouts.js +1 -1
  18. package/build/lib/commands/timeouts.js.map +1 -1
  19. package/build/lib/commands/web.d.ts +199 -202
  20. package/build/lib/commands/web.d.ts.map +1 -1
  21. package/build/lib/commands/web.js +216 -175
  22. package/build/lib/commands/web.js.map +1 -1
  23. package/build/lib/driver.d.ts +2 -1
  24. package/build/lib/driver.d.ts.map +1 -1
  25. package/build/lib/driver.js +10 -4
  26. package/build/lib/driver.js.map +1 -1
  27. package/build/lib/execute-method-map.d.ts.map +1 -1
  28. package/build/lib/execute-method-map.js +0 -1
  29. package/build/lib/execute-method-map.js.map +1 -1
  30. package/lib/commands/{context.js → context.ts} +172 -145
  31. package/lib/commands/execute.js +1 -1
  32. package/lib/commands/general.js +1 -1
  33. package/lib/commands/{gesture.js → gesture.ts} +225 -183
  34. package/lib/commands/screenshots.js +3 -5
  35. package/lib/commands/timeouts.js +1 -1
  36. package/lib/commands/{web.js → web.ts} +314 -264
  37. package/lib/driver.ts +11 -4
  38. package/lib/execute-method-map.ts +0 -1
  39. package/npm-shrinkwrap.json +13 -43
  40. package/package.json +1 -1
@@ -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
- * @this {XCUITestDriver}
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
- * @param {number|string|null} frame
119
- * @returns {Promise<void>}
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
- * @this {XCUITestDriver}
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
- * @param {string} propertyName
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
- * Submit the form an element is in
165
+ * Submits the form that contains the specified element.
162
166
  *
163
- * @param {string|Element} el - the element ID
167
+ * @param el - The element ID or element object
164
168
  * @group Mobile Web Only
165
- * @this {XCUITestDriver}
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
- * @this {XCUITestDriver}
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 ( /** @type {RemoteDebugger} */(this.remote)).execute('window.location.reload()');
188
+ await this.remote.execute('window.location.reload()');
183
189
  }
184
190
  /**
185
- * @this {XCUITestDriver}
191
+ * Gets the current page URL.
192
+ *
186
193
  * @group Mobile Web Only
187
- * @returns {Promise<string>}
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 ( /** @type {RemoteDebugger} */(this.remote)).execute('window.location.href');
200
+ return await this.remote.execute('window.location.href');
194
201
  }
195
202
  /**
196
- * @this {XCUITestDriver}
203
+ * Gets the current page title.
204
+ *
197
205
  * @group Mobile Web Only
198
- * @returns {Promise<string>}
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 ( /** @type {RemoteDebugger} */(this.remote)).execute('window.document.title');
212
+ return await this.remote.execute('window.document.title');
205
213
  }
206
214
  /**
207
- * @this {XCUITestDriver}
215
+ * Gets all cookies for the current page.
216
+ *
217
+ * Cookie values are automatically URI-decoded.
218
+ *
208
219
  * @group Mobile Web Only
209
- * @returns {Promise<import('@appium/types').Cookie[]>}
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 ( /** @type {RemoteDebugger} */(this.remote)).getCookies();
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
- * @this {XCUITestDriver}
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
- * @param {import('@appium/types').Cookie} cookie
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
- * @this {XCUITestDriver}
261
- * @param {string} cookieName
262
- * @returns {Promise<void>}
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
- * @this {XCUITestDriver}
295
+ * Deletes all cookies for the current page.
296
+ *
279
297
  * @group Mobile Web Only
280
- * @returns {Promise<void>}
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
- * @this {XCUITestDriver}
291
- * @param {Element | string} el
292
- * @returns {Element | string}
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
- * @this {XCUITestDriver}
310
- * @param {any} response
311
- * @returns {any}
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 = (/** @type {any} */ v) => (lodash_1.default.isArray(v) || lodash_1.default.isPlainObject(v)) ? this.cacheWebElements(v) : v;
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, ...( /** @type {Element} */(this.cacheWebElement(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
- * @param {string} atom
329
- * @param {unknown[]} args
330
- * @returns {Promise<any>}
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
- let frames = alwaysDefaultFrame === true ? [] : this.curWebFrames;
336
- let promise = ( /** @type {RemoteDebugger} */(this.remote)).executeAtom(atom, args, frames);
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
- * @this {XCUITestDriver}
341
- * @param {string} atom
342
- * @param {any[]} args
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
- let promise = new bluebird_1.default((resolve, reject) => {
368
+ const promise = new bluebird_1.default((resolve, reject) => {
347
369
  this.asyncPromise = { resolve, reject };
348
370
  });
349
- await ( /** @type {RemoteDebugger} */(this.remote)).executeAtomAsync(atom, args, this.curWebFrames);
371
+ await this.remote.executeAtomAsync(atom, args, this.curWebFrames);
350
372
  return await this.waitForAtom(promise);
351
373
  }
352
374
  /**
353
- * @template {string} S
354
- * @param {S|Element<S>} elOrId
355
- * @returns {import('./types').AtomsElement<S>}
356
- * @this {XCUITestDriver}
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
- * @param {readonly any[]} [args]
367
- * @this {XCUITestDriver}
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 {any} element
388
- * @returns {string | undefined}
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
- * @param {any} element
395
- * @returns {element is Element}
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
- * @this {XCUITestDriver}
403
- * @param {string} strategy
404
- * @param {string} selector
405
- * @param {boolean} [many]
406
- * @param {Element | string | null} [ctx]
407
- * @returns {Promise<Element | Element[]>}
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
- * @this {XCUITestDriver}
439
- * @param {number} x
440
- * @param {number} y
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
- * @this {XCUITestDriver}
448
- * @returns {Promise<boolean>}
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 = /** @type {string} */ (await this.execute('return navigator.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
- * @this {XCUITestDriver}
466
- * @returns {Promise<import('@appium/types').Size>}
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 } = /** @type {import('@appium/types').Size} */ (await this.execute(script));
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
- * @this {XCUITestDriver}
479
- * @returns {Promise<boolean>}
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
- * @this {XCUITestDriver}
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(/** @type {string} */ (this.opts.platformVersion), '>=', '15.0') &&
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(/** @type {string} */ (this.opts.platformVersion), '=', '13.0')
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
- * @this {XCUITestDriver}
577
- * @param {boolean} isIphone
578
- * @param {string} bannerVisibility
579
- * @returns {Promise<number>}
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 = /** @type {import('@appium/types').Element[]} */ (await this.findNativeElementOrElements('accessibility id', 'Close app download offer', true));
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
- * @this {XCUITestDriver}
602
- * @param {any} el
603
- * @returns {Promise<void>}
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
- * @this {XCUITestDriver}
624
- * @param {number} x
625
- * @param {number} y
626
- * @returns {Promise<import('@appium/types').Position>}
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 ( /** @type {RemoteDebugger} */(this.remote)).execute(cmd);
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 = /** @type {import('@appium/types').Element|undefined} */ (await (0, asyncbox_1.retryInterval)(5, 100, async () => await this.findNativeElementOrElements('class name', 'XCUIElementTypeWebView', false)));
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 = /** @type {Rect} */ (await this.proxyCommand(`/element/${webview}/rect`, 'GET'));
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 ( /** @type {RemoteDebugger} */(this.remote)).execute(cmd);
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
- * @this {XCUITestDriver}
699
- * @returns {Promise<boolean>}
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
- * @param {Promise<any>} promise
706
- * @this {XCUITestDriver}
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,25 +835,36 @@ async function waitForAtom(promise) {
775
835
  }
776
836
  }
777
837
  /**
778
- * @param {string} navType
779
- * @this {XCUITestDriver}
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
- ( /** @type {RemoteDebugger} */(this.remote)).allowNavigationWithoutReload = true;
843
+ this.remote.allowNavigationWithoutReload = true;
783
844
  try {
784
845
  await this.executeAtom('execute_script', [`history.${navType}();`, null]);
785
846
  }
786
847
  finally {
787
- ( /** @type {RemoteDebugger} */(this.remote)).allowNavigationWithoutReload = false;
848
+ this.remote.allowNavigationWithoutReload = false;
788
849
  }
789
850
  }
790
851
  /**
791
- * @this {XCUITestDriver}
792
- * @returns {string} The base url which could be used to access WDA HTTP endpoints.
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() {
857
+ const wdaPort = () => {
858
+ try {
859
+ return this.wda.url?.port;
860
+ }
861
+ catch {
862
+ // this.wda could raise an error when that was not initialized yet.
863
+ return null;
864
+ }
865
+ };
795
866
  const remotePort = ((this.isRealDevice() ? this.opts.wdaRemotePort : null)
796
- ?? this.wda?.url?.port
867
+ ?? wdaPort()
797
868
  ?? this.opts.wdaLocalPort)
798
869
  || 8100;
799
870
  const remoteIp = this.opts.wdaBindingIP ?? '127.0.0.1';
@@ -809,8 +880,8 @@ function getWdaLocalhostRoot() {
809
880
  * The returned value could also be used to manually transform web coordinates
810
881
  * to real devices ones in client scripts.
811
882
  *
812
- * @this {XCUITestDriver}
813
- * @returns {Promise<import('../types').CalibrationData>}
883
+ * @returns Calibration data with offset and pixel ratio information
884
+ * @throws {errors.NotImplementedError} If not in a web context
814
885
  */
815
886
  async function mobileCalibrateWebToRealCoordinatesTranslation() {
816
887
  if (!this.isWebContext()) {
@@ -818,12 +889,11 @@ async function mobileCalibrateWebToRealCoordinatesTranslation() {
818
889
  }
819
890
  const currentUrl = await this.getUrl();
820
891
  await this.setUrl(`${this.getWdaLocalhostRoot()}/calibrate`);
821
- const { width, height } = /** @type {import('@appium/types').Rect} */ (await this.proxyCommand('/window/rect', 'GET'));
892
+ const { width, height } = await this.proxyCommand('/window/rect', 'GET');
822
893
  const [centerX, centerY] = [width / 2, height / 2];
823
894
  const errorPrefix = 'Cannot determine web view coordinates offset. Are you in Safari context?';
824
- const performCalibrationTap = async (/** @type {number} */ tapX, /** @type {number} */ tapY) => {
895
+ const performCalibrationTap = async (tapX, tapY) => {
825
896
  await this.mobileTap(tapX, tapY);
826
- /** @type {import('@appium/types').Position} */
827
897
  let result;
828
898
  try {
829
899
  const title = await this.title();
@@ -855,30 +925,17 @@ async function mobileCalibrateWebToRealCoordinatesTranslation() {
855
925
  // restore the previous url
856
926
  await this.setUrl(currentUrl);
857
927
  }
858
- const result = /** @type {import('../types').CalibrationData} */ (this.webviewCalibrationResult);
928
+ const result = this.webviewCalibrationResult;
859
929
  return {
860
930
  ...result,
861
931
  offsetX: Math.round(result.offsetX),
862
932
  offsetY: Math.round(result.offsetY),
863
933
  };
864
934
  }
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
935
  /**
879
936
  * Updates Mobile Safari preferences on an iOS Simulator
880
937
  *
881
- * @param {import('@appium/types').StringRecord} preferences - An object containing Safari settings to be updated.
938
+ * @param preferences - An object containing Safari settings to be updated.
882
939
  * The list of available setting names and their values can be retrieved by changing the
883
940
  * corresponding Safari settings in the UI and then inspecting
884
941
  * `Library/Preferences/com.apple.mobilesafari.plist` file inside of the `com.apple.mobilesafari`
@@ -888,29 +945,27 @@ async function mobileCalibrateWebToRealCoordinatesTranslation() {
888
945
  * the plist content to the Terminal.
889
946
  *
890
947
  * @group Simulator Only
891
- * @returns {Promise<void>}
892
- * @throws {Error} if run on a real device or if the preferences argument is invalid
893
- * @this {XCUITestDriver}
948
+ * @throws {Error} If run on a real device
949
+ * @throws {errors.InvalidArgumentError} If the preferences argument is invalid
894
950
  */
895
951
  async function mobileUpdateSafariPreferences(preferences) {
896
- if (!this.isSimulator()) {
897
- throw new Error('This extension is only available for Simulator');
898
- }
952
+ const simulator = utils_1.assertSimulator.call(this, 'Updating Safari preferences');
899
953
  if (!lodash_1.default.isPlainObject(preferences)) {
900
954
  throw new driver_1.errors.InvalidArgumentError('"preferences" argument must be a valid object');
901
955
  }
902
956
  this.log.debug(`About to update Safari preferences: ${JSON.stringify(preferences)}`);
903
- await /** @type {import('appium-ios-simulator').Simulator} */ (this.device).updateSafariSettings(preferences);
957
+ await simulator.updateSafariSettings(preferences);
904
958
  }
905
959
  /**
906
- * @this {XCUITestDriver}
907
- * @param {timing.Timer} timer
908
- * @returns {Promise<InstanceType<typeof errors.TimeoutError>>}
960
+ * Generates a timeout error with detailed information about atom execution failure.
961
+ *
962
+ * @param timer - Timer instance to get duration from
963
+ * @returns Timeout error with descriptive message
909
964
  */
910
965
  async function generateAtomTimeoutError(timer) {
911
966
  let message = (`The remote Safari debugger did not respond to the requested ` +
912
967
  `command after ${timer.getDuration().asMilliSeconds}ms. `);
913
- message += (await this.remote?.isJavascriptExecutionBlocked()) ? (`It appears that JavaScript execution is blocked, ` +
968
+ message += (await this._remote?.isJavascriptExecutionBlocked()) ? (`It appears that JavaScript execution is blocked, ` +
914
969
  `which could be caused by either a modal dialog obstructing the current page, ` +
915
970
  `or a JavaScript routine monopolizing the event loop.`) : (`However, the debugger still responds to JavaScript commands, ` +
916
971
  `which suggests that the provided atom script is taking too long to execute.`);
@@ -921,9 +976,12 @@ async function generateAtomTimeoutError(timer) {
921
976
  return new driver_1.errors.TimeoutError(message);
922
977
  }
923
978
  /**
924
- * @this {XCUITestDriver}
925
- * @param {any} atomsElement
926
- * @returns {Promise<boolean>}
979
+ * Attempts to tap a web element using native element matching.
980
+ *
981
+ * Tries to find a native element by matching text content, then taps it directly.
982
+ *
983
+ * @param atomsElement - Atoms-compatible element to tap
984
+ * @returns True if the native tap was successful, false otherwise
927
985
  */
928
986
  async function tapWebElementNatively(atomsElement) {
929
987
  // try to get the text of the element, which will be accessible in the
@@ -943,10 +1001,10 @@ async function tapWebElementNatively(atomsElement) {
943
1001
  }
944
1002
  const el = els[0];
945
1003
  // use tap because on iOS 11.2 and below `nativeClick` crashes WDA
946
- const rect = /** @type {import('@appium/types').Rect} */ (await this.proxyCommand(`/element/${support_1.util.unwrapElement(el)}/rect`, 'GET'));
1004
+ const rect = await this.proxyCommand(`/element/${support_1.util.unwrapElement(el)}/rect`, 'GET');
947
1005
  if (els.length > 1) {
948
1006
  const el2 = els[1];
949
- const rect2 = /** @type {import('@appium/types').Rect} */ (await this.proxyCommand(`/element/${support_1.util.unwrapElement(el2)}/rect`, 'GET'));
1007
+ const rect2 = await this.proxyCommand(`/element/${support_1.util.unwrapElement(el2)}/rect`, 'GET');
950
1008
  if (rect.x !== rect2.x || rect.y !== rect2.y
951
1009
  || rect.width !== rect2.width || rect.height !== rect2.height) {
952
1010
  // These 2 native elements are not referring to the same web element
@@ -964,8 +1022,10 @@ async function tapWebElementNatively(atomsElement) {
964
1022
  return false;
965
1023
  }
966
1024
  /**
967
- * @param {any} id
968
- * @returns {boolean}
1025
+ * Validates if a value is a valid element identifier.
1026
+ *
1027
+ * @param id - Value to validate
1028
+ * @returns True if the value is a valid element identifier
969
1029
  */
970
1030
  function isValidElementIdentifier(id) {
971
1031
  if (!lodash_1.default.isString(id) && !lodash_1.default.isNumber(id)) {
@@ -980,12 +1040,12 @@ function isValidElementIdentifier(id) {
980
1040
  return true;
981
1041
  }
982
1042
  /**
983
- * Creates a JavaScript Cookie
1043
+ * Creates a JavaScript cookie string.
984
1044
  *
985
- * @param {string} key
986
- * @param {string} value
987
- * @param {CookieOptions} [options={}]
988
- * @returns {string}
1045
+ * @param key - Cookie name
1046
+ * @param value - Cookie value
1047
+ * @param options - Cookie options (expires, path, domain, secure, httpOnly)
1048
+ * @returns Cookie string suitable for document.cookie
989
1049
  */
990
1050
  function createJSCookie(key, value, options = {}) {
991
1051
  return [
@@ -999,31 +1059,12 @@ function createJSCookie(key, value, options = {}) {
999
1059
  ].join('');
1000
1060
  }
1001
1061
  /**
1002
- * @this {XCUITestDriver}
1003
- * @param {import('@appium/types').Cookie} cookie
1004
- * @returns {Promise<any>}
1062
+ * Deletes a cookie via the remote debugger.
1063
+ *
1064
+ * @param cookie - Cookie object to delete
1005
1065
  */
1006
1066
  async function _deleteCookie(cookie) {
1007
1067
  const url = `http${cookie.secure ? 's' : ''}://${cookie.domain}${cookie.path}`;
1008
- return await ( /** @type {RemoteDebugger} */(this.remote)).deleteCookie(cookie.name, url);
1068
+ return await this.remote.deleteCookie(cookie.name, url);
1009
1069
  }
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
1070
  //# sourceMappingURL=web.js.map