appium-xcuitest-driver 10.14.13 → 10.15.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.
@@ -1,7 +1,6 @@
1
1
  import _ from 'lodash';
2
2
  import {fs, tempDir, mkdirp, zip, util} from 'appium/support';
3
3
  import path from 'path';
4
- import {services} from 'appium-ios-device';
5
4
  import {
6
5
  pullFile as realDevicePullFile,
7
6
  pullFolder as realDevicePullFolder,
@@ -11,6 +10,24 @@ import {errors} from 'appium/driver';
11
10
  import type {Simulator} from 'appium-ios-simulator';
12
11
  import type {XCUITestDriver} from '../driver';
13
12
  import type {ContainerObject, ContainerRootSupplier} from './types';
13
+ import {isIos18OrNewer} from '../utils';
14
+ import {AfcClient} from '../device/afc-client';
15
+
16
+ //#region Type Definitions
17
+
18
+ interface CreateServiceResult {
19
+ client: AfcClient;
20
+ relativePath: string;
21
+ }
22
+
23
+ interface CreateAfcClientOptions {
24
+ bundleId?: string | null;
25
+ containerType?: string | null;
26
+ }
27
+
28
+ //#endregion
29
+
30
+ //#region Constants
14
31
 
15
32
  const CONTAINER_PATH_MARKER = '@';
16
33
  // https://regex101.com/r/PLdB0G/2
@@ -19,6 +36,10 @@ const CONTAINER_TYPE_SEPARATOR = ':';
19
36
  const CONTAINER_DOCUMENTS_PATH = 'Documents';
20
37
  const OBJECT_NOT_FOUND_ERROR_MESSAGE = 'OBJECT_NOT_FOUND';
21
38
 
39
+ //#endregion
40
+
41
+ //#region Public Exported Functions
42
+
22
43
  /**
23
44
  * Parses the actual path and the bundle identifier from the given path string.
24
45
  *
@@ -44,9 +65,12 @@ export async function parseContainerPath(
44
65
  const typeSeparatorPos = bundleId.indexOf(CONTAINER_TYPE_SEPARATOR);
45
66
  // We only consider container type exists if its length is greater than zero
46
67
  // not counting the colon
47
- if (typeSeparatorPos > 0 && typeSeparatorPos < bundleId.length - 1) {
48
- containerType = bundleId.substring(typeSeparatorPos + 1);
49
- this.log.debug(`Parsed container type: ${containerType}`);
68
+ if (typeSeparatorPos > 0) {
69
+ if (typeSeparatorPos < bundleId.length - 1) {
70
+ containerType = bundleId.substring(typeSeparatorPos + 1);
71
+ this.log.debug(`Parsed container type: ${containerType}`);
72
+ }
73
+ // Always strip the colon and everything after it
50
74
  bundleId = bundleId.substring(0, typeSeparatorPos);
51
75
  }
52
76
  if (_.isNil(containerRootSupplier)) {
@@ -202,6 +226,29 @@ export async function mobilePullFolder(this: XCUITestDriver, remotePath: string)
202
226
  return await this.pullFolder(remotePath);
203
227
  }
204
228
 
229
+ //#endregion
230
+
231
+ //#region Private Helper Functions
232
+
233
+ /**
234
+ * Delete file or folder helper
235
+ */
236
+ async function deleteFileOrFolder(this: XCUITestDriver, remotePath: string): Promise<void> {
237
+ return this.isSimulator()
238
+ ? await deleteFromSimulator.bind(this)(remotePath)
239
+ : await deleteFromRealDevice.bind(this)(remotePath);
240
+ }
241
+
242
+ /**
243
+ * Check if container type refers to documents container
244
+ */
245
+ function isDocumentsContainer(containerType?: string | null): boolean {
246
+ return _.toLower(containerType ?? '') === _.toLower(CONTAINER_DOCUMENTS_PATH);
247
+ }
248
+
249
+ /**
250
+ * Verify that a path is a subpath of a root directory
251
+ */
205
252
  function verifyIsSubPath(originalPath: string, root: string): void {
206
253
  const normalizedRoot = path.normalize(root);
207
254
  const normalizedPath = path.normalize(path.dirname(originalPath));
@@ -211,50 +258,49 @@ function verifyIsSubPath(originalPath: string, root: string): void {
211
258
  }
212
259
  }
213
260
 
261
+ /**
262
+ * Create AFC client for file operations
263
+ */
214
264
  async function createAfcClient(
215
265
  this: XCUITestDriver,
216
- bundleId?: string | null,
217
- containerType?: string | null,
218
- ): Promise<any> {
266
+ opts: CreateAfcClientOptions = {}
267
+ ): Promise<AfcClient> {
268
+ const {bundleId, containerType} = opts;
219
269
  const udid = this.device.udid as string;
270
+ const useIos18 = isIos18OrNewer(this.opts);
220
271
 
221
- if (!bundleId) {
222
- return await services.startAfcService(udid);
223
- }
224
- const service = await services.startHouseArrestService(udid);
225
-
226
- const {
227
- skipDocumentsContainerCheck = false,
228
- } = await this.settings.getSettings();
229
-
230
- if (skipDocumentsContainerCheck) {
231
- return service.vendContainer(bundleId);
272
+ if (bundleId) {
273
+ const skipDocumentsCheck = this.settings.getSettings().skipDocumentsContainerCheck ?? false;
274
+ return await AfcClient.createForApp(udid, bundleId, useIos18, {
275
+ containerType: containerType ?? null,
276
+ skipDocumentsCheck,
277
+ });
232
278
  }
233
279
 
234
- return isDocumentsContainer(containerType)
235
- ? await service.vendDocuments(bundleId)
236
- : await service.vendContainer(bundleId);
237
- }
238
-
239
- function isDocumentsContainer(containerType?: string | null): boolean {
240
- return _.toLower(containerType ?? '') === _.toLower(CONTAINER_DOCUMENTS_PATH);
280
+ return await AfcClient.createForDevice(udid, useIos18);
241
281
  }
242
282
 
283
+ /**
284
+ * Create service for file operations
285
+ */
243
286
  async function createService(
244
287
  this: XCUITestDriver,
245
288
  remotePath: string,
246
- ): Promise<{service: any; relativePath: string}> {
289
+ ): Promise<CreateServiceResult> {
247
290
  if (CONTAINER_PATH_PATTERN.test(remotePath)) {
248
- const {bundleId, pathInContainer, containerType} = await parseContainerPath.bind(this)(remotePath);
249
- const service = await createAfcClient.bind(this)(bundleId, containerType);
250
- const relativePath = isDocumentsContainer(containerType)
291
+ const {bundleId, pathInContainer, containerType}: ContainerObject = await parseContainerPath.bind(this)(remotePath);
292
+ const client: AfcClient = await createAfcClient.bind(this)({bundleId, containerType});
293
+ let relativePath = isDocumentsContainer(containerType)
251
294
  ? path.join(CONTAINER_DOCUMENTS_PATH, pathInContainer)
252
295
  : pathInContainer;
253
- return {service, relativePath};
254
- } else {
255
- const service = await createAfcClient.bind(this)();
256
- return {service, relativePath: remotePath};
296
+ // Ensure path starts with / for AFC operations
297
+ if (!relativePath.startsWith('/')) {
298
+ relativePath = `/${relativePath}`;
299
+ }
300
+ return {client, relativePath};
257
301
  }
302
+ const client: AfcClient = await createAfcClient.bind(this)({});
303
+ return {client, relativePath: remotePath};
258
304
  }
259
305
 
260
306
  /**
@@ -312,23 +358,17 @@ async function pushFileToRealDevice(
312
358
  remotePath: string,
313
359
  base64Data: string,
314
360
  ): Promise<void> {
315
- const {service, relativePath} = await createService.bind(this)(remotePath);
361
+ const {client, relativePath} = await createService.bind(this)(remotePath);
316
362
  try {
317
- await realDevicePushFile(service, Buffer.from(base64Data, 'base64'), relativePath);
363
+ await realDevicePushFile(client, Buffer.from(base64Data, 'base64'), relativePath);
318
364
  } catch (e) {
319
- this.log.debug(e.stack);
320
- throw new Error(`Could not push the file to '${remotePath}'. Original error: ${e.message}`);
365
+ this.log.debug((e as Error).stack);
366
+ throw new Error(`Could not push the file to '${remotePath}'. Original error: ${(e as Error).message}`);
321
367
  } finally {
322
- service.close();
368
+ await client.close();
323
369
  }
324
370
  }
325
371
 
326
- async function deleteFileOrFolder(this: XCUITestDriver, remotePath: string): Promise<void> {
327
- return this.isSimulator()
328
- ? await deleteFromSimulator.bind(this)(remotePath)
329
- : await deleteFromRealDevice.bind(this)(remotePath);
330
- }
331
-
332
372
  /**
333
373
  * Get the content of given file or folder from iOS Simulator and return it as base-64 encoded string.
334
374
  * Folder content is recursively packed into a zip archive.
@@ -402,21 +442,23 @@ async function pullFromRealDevice(
402
442
  remotePath: string,
403
443
  isFile: boolean,
404
444
  ): Promise<string> {
405
- const {service, relativePath} = await createService.bind(this)(remotePath);
445
+ const {client, relativePath} = await createService.bind(this)(remotePath);
406
446
  try {
407
- const fileInfo = await service.getFileInfo(relativePath);
408
- if (isFile && fileInfo.isDirectory()) {
447
+ // Check if path is a directory
448
+ const isDirectory = await client.isDirectory(relativePath);
449
+
450
+ if (isFile && isDirectory) {
409
451
  throw new Error(`The requested path is not a file. Path: '${remotePath}'`);
410
452
  }
411
- if (!isFile && !fileInfo.isDirectory()) {
453
+ if (!isFile && !isDirectory) {
412
454
  throw new Error(`The requested path is not a folder. Path: '${remotePath}'`);
413
455
  }
414
456
 
415
- return fileInfo.isFile()
416
- ? (await realDevicePullFile(service, relativePath)).toString('base64')
417
- : (await realDevicePullFolder(service, relativePath)).toString();
457
+ return isDirectory
458
+ ? (await realDevicePullFolder(client, relativePath)).toString()
459
+ : (await realDevicePullFile(client, relativePath)).toString('base64');
418
460
  } finally {
419
- service.close();
461
+ await client.close();
420
462
  }
421
463
  }
422
464
 
@@ -476,16 +518,18 @@ async function deleteFromSimulator(this: XCUITestDriver, remotePath: string): Pr
476
518
  * @returns Nothing
477
519
  */
478
520
  async function deleteFromRealDevice(this: XCUITestDriver, remotePath: string): Promise<void> {
479
- const {service, relativePath} = await createService.bind(this)(remotePath);
521
+ const {client, relativePath} = await createService.bind(this)(remotePath);
480
522
  try {
481
- await service.deleteDirectory(relativePath);
523
+ await client.deleteDirectory(relativePath);
482
524
  } catch (e) {
483
- if (e.message.includes(OBJECT_NOT_FOUND_ERROR_MESSAGE)) {
525
+ if ((e as Error).message.includes(OBJECT_NOT_FOUND_ERROR_MESSAGE)) {
484
526
  throw new Error(`Path '${remotePath}' does not exist on the device`);
485
527
  }
486
528
  throw e;
487
529
  } finally {
488
- service.close();
530
+ await client.close();
489
531
  }
490
532
  }
491
533
 
534
+ //#endregion
535
+