appium-xcuitest-driver 10.10.0 → 10.11.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.
Files changed (75) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/build/lib/app-infos-cache.d.ts +29 -31
  3. package/build/lib/app-infos-cache.d.ts.map +1 -1
  4. package/build/lib/app-infos-cache.js +29 -33
  5. package/build/lib/app-infos-cache.js.map +1 -1
  6. package/build/lib/app-utils.d.ts +30 -59
  7. package/build/lib/app-utils.d.ts.map +1 -1
  8. package/build/lib/app-utils.js +158 -211
  9. package/build/lib/app-utils.js.map +1 -1
  10. package/build/lib/commands/battery.d.ts.map +1 -1
  11. package/build/lib/commands/battery.js +4 -8
  12. package/build/lib/commands/battery.js.map +1 -1
  13. package/build/lib/commands/biometric.d.ts.map +1 -1
  14. package/build/lib/commands/biometric.js +1 -5
  15. package/build/lib/commands/biometric.js.map +1 -1
  16. package/build/lib/commands/condition.js +4 -4
  17. package/build/lib/commands/condition.js.map +1 -1
  18. package/build/lib/commands/content-size.js +1 -1
  19. package/build/lib/commands/content-size.js.map +1 -1
  20. package/build/lib/commands/find.js +2 -2
  21. package/build/lib/commands/find.js.map +1 -1
  22. package/build/lib/commands/increase-contrast.js +1 -1
  23. package/build/lib/commands/increase-contrast.js.map +1 -1
  24. package/build/lib/commands/keychains.d.ts.map +1 -1
  25. package/build/lib/commands/keychains.js +1 -5
  26. package/build/lib/commands/keychains.js.map +1 -1
  27. package/build/lib/commands/localization.d.ts.map +1 -1
  28. package/build/lib/commands/localization.js +1 -5
  29. package/build/lib/commands/localization.js.map +1 -1
  30. package/build/lib/commands/pasteboard.d.ts.map +1 -1
  31. package/build/lib/commands/pasteboard.js +10 -8
  32. package/build/lib/commands/pasteboard.js.map +1 -1
  33. package/build/lib/commands/permissions.js +1 -1
  34. package/build/lib/commands/permissions.js.map +1 -1
  35. package/build/lib/css-converter.d.ts +3 -9
  36. package/build/lib/css-converter.d.ts.map +1 -1
  37. package/build/lib/css-converter.js +41 -52
  38. package/build/lib/css-converter.js.map +1 -1
  39. package/build/lib/device/real-device-management.js +14 -14
  40. package/build/lib/device/real-device-management.js.map +1 -1
  41. package/build/lib/device/simulator-management.d.ts.map +1 -1
  42. package/build/lib/device/simulator-management.js +8 -4
  43. package/build/lib/device/simulator-management.js.map +1 -1
  44. package/build/lib/driver.d.ts.map +1 -1
  45. package/build/lib/driver.js +3 -3
  46. package/build/lib/driver.js.map +1 -1
  47. package/build/lib/logger.d.ts +1 -2
  48. package/build/lib/logger.d.ts.map +1 -1
  49. package/build/lib/logger.js +2 -2
  50. package/build/lib/logger.js.map +1 -1
  51. package/build/lib/utils.d.ts +76 -134
  52. package/build/lib/utils.d.ts.map +1 -1
  53. package/build/lib/utils.js +80 -141
  54. package/build/lib/utils.js.map +1 -1
  55. package/lib/{app-infos-cache.js → app-infos-cache.ts} +44 -46
  56. package/lib/{app-utils.js → app-utils.ts} +215 -245
  57. package/lib/commands/battery.js +3 -4
  58. package/lib/commands/biometric.js +1 -2
  59. package/lib/commands/condition.js +1 -1
  60. package/lib/commands/content-size.js +1 -1
  61. package/lib/commands/find.js +1 -1
  62. package/lib/commands/increase-contrast.js +1 -1
  63. package/lib/commands/keychains.js +1 -2
  64. package/lib/commands/localization.js +1 -2
  65. package/lib/commands/pasteboard.js +9 -8
  66. package/lib/commands/permissions.js +1 -1
  67. package/lib/{css-converter.js → css-converter.ts} +75 -88
  68. package/lib/device/real-device-management.ts +1 -1
  69. package/lib/device/simulator-management.ts +9 -4
  70. package/lib/driver.ts +6 -4
  71. package/lib/logger.ts +3 -0
  72. package/lib/{utils.js → utils.ts} +102 -139
  73. package/npm-shrinkwrap.json +38 -32
  74. package/package.json +3 -3
  75. package/lib/logger.js +0 -5
@@ -1,43 +1,57 @@
1
1
  import _ from 'lodash';
