appium-xcuitest-driver 10.14.13 → 10.16.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/certificate.d.ts.map +1 -1
- package/build/lib/commands/certificate.js +33 -22
- package/build/lib/commands/certificate.js.map +1 -1
- package/build/lib/commands/file-movement.d.ts.map +1 -1
- package/build/lib/commands/file-movement.js +73 -45
- package/build/lib/commands/file-movement.js.map +1 -1
- package/build/lib/device/afc-client.d.ts +135 -0
- package/build/lib/device/afc-client.d.ts.map +1 -0
- package/build/lib/device/afc-client.js +422 -0
- package/build/lib/device/afc-client.js.map +1 -0
- package/build/lib/device/certificate-client.d.ts +73 -0
- package/build/lib/device/certificate-client.d.ts.map +1 -0
- package/build/lib/device/certificate-client.js +163 -0
- package/build/lib/device/certificate-client.js.map +1 -0
- package/build/lib/device/real-device-management.d.ts +10 -12
- package/build/lib/device/real-device-management.d.ts.map +1 -1
- package/build/lib/device/real-device-management.js +47 -160
- package/build/lib/device/real-device-management.js.map +1 -1
- package/build/tsconfig.tsbuildinfo +1 -1
- package/lib/commands/certificate.ts +46 -27
- package/lib/commands/file-movement.ts +100 -56
- package/lib/device/afc-client.ts +503 -0
- package/lib/device/certificate-client.ts +195 -0
- package/lib/device/real-device-management.ts +70 -164
- package/npm-shrinkwrap.json +5 -5
- package/package.json +1 -1
|
@@ -7,8 +7,8 @@ import path from 'path';
|
|
|
7
7
|
import http from 'http';
|
|
8
8
|
import {exec} from 'teen_process';
|
|
9
9
|
import {findAPortNotInUse, checkPortStatus} from 'portscanner';
|
|
10
|
-
import {
|
|
11
|
-
import {requireRealDevice} from '../utils';
|
|
10
|
+
import {CertificateClient} from '../device/certificate-client';
|
|
11
|
+
import {requireRealDevice, isIos18OrNewer} from '../utils';
|
|
12
12
|
import type {Simulator} from 'appium-ios-simulator';
|
|
13
13
|
import type {XCUITestDriver} from '../driver';
|
|
14
14
|
import type {CertificateList} from './types';
|
|
@@ -122,18 +122,23 @@ export async function mobileInstallCertificate(
|
|
|
122
122
|
if (!this.opts.udid) {
|
|
123
123
|
throw new Error('udid capability is required');
|
|
124
124
|
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
} else {
|
|
133
|
-
this.log.info(
|
|
134
|
-
'pyidevice is not installed on your system. ' +
|
|
135
|
-
'Falling back to the (slow) UI-based installation',
|
|
125
|
+
|
|
126
|
+
let client: CertificateClient | null = null;
|
|
127
|
+
try {
|
|
128
|
+
client = await CertificateClient.create(
|
|
129
|
+
this.opts.udid,
|
|
130
|
+
this.log,
|
|
131
|
+
isIos18OrNewer(this.opts)
|
|
136
132
|
);
|
|
133
|
+
await client.installCertificate({payload: Buffer.from(content, 'base64')});
|
|
134
|
+
return;
|
|
135
|
+
} catch (err) {
|
|
136
|
+
this.log.error(`Failed to install the certificate: ${err.message}`);
|
|
137
|
+
this.log.info('Falling back to the (slow) UI-based installation');
|
|
138
|
+
} finally {
|
|
139
|
+
if (client) {
|
|
140
|
+
await client.close();
|
|
141
|
+
}
|
|
137
142
|
}
|
|
138
143
|
}
|
|
139
144
|
|
|
@@ -256,12 +261,20 @@ export async function mobileRemoveCertificate(this: XCUITestDriver, name: string
|
|
|
256
261
|
if (!this.opts.udid) {
|
|
257
262
|
throw new Error('udid capability is required');
|
|
258
263
|
}
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
264
|
+
|
|
265
|
+
let client: CertificateClient | null = null;
|
|
266
|
+
try {
|
|
267
|
+
client = await CertificateClient.create(
|
|
268
|
+
this.opts.udid,
|
|
269
|
+
this.log,
|
|
270
|
+
isIos18OrNewer(this.opts)
|
|
271
|
+
);
|
|
272
|
+
return await client.removeCertificate(name);
|
|
273
|
+
} finally {
|
|
274
|
+
if (client) {
|
|
275
|
+
await client.close();
|
|
276
|
+
}
|
|
277
|
+
}
|
|
265
278
|
}
|
|
266
279
|
|
|
267
280
|
/**
|
|
@@ -278,14 +291,21 @@ export async function mobileListCertificates(this: XCUITestDriver): Promise<Cert
|
|
|
278
291
|
if (!this.opts.udid) {
|
|
279
292
|
throw new Error('udid capability is required');
|
|
280
293
|
}
|
|
281
|
-
const client = new Pyidevice({
|
|
282
|
-
udid: this.opts.udid,
|
|
283
|
-
log: this.log,
|
|
284
|
-
});
|
|
285
|
-
await client.assertExists(true);
|
|
286
|
-
return await client.listProfiles();
|
|
287
|
-
}
|
|
288
294
|
|
|
295
|
+
let client: CertificateClient | null = null;
|
|
296
|
+
try {
|
|
297
|
+
client = await CertificateClient.create(
|
|
298
|
+
this.opts.udid,
|
|
299
|
+
this.log,
|
|
300
|
+
isIos18OrNewer(this.opts)
|
|
301
|
+
);
|
|
302
|
+
return await client.listCertificates();
|
|
303
|
+
} finally {
|
|
304
|
+
if (client) {
|
|
305
|
+
await client.close();
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
289
309
|
|
|
290
310
|
/**
|
|
291
311
|
* Extracts the common name of the certificate from the given buffer.
|
|
@@ -501,4 +521,3 @@ async function installPost122Certificate(driver: XCUITestDriver, name: string):
|
|
|
501
521
|
|
|
502
522
|
return true;
|
|
503
523
|
}
|
|
504
|
-
|
|
@@ -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
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
217
|
-
|
|
218
|
-
|
|
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 (
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
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
|
|
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<
|
|
289
|
+
): Promise<CreateServiceResult> {
|
|
247
290
|
if (CONTAINER_PATH_PATTERN.test(remotePath)) {
|
|
248
|
-
const {bundleId, pathInContainer, containerType} = await parseContainerPath.bind(this)(remotePath);
|
|
249
|
-
const
|
|
250
|
-
|
|
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
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
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 {
|
|
361
|
+
const {client, relativePath} = await createService.bind(this)(remotePath);
|
|
316
362
|
try {
|
|
317
|
-
await realDevicePushFile(
|
|
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
|
-
|
|
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 {
|
|
445
|
+
const {client, relativePath} = await createService.bind(this)(remotePath);
|
|
406
446
|
try {
|
|
407
|
-
|
|
408
|
-
|
|
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 && !
|
|
453
|
+
if (!isFile && !isDirectory) {
|
|
412
454
|
throw new Error(`The requested path is not a folder. Path: '${remotePath}'`);
|
|
413
455
|
}
|
|
414
456
|
|
|
415
|
-
return
|
|
416
|
-
? (await
|
|
417
|
-
: (await
|
|
457
|
+
return isDirectory
|
|
458
|
+
? (await realDevicePullFolder(client, relativePath)).toString()
|
|
459
|
+
: (await realDevicePullFile(client, relativePath)).toString('base64');
|
|
418
460
|
} finally {
|
|
419
|
-
|
|
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 {
|
|
521
|
+
const {client, relativePath} = await createService.bind(this)(remotePath);
|
|
480
522
|
try {
|
|
481
|
-
await
|
|
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
|
-
|
|
530
|
+
await client.close();
|
|
489
531
|
}
|
|
490
532
|
}
|
|
491
533
|
|
|
534
|
+
//#endregion
|
|
535
|
+
|