2
- import path from 'path';
2
+ import path from 'node:path';
3
3
  import {plist, fs, util, tempDir, zip, timing} from 'appium/support';
4
- import log from './logger.js';
4
+ import {log} from './logger';
5
5
  import os from 'node:os';
6
6
  import {exec} from 'teen_process';
7
7
  import B from 'bluebird';
8
8
  import {spawn} from 'node:child_process';
9
9
  import assert from 'node:assert';
10
- import { isTvOs } from './utils.js';
10
+ import {isTvOs} from './utils';
11
+ import type {XCUITestDriver, XCUITestDriverOpts} from './driver';
12
+ import type {StringRecord, HTTPHeaders, DownloadAppOptions, PostProcessOptions, PostProcessResult, CachedAppInfo} from '@appium/types';
13
+ import type {Readable} from 'node:stream';
11
14
 
12
- const STRINGSDICT_RESOURCE = '.stringsdict';
13
- const STRINGS_RESOURCE = '.strings';
14
15
  export const SAFARI_BUNDLE_ID = 'com.apple.mobilesafari';
15
16
  export const APP_EXT = '.app';
16
17
  export const IPA_EXT = '.ipa';
18
+ export const SUPPORTED_EXTENSIONS = [IPA_EXT, APP_EXT];
19
+ const STRINGSDICT_RESOURCE = '.stringsdict';
20
+ const STRINGS_RESOURCE = '.strings';
17
21
  const ZIP_EXT = '.zip';
18
- const SAFARI_OPTS_ALIASES_MAP = /** @type {const} */ ({
22
+ const SAFARI_OPTS_ALIASES_MAP = {
19
23
  safariAllowPopups: [
20
24
  ['WebKitJavaScriptCanOpenWindowsAutomatically', 'JavaScriptCanOpenWindowsAutomatically'],
21
- (x) => Number(Boolean(x)),
25
+ (x: boolean) => Number(Boolean(x)),
22
26
  ],
23
- safariIgnoreFraudWarning: [['WarnAboutFraudulentWebsites'], (x) => Number(!x)],
24
- safariOpenLinksInBackground: [['OpenLinksInBackground'], (x) => Number(Boolean(x))],
25
- });
27
+ safariIgnoreFraudWarning: [['WarnAboutFraudulentWebsites'], (x: boolean) => Number(!x)],
28
+ safariOpenLinksInBackground: [['OpenLinksInBackground'], (x: boolean) => Number(Boolean(x))],
29
+ } as const;
26
30
  const MAX_ARCHIVE_SCAN_DEPTH = 1;
27
- export const SUPPORTED_EXTENSIONS = [IPA_EXT, APP_EXT];
28
31
  const MACOS_RESOURCE_FOLDER = '__MACOSX';
29
32
  const SANITIZE_REPLACEMENT = '-';
30
33
  const INTEL_ARCH = 'x86_64';
31
34
 
35
+ export interface LocalizableStringsOptions {
36
+ app?: string;
37
+ language?: string;
38
+ localizableStringsDir?: string;
39
+ stringFile?: string;
40
+ strictMode?: boolean;
41
+ }
42
+
43
+ export interface UnzipInfo {
44
+ rootDir: string;
45
+ archiveSize: number;
46
+ }
47
+
32
48
  /**
33
49
  * Verify whether the given application is compatible to the
34
50
  * platform where it is going to be installed and tested.
35
51
  *
36
- * @this {XCUITestDriver}
37
- * @returns {Promise<void>}
38
- * @throws {Error} If bundle architecture does not match the expected device architecture.
52
+ * @throws If bundle architecture does not match the expected device architecture.
39
53
  */
40
- export async function verifyApplicationPlatform() {
54
+ export async function verifyApplicationPlatform(this: XCUITestDriver): Promise<void> {
41
55
  this.log.debug('Verifying application platform');
42
56
 
43
57
  if (!this.opts.app) {
@@ -104,37 +118,13 @@ export async function verifyApplicationPlatform() {
104
118
  );
105
119
  }
106
120
 
107
- /**
108
- *
109
- * @param {string} resourcePath
110
- * @returns {Promise<import('@appium/types').StringRecord>}
111
- */
112
- async function readResource(resourcePath) {
113
- const data = await plist.parsePlistFile(resourcePath);
114
- const result = {};
115
- for (const [key, value] of _.toPairs(data)) {
116
- result[key] = _.isString(value) ? value : JSON.stringify(value);
117
- }
118
- return result;
119
- }
120
-
121
- /**
122
- * @typedef {Object} LocalizableStringsOptions
123
- * @property {string} [app]
124
- * @property {string} [language='en']
125
- * @property {string} [localizableStringsDir]
126
- * @property {string} [stringFile]
127
- * @property {boolean} [strictMode]
128
- */
129
-
130
121
  /**
131
122
  * Extracts string resources from an app
132
- *
133
- * @this {XCUITestDriver}
134
- * @param {LocalizableStringsOptions} opts
135
- * @returns {Promise<import('@appium/types').StringRecord>}
136
123
  */
137
- export async function parseLocalizableStrings(opts = {}) {
124
+ export async function parseLocalizableStrings(
125
+ this: XCUITestDriver,
126
+ opts: LocalizableStringsOptions = {}
127
+ ): Promise<StringRecord> {
138
128
  const {app, language = 'en', localizableStringsDir, stringFile, strictMode} = opts;
139
129
  if (!app) {
140
130
  const message = `Strings extraction is not supported if 'app' capability is not set`;
@@ -147,21 +137,20 @@ export async function parseLocalizableStrings(opts = {}) {
147
137
 
148
138
  let bundleRoot = app;
149
139
  const isArchive = (await fs.stat(app)).isFile();
150
- let tmpRoot;
140
+ let tmpRoot: string | undefined;
151
141
  try {
152
142
  if (isArchive) {
153
143
  tmpRoot = await tempDir.openDir();
154
144
  this.log.info(`Extracting '${app}' into a temporary location to parse its resources`);
155
145
  await zip.extractAllTo(app, tmpRoot);
156
- const relativeBundleRoot = /** @type {string} */ (_.first(await findApps(tmpRoot, [APP_EXT])));
146
+ const relativeBundleRoot = _.first(await findApps(tmpRoot, [APP_EXT])) as string;
157
147
  this.log.info(`Selecting '${relativeBundleRoot}'`);
158
148
  bundleRoot = path.join(tmpRoot, relativeBundleRoot);
159
149
  }
160
150
 
161
- /** @type {string|undefined} */
162
- let lprojRoot;
151
+ let lprojRoot: string | undefined;
163
152
  for (const subfolder of [`${language}.lproj`, localizableStringsDir, ''].filter(_.isString)) {
164
- lprojRoot = path.resolve(bundleRoot, /** @type {string} */ (subfolder));
153
+ lprojRoot = path.resolve(bundleRoot, subfolder as string);
165
154
  if (await fs.exists(lprojRoot)) {
166
155
  break;
167
156
  }
@@ -176,9 +165,9 @@ export async function parseLocalizableStrings(opts = {}) {
176
165
  }
177
166
 
178
167
  this.log.info(`Retrieving resource strings from '${lprojRoot}'`);
179
- const resourcePaths = [];
168
+ const resourcePaths: string[] = [];
180
169
  if (stringFile) {
181
- const dstPath = path.resolve(/** @type {string} */ (lprojRoot), stringFile);
170
+ const dstPath = path.resolve(lprojRoot, stringFile);
182
171
  if (await fs.exists(dstPath)) {
183
172
  resourcePaths.push(dstPath);
184
173
  } else {
@@ -190,7 +179,7 @@ export async function parseLocalizableStrings(opts = {}) {
190
179
  }
191
180
  }
192
181
 
193
- if (_.isEmpty(resourcePaths) && (await fs.exists(lprojRoot))) {
182
+ if (_.isEmpty(resourcePaths) && lprojRoot && (await fs.exists(lprojRoot))) {
194
183
  const resourceFiles = (await fs.readdir(lprojRoot))
195
184
  .filter((name) => _.some([STRINGS_RESOURCE, STRINGSDICT_RESOURCE], (x) => name.endsWith(x)))
196
185
  .map((name) => path.resolve(lprojRoot, name));
@@ -202,8 +191,8 @@ export async function parseLocalizableStrings(opts = {}) {
202
191
  return {};
203
192
  }
204
193
 
205
- const resultStrings = {};
206
- const toAbsolutePath = (/** @type {string} */ p) => path.isAbsolute(p) ? p : path.resolve(process.cwd(), p);
194
+ const resultStrings: StringRecord = {};
195
+ const toAbsolutePath = (p: string) => path.isAbsolute(p) ? p : path.resolve(process.cwd(), p);
207
196
  for (const resourcePath of resourcePaths) {
208
197
  if (!util.isSubPath(toAbsolutePath(resourcePath), toAbsolutePath(bundleRoot))) {
209
198
  // security precaution
@@ -213,7 +202,7 @@ export async function parseLocalizableStrings(opts = {}) {
213
202
  const data = await readResource(resourcePath);
214
203
  this.log.debug(`Parsed ${util.pluralize('string', _.keys(data).length, true)} from '${resourcePath}'`);
215
204
  _.merge(resultStrings, data);
216
- } catch (e) {
205
+ } catch (e: any) {
217
206
  this.log.warn(`Cannot parse '${resourcePath}' resource. Original error: ${e.message}`);
218
207
  }
219
208
  }
@@ -227,43 +216,13 @@ export async function parseLocalizableStrings(opts = {}) {
227
216
  }
228
217
  }
229
218
 
230
- /**
231
- * Check whether the given path on the file system points to the .app bundle root
232
- *
233
- * @param {string} appPath Possible .app bundle root
234
- * @returns {Promise<boolean>} Whether the given path points to an .app bundle
235
- */
236
- async function isAppBundle(appPath) {
237
- return (
238
- _.endsWith(_.toLower(appPath), APP_EXT) &&
239
- (await fs.stat(appPath)).isDirectory() &&
240
- (await fs.exists(path.join(appPath, 'Info.plist')))
241
- );
242
- }
243
-
244
- /**
245
- * Check whether the given path on the file system points to the .ipa file
246
- *
247
- * @param {string} appPath Possible .ipa file
248
- * @returns {Promise<boolean>} Whether the given path points to an .ipa bundle
249
- */
250
- async function isIpaBundle(appPath) {
251
- return _.endsWith(_.toLower(appPath), IPA_EXT) && (await fs.stat(appPath)).isFile();
252
- }
253
-
254
- /**
255
- * @typedef {Object} UnzipInfo
256
- * @property {string} rootDir
257
- * @property {number} archiveSize
258
- */
259
-
260
219
  /**
261
220
  * Unzips a ZIP archive on the local file system.
262
221
  *
263
- * @param {string} archivePath Full path to a .zip archive
264
- * @returns {Promise<UnzipInfo>} temporary folder root where the archive has been extracted
222
+ * @param archivePath Full path to a .zip archive
223
+ * @returns temporary folder root where the archive has been extracted
265
224
  */
266
- export async function unzipFile(archivePath) {
225
+ export async function unzipFile(archivePath: string): Promise<UnzipInfo> {
267
226
  const useSystemUnzipEnv = process.env.APPIUM_PREFER_SYSTEM_UNZIP;
268
227
  const useSystemUnzip =
269
228
  _.isEmpty(useSystemUnzipEnv) || !['0', 'false'].includes(_.toLower(useSystemUnzipEnv));
@@ -289,11 +248,8 @@ export async function unzipFile(archivePath) {
289
248
  * Uses bdstar tool for this purpose.
290
249
  * This allows to optimize the time needed to prepare the app under test
291
250
  * to MAX(download, unzip) instead of SUM(download, unzip)
292
- *
293
- * @param {import('node:stream').Readable} zipStream
294
- * @returns {Promise<UnzipInfo>}
295
251
  */
296
- export async function unzipStream(zipStream) {
252
+ export async function unzipStream(zipStream: Readable): Promise<UnzipInfo> {
297
253
  const tmpRoot = await tempDir.openDir();
298
254
  const bsdtarProcess = spawn(await fs.which('bsdtar'), [
299
255
  '-x',
@@ -321,7 +277,7 @@ export async function unzipStream(zipStream) {
321
277
  zipStream.unpipe(bsdtarProcess.stdin);
322
278
  log.debug(`bsdtar process exited with code ${code}, signal ${signal}`);
323
279
  if (code === 0) {
324
- resolve();
280
+ resolve(undefined);
325
281
  } else {
326
282
  reject(new Error('Is it a valid ZIP archive?'));
327
283
  }
@@ -331,7 +287,7 @@ export async function unzipStream(zipStream) {
331
287
  reject(e);
332
288
  });
333
289
  });
334
- } catch (err) {
290
+ } catch (err: any) {
335
291
  bsdtarProcess.kill(9);
336
292
  await fs.rimraf(tmpRoot);
337
293
  throw new Error(`The response data cannot be unzipped: ${err.message}`);
@@ -346,19 +302,150 @@ export async function unzipStream(zipStream) {
346
302
  }
347
303
 
348
304
  /**
349
- * Used to parse the file name value from response headers
305
+ * Builds Safari preferences object based on the given session capabilities
350
306
  *
351
- * @param {import('@appium/types').HTTPHeaders} headers
352
- * @returns {string?}
307
+ * @param opts
308
+ * @return
353
309
  */
354
- function parseFileName(headers) {
310
+ export function buildSafariPreferences(opts: XCUITestDriverOpts): StringRecord {
311
+ const safariSettings = _.cloneDeep(opts?.safariGlobalPreferences ?? {});
312
+
313
+ for (const [name, [aliases, valueConverter]] of _.toPairs(SAFARI_OPTS_ALIASES_MAP)) {
314
+ if (!_.has(opts, name)) {
315
+ continue;
316
+ }
317
+
318
+ for (const alias of aliases) {
319
+ safariSettings[alias] = valueConverter((opts as any)[name]);
320
+ }
321
+ }
322
+ return safariSettings;
323
+ }
324
+
325
+ /**
326
+ * The callback invoked by configureApp helper
327
+ * when it is necessary to download the remote application.
328
+ * We assume the remote file could be anythingm, but only
329
+ * .zip and .ipa formats are supported.
330
+ * A .zip archive can contain one or more
331
+ */
332
+ export async function onDownloadApp(this: XCUITestDriver, opts: DownloadAppOptions): Promise<string> {
333
+ return this.isRealDevice()
334
+ ? await downloadIpa.bind(this)(opts.stream, opts.headers)
335
+ : await unzipApp.bind(this)(opts.stream);
336
+ }
337
+
338
+ export async function onPostConfigureApp(
339
+ this: XCUITestDriver,
340
+ opts: PostProcessOptions
341
+ ): Promise<PostProcessResult | false> {
342
+ // Pick the previously cached entry if its integrity has been preserved
343
+ const appInfo = _.isPlainObject(opts.cachedAppInfo) ? opts.cachedAppInfo as CachedAppInfo : undefined;
344
+ const cachedPath = appInfo ? appInfo.fullPath : undefined;
345
+
346
+ const shouldUseCachedApp = async () => {
347
+ if (!appInfo || !cachedPath || !await fs.exists(cachedPath)) {
348
+ return false;
349
+ }
350
+
351
+ const isCachedPathAFile = (await fs.stat(cachedPath)).isFile();
352
+ if (isCachedPathAFile) {
353
+ return await fs.hash(cachedPath) === (appInfo.integrity as any)?.file;
354
+ }
355
+ // If the cached path is a folder then it is expected to be previously extracted from
356
+ // an archive located under appPath whose hash is stored as `cachedAppInfo.packageHash`
357
+ if (
358
+ !isCachedPathAFile
359
+ && opts.cachedAppInfo?.packageHash
360
+ && opts.appPath
361
+ && await fs.exists(opts.appPath)
362
+ && (await fs.stat(opts.appPath)).isFile()
363
+ && opts.cachedAppInfo.packageHash === await fs.hash(opts.appPath)
364
+ ) {
365
+ const nestedItemsCountInCache = (appInfo.integrity as any)?.folder;
366
+ if (nestedItemsCountInCache !== undefined) {
367
+ return (await fs.glob('**/*', {cwd: cachedPath})).length >= nestedItemsCountInCache;
368
+ }
369
+ }
370
+
371
+ return false;
372
+ };
373
+
374
+ if (await shouldUseCachedApp()) {
375
+ if (!cachedPath) {
376
+ return false;
377
+ }
378
+ this.log.info(`Using '${cachedPath}' which was cached from '${opts.appPath || 'unknown'}'`);
379
+ return {appPath: cachedPath};
380
+ }
381
+
382
+ if (!opts.appPath) {
383
+ return false;
384
+ }
385
+
386
+ const isLocalIpa = await isIpaBundle(opts.appPath);
387
+ const isLocalApp = !isLocalIpa && await isAppBundle(opts.appPath);
388
+ const isPackageReadyForInstall = isLocalApp || (this.isRealDevice() && isLocalIpa);
389
+ if (isPackageReadyForInstall) {
390
+ await this.appInfosCache.put(opts.appPath);
391
+ }
392
+ // Only local .app bundles (real device/Simulator)
393
+ // and .ipa packages for real devices should not be cached
394
+ if (!opts.isUrl && isPackageReadyForInstall) {
395
+ return false;
396
+ }
397
+ // Cache the app while unpacking the bundle if necessary
398
+ return {
399
+ appPath: isPackageReadyForInstall
400
+ ? opts.appPath
401
+ : await unzipApp.bind(this)(opts.appPath)
402
+ };
403
+ }
404
+
405
+ // Private functions
406
+ async function readResource(resourcePath: string): Promise<StringRecord> {
407
+ const data = await plist.parsePlistFile(resourcePath);
408
+ return _.toPairs(data).reduce((result, [key, value]) => {
409
+ result[key] = _.isString(value) ? value : JSON.stringify(value);
410
+ return result;
411
+ }, {} as StringRecord);
412
+ }
413
+
414
+ /**
415
+ * Check whether the given path on the file system points to the .app bundle root
416
+ *
417
+ * @param appPath Possible .app bundle root
418
+ * @returns Whether the given path points to an .app bundle
419
+ */
420
+ async function isAppBundle(appPath: string): Promise<boolean> {
421
+ return (
422
+ _.endsWith(_.toLower(appPath), APP_EXT) &&
423
+ (await fs.stat(appPath)).isDirectory() &&
424
+ (await fs.exists(path.join(appPath, 'Info.plist')))
425
+ );
426
+ }
427
+
428
+ /**
429
+ * Check whether the given path on the file system points to the .ipa file
430
+ *
431
+ * @param appPath Possible .ipa file
432
+ * @returns Whether the given path points to an .ipa bundle
433
+ */
434
+ async function isIpaBundle(appPath: string): Promise<boolean> {
435
+ return _.endsWith(_.toLower(appPath), IPA_EXT) && (await fs.stat(appPath)).isFile();
436
+ }
437
+
438
+ /**
439
+ * Used to parse the file name value from response headers
440
+ */
441
+ function parseFileName(headers: HTTPHeaders): string | null {
355
442
  const contentDisposition = headers['content-disposition'];
356
443
  if (!_.isString(contentDisposition)) {
357
444
  return null;
358
445
  }
359
446
 
360
- if (/^attachment/i.test(/** @type {string} */ (contentDisposition))) {
361
- const match = /filename="([^"]+)/i.exec(/** @type {string} */ (contentDisposition));
447
+ if (/^attachment/i.test(contentDisposition)) {
448
+ const match = /filename="([^"]+)/i.exec(contentDisposition);
362
449
  if (match) {
363
450
  return fs.sanitizeName(match[1], {replacement: SANITIZE_REPLACEMENT});
364
451
  }
@@ -368,16 +455,11 @@ function parseFileName(headers) {
368
455
 
369
456
  /**
370
457
  * Downloads and verifies remote applications for real devices
371
- *
372
- * @this {XCUITestDriver}
373
- * @param {import('node:stream').Readable} stream
374
- * @param {import('@appium/types').HTTPHeaders} headers
375
- * @returns {Promise<string>}
376
458
  */
377
- async function downloadIpa(stream, headers) {
459
+ async function downloadIpa(this: XCUITestDriver, stream: Readable, headers: HTTPHeaders): Promise<string> {
378
460
  const timer = new timing.Timer().start();
379
461
 
380
- const logPerformance = (/** @type {string} */ dstPath, /** @type {number} */ fileSize, /** @type {string} */ action) => {
462
+ const logPerformance = (dstPath: string, fileSize: number, action: string) => {
381
463
  const secondsElapsed = timer.getDuration().asSeconds;
382
464
  this.log.info(
383
465
  `The remote file (${util.toReadableSizeString(fileSize)}) ` +
@@ -405,7 +487,7 @@ async function downloadIpa(stream, headers) {
405
487
  for (const matchedPath of matchedPaths) {
406
488
  try {
407
489
  await this.appInfosCache.put(matchedPath);
408
- } catch (e) {
490
+ } catch (e: any) {
409
491
  this.log.info(e.message);
410
492
  continue;
411
493
  }
@@ -436,7 +518,7 @@ async function downloadIpa(stream, headers) {
436
518
  reject(e);
437
519
  });
438
520
  });
439
- } catch (err) {
521
+ } catch (err: any) {
440
522
  throw new Error(`Cannot fetch the remote file: ${err.message}`);
441
523
  }
442
524
  const {size} = await fs.stat(ipaPath);
@@ -453,11 +535,11 @@ async function downloadIpa(stream, headers) {
453
535
  /**
454
536
  * Looks for items with given extensions in the given folder
455
537
  *
456
- * @param {string} appPath Full path to an app bundle
457
- * @param {Array<string>} appExtensions List of matching item extensions
458
- * @returns {Promise<string[]>} List of relative paths to matched items
538
+ * @param appPath Full path to an app bundle
539
+ * @param appExtensions List of matching item extensions
540
+ * @returns List of relative paths to matched items
459
541
  */
460
- async function findApps(appPath, appExtensions) {
542
+ async function findApps(appPath: string, appExtensions: string[]): Promise<string[]> {
461
543
  const globPattern = `**/*.+(${appExtensions.map((ext) => ext.replace(/^\./, '')).join('|')})`;
462
544
  const sortedBundleItems = (
463
545
  await fs.glob(globPattern, {
@@ -470,11 +552,11 @@ async function findApps(appPath, appExtensions) {
470
552
  /**
471
553
  * Moves the application bundle to a newly created temporary folder
472
554
  *
473
- * @param {string} appPath Full path to the .app or .ipa bundle
474
- * @returns {Promise<string>} The new path to the app bundle.
555
+ * @param appPath Full path to the .app or .ipa bundle
556
+ * @returns The new path to the app bundle.
475
557
  * The name of the app bundle remains the same
476
558
  */
477
- async function isolateApp(appPath) {
559
+ async function isolateApp(appPath: string): Promise<string> {
478
560
  const appFileName = path.basename(appPath);
479
561
  if ((await fs.stat(appPath)).isFile()) {
480
562
  const isolatedPath = await tempDir.path({
@@ -491,38 +573,20 @@ async function isolateApp(appPath) {
491
573
  return isolatedRoot;
492
574
  }
493
575
 
494
- /**
495
- * Builds Safari preferences object based on the given session capabilities
496
- *
497
- * @param {import('./driver').XCUITestDriverOpts} opts
498
- * @return {import('@appium/types').StringRecord}
499
- */
500
- export function buildSafariPreferences(opts) {
501
- const safariSettings = _.cloneDeep(opts?.safariGlobalPreferences ?? {});
502
-
503
- for (const [name, [aliases, valueConverter]] of _.toPairs(SAFARI_OPTS_ALIASES_MAP)) {
504
- if (!_.has(opts, name)) {
505
- continue;
506
- }
507
-
508
- for (const alias of aliases) {
509
- safariSettings[alias] = valueConverter(opts[name]);
510
- }
511
- }
512
- return safariSettings;
513
- }
514
-
515
576
  /**
516
577
  * Unzip the given archive and find a matching .app bundle in it
517
578
  *
518
- * @this {XCUITestDriver}
519
- * @param {string|import('node:stream').Readable} appPathOrZipStream The path to the archive.
520
- * @param {number} depth [0] the current nesting depth. App bundles whose nesting level
579
+ * @param appPathOrZipStream The path to the archive.
580
+ * @param depth [0] the current nesting depth. App bundles whose nesting level
521
581
  * is greater than 1 are not supported.
522
- * @returns {Promise<string>} Full path to the first matching .app bundle..
582
+ * @returns Full path to the first matching .app bundle..
523
583
  * @throws If no matching .app bundles were found in the provided archive.
524
584
  */
525
- async function unzipApp(appPathOrZipStream, depth = 0) {
585
+ async function unzipApp(
586
+ this: XCUITestDriver,
587
+ appPathOrZipStream: string | Readable,
588
+ depth: number = 0
589
+ ): Promise<string> {
526
590
  const errMsg = `The archive did not have any matching ${APP_EXT} or ${IPA_EXT} ` +
527
591
  `bundles. Please make sure the provided package is valid and contains at least one matching ` +
528
592
  `application bundle which is not nested.`;
@@ -531,22 +595,18 @@ async function unzipApp(appPathOrZipStream, depth = 0) {
531
595
  }
532
596
 
533
597
  const timer = new timing.Timer().start();
534
- /** @type {string} */
535
- let rootDir;
536
- /** @type {number} */
537
- let archiveSize;
598
+ let rootDir: string;
599
+ let archiveSize: number;
538
600
  try {
539
601
  if (_.isString(appPathOrZipStream)) {
540
- ({rootDir, archiveSize} = await unzipFile(/** @type {string} */ (appPathOrZipStream)));
602
+ ({rootDir, archiveSize} = await unzipFile(appPathOrZipStream));
541
603
  } else {
542
604
  if (depth > 0) {
543
605
  assert.fail('Streaming unzip cannot be invoked for nested archive items');
544
606
  }
545
- ({rootDir, archiveSize} = await unzipStream(
546
- /** @type {import('node:stream').Readable} */ (appPathOrZipStream))
547
- );
607
+ ({rootDir, archiveSize} = await unzipStream(appPathOrZipStream));
548
608
  }
549
- } catch (e) {
609
+ } catch (e: any) {
550
610
  this.log.debug(e.stack);
551
611
  throw new Error(
552
612
  `Cannot prepare the application for testing. Original error: ${e.message}`
@@ -564,11 +624,11 @@ async function unzipApp(appPathOrZipStream, depth = 0) {
564
624
  this.log.debug(`Approximate decompression speed: ${util.toReadableSizeString(bytesPerSec)}/s`);
565
625
  }
566
626
 
567
- const isCompatibleWithCurrentPlatform = async (/** @type {string} */ appPath) => {
568
- let platforms;
627
+ const isCompatibleWithCurrentPlatform = async (appPath: string) => {
628
+ let platforms: string[];
569
629
  try {
570
630
  platforms = await this.appInfosCache.extractAppPlatforms(appPath);
571
- } catch (e) {
631
+ } catch (e: any) {
572
632
  this.log.info(e.message);
573
633
  return false;
574
634
  }
@@ -615,100 +675,10 @@ async function unzipApp(appPathOrZipStream, depth = 0) {
615
675
  throw new Error(errMsg);
616
676
  }
617
677
 
618
- /**
619
- * The callback invoked by configureApp helper
620
- * when it is necessary to download the remote application.
621
- * We assume the remote file could be anythingm, but only
622
- * .zip and .ipa formats are supported.
623
- * A .zip archive can contain one or more
624
- *
625
- * @this {XCUITestDriver}
626
- * @param {import('@appium/types').DownloadAppOptions} opts
627
- * @returns {Promise<string>}
628
- */
629
- export async function onDownloadApp({stream, headers}) {
630
- return this.isRealDevice()
631
- ? await downloadIpa.bind(this)(stream, headers)
632
- : await unzipApp.bind(this)(stream);
633
- }
634
-
635
- /**
636
- * @this {XCUITestDriver}
637
- * @param {import('@appium/types').PostProcessOptions} opts
638
- * @returns {Promise<import('@appium/types').PostProcessResult|false>}
639
- */
640
- export async function onPostConfigureApp({cachedAppInfo, isUrl, appPath}) {
641
- // Pick the previously cached entry if its integrity has been preserved
642
- /** @type {import('@appium/types').CachedAppInfo|undefined} */
643
- const appInfo = _.isPlainObject(cachedAppInfo) ? cachedAppInfo : undefined;
644
- const cachedPath = appInfo ? /** @type {string} */ (appInfo.fullPath) : undefined;
645
-
646
- const shouldUseCachedApp = async () => {
647
- if (!appInfo || !cachedPath || !await fs.exists(cachedPath)) {
648
- return false;
649
- }
650
-
651
- const isCachedPathAFile = (await fs.stat(cachedPath)).isFile();
652
- if (isCachedPathAFile) {
653
- return await fs.hash(cachedPath) === /** @type {any} */ (appInfo.integrity)?.file;
654
- }
655
- // If the cached path is a folder then it is expected to be previously extracted from
656
- // an archive located under appPath whose hash is stored as `cachedAppInfo.packageHash`
657
- if (
658
- !isCachedPathAFile
659
- && cachedAppInfo?.packageHash
660
- && await fs.exists(/** @type {string} */ (appPath))
661
- && (await fs.stat(/** @type {string} */ (appPath))).isFile()
662
- && cachedAppInfo.packageHash === await fs.hash(/** @type {string} */ (appPath))
663
- ) {
664
- /** @type {number|undefined} */
665
- const nestedItemsCountInCache = /** @type {any} */ (appInfo.integrity)?.folder;
666
- if (nestedItemsCountInCache !== undefined) {
667
- return (await fs.glob('**/*', {cwd: cachedPath})).length >= nestedItemsCountInCache;
668
- }
669
- }
670
-
671
- return false;
672
- };
673
-
674
- if (await shouldUseCachedApp()) {
675
- this.log.info(`Using '${cachedPath}' which was cached from '${appPath}'`);
676
- return {appPath: /** @type {string} */ (cachedPath)};
677
- }
678
-
679
- const isLocalIpa = await isIpaBundle(/** @type {string} */(appPath));
680
- const isLocalApp = !isLocalIpa && await isAppBundle(/** @type {string} */(appPath));
681
- const isPackageReadyForInstall = isLocalApp || (this.isRealDevice() && isLocalIpa);
682
- if (isPackageReadyForInstall) {
683
- await this.appInfosCache.put(/** @type {string} */(appPath));
684
- }
685
- // Only local .app bundles (real device/Simulator)
686
- // and .ipa packages for real devices should not be cached
687
- if (!isUrl && isPackageReadyForInstall) {
688
- return false;
689
- }
690
- // Cache the app while unpacking the bundle if necessary
691
- return {
692
- appPath: isPackageReadyForInstall
693
- ? appPath
694
- : await unzipApp.bind(this)(/** @type {string} */(appPath))
695
- };
696
- }
697
-
698
- /**
699
- * @returns {Promise<boolean>}
700
- */
701
- async function isRosettaInstalled() {
678
+ async function isRosettaInstalled(): Promise<boolean> {
702
679
  return await fs.exists('/Library/Apple/usr/share/rosetta/rosetta');
703
680
  }
704
681
 
705
- /**
706
- * @returns {boolean}
707
- */
708
- function isAppleSilicon() {
682
+ function isAppleSilicon(): boolean {
709
683
  return os.cpus()[0].model.includes('Apple');
710
684
  }
711
-
712
- /**
713
- * @typedef {import('./driver').XCUITestDriver} XCUITestDriver
714
- */
@@ -1,5 +1,4 @@
1
- import log from '../logger.js';
2
- import { isIos18OrNewer } from '../utils.js';
1
+ import { isIos18OrNewer } from '../utils';
3
2
 
4
3
  /**
5
4
  * Reads the battery information from the device under test.
@@ -22,10 +21,10 @@ export async function mobileGetBatteryInfo() {
22
21
  returnRawJson: true,
23
22
  });
24
23
  } catch (err) {
25
- log.error(`Failed to get battery info from DiagnosticsService: ${err.message}`);
24
+ this.log.error(`Failed to get battery info from DiagnosticsService: ${err.message}`);
26
25
  } finally {
27
26
  if (remoteXPCConnection) {
28
- log.info(`Closing remoteXPC connection for device ${this.device.udid}`);
27
+ this.log.info(`Closing remoteXPC connection for device ${this.device.udid}`);
29
28
  await remoteXPCConnection.close();
30
29
  }
31
30
  }