appium-xcuitest-driver 7.33.0 → 7.33.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.
- package/CHANGELOG.md +6 -0
- package/build/lib/commands/condition.d.ts.map +1 -1
- package/build/lib/commands/condition.js +11 -11
- package/build/lib/commands/condition.js.map +1 -1
- package/build/lib/commands/file-movement.d.ts +4 -3
- package/build/lib/commands/file-movement.d.ts.map +1 -1
- package/build/lib/commands/file-movement.js +80 -57
- package/build/lib/commands/file-movement.js.map +1 -1
- package/build/lib/commands/pcap.js +2 -2
- package/build/lib/commands/pcap.js.map +1 -1
- package/build/lib/commands/performance.js +6 -6
- package/build/lib/commands/performance.js.map +1 -1
- package/build/lib/commands/proxy-helper.js +2 -2
- package/build/lib/commands/proxy-helper.js.map +1 -1
- package/build/lib/commands/record-audio.js +4 -4
- package/build/lib/commands/record-audio.js.map +1 -1
- package/build/lib/commands/recordscreen.js +3 -3
- package/build/lib/commands/recordscreen.js.map +1 -1
- package/build/lib/driver.d.ts +2 -2
- package/build/lib/driver.d.ts.map +1 -1
- package/build/lib/driver.js +11 -11
- package/build/lib/driver.js.map +1 -1
- package/build/lib/utils.js +4 -4
- package/build/lib/utils.js.map +1 -1
- package/lib/commands/condition.js +13 -11
- package/lib/commands/file-movement.js +80 -57
- package/lib/commands/pcap.js +2 -2
- package/lib/commands/performance.js +6 -6
- package/lib/commands/proxy-helper.js +2 -2
- package/lib/commands/record-audio.js +4 -4
- package/lib/commands/recordscreen.js +3 -3
- package/lib/driver.js +13 -11
- package/lib/utils.js +4 -4
- package/npm-shrinkwrap.json +112 -19
- package/package.json +1 -1
|
@@ -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 log from '../logger';
|
|
5
4
|
import {services} from 'appium-ios-device';
|
|
6
5
|
import {pullFile, pullFolder, pushFile} from '../ios-fs-helpers';
|
|
7
6
|
import {errors} from 'appium/driver';
|
|
@@ -16,6 +15,7 @@ const OBJECT_NOT_FOUND_ERROR_MESSAGE = 'OBJECT_NOT_FOUND';
|
|
|
16
15
|
/**
|
|
17
16
|
* Parses the actual path and the bundle identifier from the given path string
|
|
18
17
|
*
|
|
18
|
+
* @this {XCUITestDriver}
|
|
19
19
|
* @param {string} remotePath - The given path string. The string should
|
|
20
20
|
* match `CONTAINER_PATH_PATTERN` regexp, otherwise an error is going
|
|
21
21
|
* to be thrown. A valid string example: `@bundle.identifier:container_type/relative_path_in_container`
|
|
@@ -38,7 +38,7 @@ export async function parseContainerPath(remotePath, containerRootSupplier) {
|
|
|
38
38
|
// not counting the colon
|
|
39
39
|
if (typeSeparatorPos > 0 && typeSeparatorPos < bundleId.length - 1) {
|
|
40
40
|
containerType = bundleId.substring(typeSeparatorPos + 1);
|
|
41
|
-
log.debug(`Parsed container type: ${containerType}`);
|
|
41
|
+
this.log.debug(`Parsed container type: ${containerType}`);
|
|
42
42
|
bundleId = bundleId.substring(0, typeSeparatorPos);
|
|
43
43
|
}
|
|
44
44
|
if (_.isNil(containerRootSupplier)) {
|
|
@@ -53,6 +53,12 @@ export async function parseContainerPath(remotePath, containerRootSupplier) {
|
|
|
53
53
|
return {bundleId, pathInContainer, containerType};
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
/**
|
|
57
|
+
*
|
|
58
|
+
* @param {string} originalPath
|
|
59
|
+
* @param {string} root
|
|
60
|
+
* @returns {void}
|
|
61
|
+
*/
|
|
56
62
|
function verifyIsSubPath(originalPath, root) {
|
|
57
63
|
const normalizedRoot = path.normalize(root);
|
|
58
64
|
const normalizedPath = path.normalize(path.dirname(originalPath));
|
|
@@ -62,6 +68,13 @@ function verifyIsSubPath(originalPath, root) {
|
|
|
62
68
|
}
|
|
63
69
|
}
|
|
64
70
|
|
|
71
|
+
/**
|
|
72
|
+
*
|
|
73
|
+
* @param {string} udid
|
|
74
|
+
* @param {string} [bundleId]
|
|
75
|
+
* @param {string} [containerType]
|
|
76
|
+
* @returns {Promise<any>}
|
|
77
|
+
*/
|
|
65
78
|
async function createAfcClient(udid, bundleId, containerType) {
|
|
66
79
|
if (!bundleId) {
|
|
67
80
|
return await services.startAfcService(udid);
|
|
@@ -72,20 +85,31 @@ async function createAfcClient(udid, bundleId, containerType) {
|
|
|
72
85
|
: await service.vendContainer(bundleId);
|
|
73
86
|
}
|
|
74
87
|
|
|
88
|
+
/**
|
|
89
|
+
*
|
|
90
|
+
* @param {string} [containerType]
|
|
91
|
+
* @returns {boolean}
|
|
92
|
+
*/
|
|
75
93
|
function isDocumentsContainer(containerType) {
|
|
76
94
|
return _.toLower(containerType) === _.toLower(CONTAINER_DOCUMENTS_PATH);
|
|
77
95
|
}
|
|
78
96
|
|
|
79
|
-
|
|
97
|
+
/**
|
|
98
|
+
*
|
|
99
|
+
* @this {XCUITestDriver}
|
|
100
|
+
* @param {string} remotePath
|
|
101
|
+
* @returns {Promise<{service: any, relativePath: string}>}
|
|
102
|
+
*/
|
|
103
|
+
async function createService(remotePath) {
|
|
80
104
|
if (CONTAINER_PATH_PATTERN.test(remotePath)) {
|
|
81
|
-
const {bundleId, pathInContainer, containerType} = await parseContainerPath(remotePath);
|
|
82
|
-
const service = await createAfcClient(udid, bundleId, containerType);
|
|
105
|
+
const {bundleId, pathInContainer, containerType} = await parseContainerPath.bind(this)(remotePath);
|
|
106
|
+
const service = await createAfcClient(this.device.udid, bundleId, containerType);
|
|
83
107
|
const relativePath = isDocumentsContainer(containerType)
|
|
84
108
|
? path.join(CONTAINER_DOCUMENTS_PATH, pathInContainer)
|
|
85
109
|
: pathInContainer;
|
|
86
110
|
return {service, relativePath};
|
|
87
111
|
} else {
|
|
88
|
-
const service = await createAfcClient(udid);
|
|
112
|
+
const service = await createAfcClient(this.device.udid);
|
|
89
113
|
return {service, relativePath: remotePath};
|
|
90
114
|
}
|
|
91
115
|
}
|
|
@@ -93,9 +117,7 @@ async function createService(udid, remotePath) {
|
|
|
93
117
|
/**
|
|
94
118
|
* Save the given base64 data chunk as a binary file on the Simulator under test.
|
|
95
119
|
*
|
|
96
|
-
* @
|
|
97
|
-
* This object is expected to have the `udid` property containing the
|
|
98
|
-
* valid device ID.
|
|
120
|
+
* @this {XCUITestDriver}
|
|
99
121
|
* @param {string} remotePath - The remote path on the device. This variable can be prefixed with
|
|
100
122
|
* bundle id, so then the file will be uploaded to the corresponding
|
|
101
123
|
* application container instead of the default media folder, for example
|
|
@@ -108,20 +130,21 @@ async function createService(udid, remotePath) {
|
|
|
108
130
|
* to the default media folder and only the file name is considered important.
|
|
109
131
|
* @param {string} base64Data - Base-64 encoded content of the file to be uploaded.
|
|
110
132
|
*/
|
|
111
|
-
async function pushFileToSimulator(
|
|
133
|
+
async function pushFileToSimulator(remotePath, base64Data) {
|
|
112
134
|
const buffer = Buffer.from(base64Data, 'base64');
|
|
135
|
+
const device = /** @type {import('../driver').Simulator} */ (this.device);
|
|
113
136
|
if (CONTAINER_PATH_PATTERN.test(remotePath)) {
|
|
114
|
-
const {bundleId, pathInContainer: dstPath} = await parseContainerPath(
|
|
137
|
+
const {bundleId, pathInContainer: dstPath} = await parseContainerPath.bind(this)(
|
|
115
138
|
remotePath,
|
|
116
139
|
async (appBundle, containerType) =>
|
|
117
140
|
await device.simctl.getAppContainer(appBundle, containerType),
|
|
118
141
|
);
|
|
119
|
-
log.info(
|
|
142
|
+
this.log.info(
|
|
120
143
|
`Parsed bundle identifier '${bundleId}' from '${remotePath}'. ` +
|
|
121
144
|
`Will put the data into '${dstPath}'`,
|
|
122
145
|
);
|
|
123
146
|
if (!(await fs.exists(path.dirname(dstPath)))) {
|
|
124
|
-
log.debug(`The destination folder '${path.dirname(dstPath)}' does not exist. Creating...`);
|
|
147
|
+
this.log.debug(`The destination folder '${path.dirname(dstPath)}' does not exist. Creating...`);
|
|
125
148
|
await mkdirp(path.dirname(dstPath));
|
|
126
149
|
}
|
|
127
150
|
await fs.writeFile(dstPath, buffer);
|
|
@@ -140,9 +163,7 @@ async function pushFileToSimulator(device, remotePath, base64Data) {
|
|
|
140
163
|
/**
|
|
141
164
|
* Save the given base64 data chunk as a binary file on the device under test.
|
|
142
165
|
*
|
|
143
|
-
* @
|
|
144
|
-
* This object is expected to have the `udid` property containing the
|
|
145
|
-
* valid device ID.
|
|
166
|
+
* @this {XCUITestDriver}
|
|
146
167
|
* @param {string} remotePath - The remote path on the device. This variable can be prefixed with
|
|
147
168
|
* bundle id, so then the file will be uploaded to the corresponding
|
|
148
169
|
* application container instead of the default media folder. Use
|
|
@@ -157,31 +178,35 @@ async function pushFileToSimulator(device, remotePath, base64Data) {
|
|
|
157
178
|
* as base64 decoded data.
|
|
158
179
|
* @param {string} base64Data - Base-64 encoded content of the file to be uploaded.
|
|
159
180
|
*/
|
|
160
|
-
async function pushFileToRealDevice(
|
|
161
|
-
const {service, relativePath} = await createService(
|
|
181
|
+
async function pushFileToRealDevice(remotePath, base64Data) {
|
|
182
|
+
const {service, relativePath} = await createService.bind(this)(remotePath);
|
|
162
183
|
try {
|
|
163
184
|
await pushFile(service, Buffer.from(base64Data, 'base64'), relativePath);
|
|
164
185
|
} catch (e) {
|
|
165
|
-
log.debug(e.stack);
|
|
166
|
-
throw new Error(`Could not push the file to '${remotePath}'.
|
|
186
|
+
this.log.debug(e.stack);
|
|
187
|
+
throw new Error(`Could not push the file to '${remotePath}'. Original error: ${e.message}`);
|
|
167
188
|
} finally {
|
|
168
189
|
service.close();
|
|
169
190
|
}
|
|
170
191
|
}
|
|
171
192
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
193
|
+
/**
|
|
194
|
+
*
|
|
195
|
+
* @this {XCUITestDriver}
|
|
196
|
+
* @param {string} remotePath
|
|
197
|
+
* @returns {Promise<void>}
|
|
198
|
+
*/
|
|
199
|
+
async function deleteFileOrFolder(remotePath) {
|
|
200
|
+
return this.isSimulator()
|
|
201
|
+
? await deleteFromSimulator.bind(this)(remotePath)
|
|
202
|
+
: await deleteFromRealDevice.bind(this)(remotePath);
|
|
176
203
|
}
|
|
177
204
|
|
|
178
205
|
/**
|
|
179
206
|
* Get the content of given file or folder from iOS Simulator and return it as base-64 encoded string.
|
|
180
207
|
* Folder content is recursively packed into a zip archive.
|
|
181
208
|
*
|
|
182
|
-
* @
|
|
183
|
-
* This object is expected to have the `udid` property containing the
|
|
184
|
-
* valid device ID.
|
|
209
|
+
* @this {XCUITestDriver}
|
|
185
210
|
* @param {string} remotePath - The path to a file or a folder, which exists in the corresponding application
|
|
186
211
|
* container on Simulator. Use
|
|
187
212
|
* `@<app_bundle_id>:<optional_container_type>/<path_to_the_file_or_folder_inside_container>`
|
|
@@ -191,15 +216,16 @@ async function deleteFileOrFolder(device, remotePath, isSimulator) {
|
|
|
191
216
|
* @param {boolean} isFile - Whether the destination item is a file or a folder
|
|
192
217
|
* @returns {Promise<string>} Base-64 encoded content of the file.
|
|
193
218
|
*/
|
|
194
|
-
async function pullFromSimulator(
|
|
219
|
+
async function pullFromSimulator(remotePath, isFile) {
|
|
195
220
|
let pathOnServer;
|
|
221
|
+
const device = /** @type {import('../driver').Simulator} */ (this.device);
|
|
196
222
|
if (CONTAINER_PATH_PATTERN.test(remotePath)) {
|
|
197
|
-
const {bundleId, pathInContainer: dstPath} = await parseContainerPath(
|
|
223
|
+
const {bundleId, pathInContainer: dstPath} = await parseContainerPath.bind(this)(
|
|
198
224
|
remotePath,
|
|
199
225
|
async (appBundle, containerType) =>
|
|
200
226
|
await device.simctl.getAppContainer(appBundle, containerType),
|
|
201
227
|
);
|
|
202
|
-
log.info(
|
|
228
|
+
this.log.info(
|
|
203
229
|
`Parsed bundle identifier '${bundleId}' from '${remotePath}'. ` +
|
|
204
230
|
`Will get the data from '${dstPath}'`,
|
|
205
231
|
);
|
|
@@ -208,10 +234,10 @@ async function pullFromSimulator(device, remotePath, isFile) {
|
|
|
208
234
|
const simRoot = device.getDir();
|
|
209
235
|
pathOnServer = path.posix.join(simRoot, remotePath);
|
|
210
236
|
verifyIsSubPath(pathOnServer, simRoot);
|
|
211
|
-
log.info(`Got the full item path: ${pathOnServer}`);
|
|
237
|
+
this.log.info(`Got the full item path: ${pathOnServer}`);
|
|
212
238
|
}
|
|
213
239
|
if (!(await fs.exists(pathOnServer))) {
|
|
214
|
-
log.
|
|
240
|
+
throw this.log.errorWithException(
|
|
215
241
|
`The remote ${isFile ? 'file' : 'folder'} at '${pathOnServer}' does not exist`,
|
|
216
242
|
);
|
|
217
243
|
}
|
|
@@ -225,9 +251,7 @@ async function pullFromSimulator(device, remotePath, isFile) {
|
|
|
225
251
|
* Get the content of given file or folder from the real device under test and return it as base-64 encoded string.
|
|
226
252
|
* Folder content is recursively packed into a zip archive.
|
|
227
253
|
*
|
|
228
|
-
* @
|
|
229
|
-
* This object is expected to have the `udid` property containing the
|
|
230
|
-
* valid device ID.
|
|
254
|
+
* @this {XCUITestDriver}
|
|
231
255
|
* @param {string} remotePath - The path to an existing remote file on the device. This variable can be prefixed with
|
|
232
256
|
* bundle id, so then the file will be downloaded from the corresponding
|
|
233
257
|
* application container instead of the default media folder. Use
|
|
@@ -244,8 +268,8 @@ async function pullFromSimulator(device, remotePath, isFile) {
|
|
|
244
268
|
* @param {boolean} isFile - Whether the destination item is a file or a folder
|
|
245
269
|
* @returns {Promise<string>} Base-64 encoded content of the remote file
|
|
246
270
|
*/
|
|
247
|
-
async function pullFromRealDevice(
|
|
248
|
-
const {service, relativePath} = await createService(
|
|
271
|
+
async function pullFromRealDevice(remotePath, isFile) {
|
|
272
|
+
const {service, relativePath} = await createService.bind(this)(remotePath);
|
|
249
273
|
try {
|
|
250
274
|
const fileInfo = await service.getFileInfo(relativePath);
|
|
251
275
|
if (isFile && fileInfo.isDirectory()) {
|
|
@@ -266,25 +290,25 @@ async function pullFromRealDevice(device, remotePath, isFile) {
|
|
|
266
290
|
/**
|
|
267
291
|
* Remove the file or folder from the device
|
|
268
292
|
*
|
|
269
|
-
* @
|
|
270
|
-
* This object is expected to have the `udid` property containing the
|
|
271
|
-
* valid device ID.
|
|
293
|
+
* @this {XCUITestDriver}
|
|
272
294
|
* @param {string} remotePath - The path to a file or a folder, which exists in the corresponding application
|
|
273
295
|
* container on Simulator. Use
|
|
274
296
|
* `@<app_bundle_id>:<optional_container_type>/<path_to_the_file_or_folder_inside_container>`
|
|
275
297
|
* format to pull a file or a folder from an application container of the given type.
|
|
276
298
|
* Possible container types are 'app', 'data', 'groups', '<A specific App Group container>'.
|
|
277
299
|
* The default type is 'app'.
|
|
300
|
+
* @returns {Promise<void>}
|
|
278
301
|
*/
|
|
279
|
-
async function deleteFromSimulator(
|
|
302
|
+
async function deleteFromSimulator(remotePath) {
|
|
280
303
|
let pathOnServer;
|
|
304
|
+
const device = /** @type {import('../driver').Simulator} */ (this.device);
|
|
281
305
|
if (CONTAINER_PATH_PATTERN.test(remotePath)) {
|
|
282
|
-
const {bundleId, pathInContainer: dstPath} = await parseContainerPath(
|
|
306
|
+
const {bundleId, pathInContainer: dstPath} = await parseContainerPath.bind(this)(
|
|
283
307
|
remotePath,
|
|
284
308
|
async (appBundle, containerType) =>
|
|
285
309
|
await device.simctl.getAppContainer(appBundle, containerType),
|
|
286
310
|
);
|
|
287
|
-
log.info(
|
|
311
|
+
this.log.info(
|
|
288
312
|
`Parsed bundle identifier '${bundleId}' from '${remotePath}'. ` +
|
|
289
313
|
`'${dstPath}' will be deleted`,
|
|
290
314
|
);
|
|
@@ -293,7 +317,7 @@ async function deleteFromSimulator(device, remotePath) {
|
|
|
293
317
|
const simRoot = device.getDir();
|
|
294
318
|
pathOnServer = path.posix.join(simRoot, remotePath);
|
|
295
319
|
verifyIsSubPath(pathOnServer, simRoot);
|
|
296
|
-
log.info(`Got the full path: ${pathOnServer}`);
|
|
320
|
+
this.log.info(`Got the full path: ${pathOnServer}`);
|
|
297
321
|
}
|
|
298
322
|
if (!(await fs.exists(pathOnServer))) {
|
|
299
323
|
throw new errors.InvalidArgumentError(`The remote path at '${pathOnServer}' does not exist`);
|
|
@@ -304,9 +328,7 @@ async function deleteFromSimulator(device, remotePath) {
|
|
|
304
328
|
/**
|
|
305
329
|
* Remove the file or folder from the device
|
|
306
330
|
*
|
|
307
|
-
* @
|
|
308
|
-
* This object is expected to have the `udid` property containing the
|
|
309
|
-
* valid device ID.
|
|
331
|
+
* @this {XCUITestDriver}
|
|
310
332
|
* @param {string} remotePath - The path to an existing remote file on the device. This variable can be prefixed with
|
|
311
333
|
* bundle id, so then the file will be downloaded from the corresponding
|
|
312
334
|
* application container instead of the default media folder. Use
|
|
@@ -320,9 +342,10 @@ async function deleteFromSimulator(device, remotePath) {
|
|
|
320
342
|
* `On My iPhone/<app name>/111.png` wil be pulled into the mounted host machine
|
|
321
343
|
* and Appium returns the data as base64-encoded string to client.
|
|
322
344
|
* `@com.myapp.bla:documents/` means `On My iPhone/<app name>`.
|
|
345
|
+
* @returns {Promise<void>}
|
|
323
346
|
*/
|
|
324
|
-
async function deleteFromRealDevice(
|
|
325
|
-
const {service, relativePath} = await createService(
|
|
347
|
+
async function deleteFromRealDevice(remotePath) {
|
|
348
|
+
const {service, relativePath} = await createService.bind(this)(remotePath);
|
|
326
349
|
try {
|
|
327
350
|
await service.deleteDirectory(relativePath);
|
|
328
351
|
} catch (e) {
|
|
@@ -361,8 +384,8 @@ export default {
|
|
|
361
384
|
base64Data = Buffer.from(base64Data).toString('utf8');
|
|
362
385
|
}
|
|
363
386
|
return this.isSimulator()
|
|
364
|
-
? await pushFileToSimulator
|
|
365
|
-
: await pushFileToRealDevice
|
|
387
|
+
? await pushFileToSimulator.bind(this)(remotePath, base64Data)
|
|
388
|
+
: await pushFileToRealDevice.bind(this)(remotePath, base64Data);
|
|
366
389
|
},
|
|
367
390
|
|
|
368
391
|
/**
|
|
@@ -396,8 +419,8 @@ export default {
|
|
|
396
419
|
);
|
|
397
420
|
}
|
|
398
421
|
return this.isSimulator()
|
|
399
|
-
? await pullFromSimulator
|
|
400
|
-
: await pullFromRealDevice
|
|
422
|
+
? await pullFromSimulator.bind(this)(remotePath, true)
|
|
423
|
+
: await pullFromRealDevice.bind(this)(remotePath, true);
|
|
401
424
|
},
|
|
402
425
|
|
|
403
426
|
/**
|
|
@@ -423,7 +446,7 @@ export default {
|
|
|
423
446
|
if (!remotePath.endsWith('/')) {
|
|
424
447
|
remotePath = `${remotePath}/`;
|
|
425
448
|
}
|
|
426
|
-
await deleteFileOrFolder(this
|
|
449
|
+
await deleteFileOrFolder.bind(this)(remotePath);
|
|
427
450
|
},
|
|
428
451
|
|
|
429
452
|
/**
|
|
@@ -440,7 +463,7 @@ export default {
|
|
|
440
463
|
`'${remotePath}' is given instead`,
|
|
441
464
|
);
|
|
442
465
|
}
|
|
443
|
-
await deleteFileOrFolder(this
|
|
466
|
+
await deleteFileOrFolder.bind(this)(remotePath);
|
|
444
467
|
},
|
|
445
468
|
|
|
446
469
|
/**
|
|
@@ -457,8 +480,8 @@ export default {
|
|
|
457
480
|
remotePath = `${remotePath}/`;
|
|
458
481
|
}
|
|
459
482
|
return this.isSimulator()
|
|
460
|
-
? await pullFromSimulator
|
|
461
|
-
: await pullFromRealDevice
|
|
483
|
+
? await pullFromSimulator.bind(this)(remotePath, false)
|
|
484
|
+
: await pullFromRealDevice.bind(this)(remotePath, false);
|
|
462
485
|
},
|
|
463
486
|
|
|
464
487
|
/**
|
package/lib/commands/pcap.js
CHANGED
|
@@ -83,7 +83,7 @@ export default {
|
|
|
83
83
|
*/
|
|
84
84
|
async mobileStartPcap(timeLimitSec = 180, forceRestart = false) {
|
|
85
85
|
if (this.isSimulator()) {
|
|
86
|
-
this.log.
|
|
86
|
+
throw this.log.errorWithException('Network traffic capture only works on real devices');
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
if (this._trafficCapture?.isCapturing()) {
|
|
@@ -150,7 +150,7 @@ export default {
|
|
|
150
150
|
try {
|
|
151
151
|
resultPath = await this._trafficCapture.finish();
|
|
152
152
|
if (!(await fs.exists(resultPath))) {
|
|
153
|
-
this.log.
|
|
153
|
+
throw this.log.errorWithException(
|
|
154
154
|
`The network traffic capture utility has failed ` +
|
|
155
155
|
`to store the actual traffic capture at '${resultPath}'`,
|
|
156
156
|
);
|
|
@@ -223,7 +223,7 @@ export class PerfRecorder {
|
|
|
223
223
|
await this._enforceTermination();
|
|
224
224
|
const listProfilesCommand =
|
|
225
225
|
toolName === XCTRACE ? `${XCRUN} ${XCTRACE} list templates` : `${INSTRUMENTS} -s`;
|
|
226
|
-
this._logger.
|
|
226
|
+
throw this._logger.errorWithException(
|
|
227
227
|
`There is no ${DEFAULT_EXT} file found for performance profile ` +
|
|
228
228
|
`'${this._profileName}'. Make sure the profile is supported on this device. ` +
|
|
229
229
|
`You could use '${listProfilesCommand}' command to see the list of all available profiles. ` +
|
|
@@ -246,7 +246,7 @@ export class PerfRecorder {
|
|
|
246
246
|
try {
|
|
247
247
|
await this._process?.stop('SIGINT', STOP_TIMEOUT_MS);
|
|
248
248
|
} catch (e) {
|
|
249
|
-
this._logger.
|
|
249
|
+
throw this._logger.errorWithException(
|
|
250
250
|
`Performance recording has failed to exit after ${STOP_TIMEOUT_MS}ms`,
|
|
251
251
|
);
|
|
252
252
|
}
|
|
@@ -279,7 +279,7 @@ export default {
|
|
|
279
279
|
pid,
|
|
280
280
|
) {
|
|
281
281
|
if (!this.isFeatureEnabled(PERF_RECORD_FEAT_NAME) && !this.isRealDevice()) {
|
|
282
|
-
this.log.
|
|
282
|
+
throw this.log.errorWithException(PERF_RECORD_SECURITY_MESSAGE);
|
|
283
283
|
}
|
|
284
284
|
|
|
285
285
|
if (!_.isEmpty(this._perfRecorders)) {
|
|
@@ -348,7 +348,7 @@ export default {
|
|
|
348
348
|
formFields,
|
|
349
349
|
) {
|
|
350
350
|
if (!this.isFeatureEnabled(PERF_RECORD_FEAT_NAME) && !this.isRealDevice()) {
|
|
351
|
-
this.log.
|
|
351
|
+
throw this.log.errorWithException(PERF_RECORD_SECURITY_MESSAGE);
|
|
352
352
|
}
|
|
353
353
|
|
|
354
354
|
if (_.isEmpty(this._perfRecorders)) {
|
|
@@ -358,7 +358,7 @@ export default {
|
|
|
358
358
|
|
|
359
359
|
const recorders = this._perfRecorders.filter((x) => x.profileName === profileName);
|
|
360
360
|
if (_.isEmpty(recorders)) {
|
|
361
|
-
this.log.
|
|
361
|
+
throw this.log.errorWithException(
|
|
362
362
|
`There are no records for performance profile '${profileName}' ` +
|
|
363
363
|
`and device ${this.device.udid}. Have you started the profiling before?`,
|
|
364
364
|
);
|
|
@@ -367,7 +367,7 @@ export default {
|
|
|
367
367
|
const recorder = _.first(recorders);
|
|
368
368
|
const resultPath = await /** @type {PerfRecorder} */ (recorder).stop();
|
|
369
369
|
if (!(await fs.exists(resultPath))) {
|
|
370
|
-
this.log.
|
|
370
|
+
throw this.log.errorWithException(
|
|
371
371
|
`There is no ${DEFAULT_EXT} file found for performance profile '${profileName}' ` +
|
|
372
372
|
`and device ${this.device.udid}. Make sure the selected profile is supported on this device`,
|
|
373
373
|
);
|
|
@@ -76,9 +76,9 @@ export default {
|
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
if (!url) {
|
|
79
|
-
this.log.
|
|
79
|
+
throw this.log.errorWithException('Proxying requires an endpoint');
|
|
80
80
|
} else if (!SUPPORTED_METHODS.has(method)) {
|
|
81
|
-
this.log.
|
|
81
|
+
throw this.log.errorWithException(
|
|
82
82
|
`Proxying only works for the following HTTP methods: ${[...SUPPORTED_METHODS].join(', ')}`,
|
|
83
83
|
);
|
|
84
84
|
}
|
|
@@ -166,14 +166,14 @@ export default {
|
|
|
166
166
|
forceRestart = false,
|
|
167
167
|
) {
|
|
168
168
|
if (!this.isFeatureEnabled(AUDIO_RECORD_FEAT_NAME)) {
|
|
169
|
-
this.log.
|
|
169
|
+
throw this.log.errorWithException(
|
|
170
170
|
`Audio capture feature must be enabled on the server side. ` +
|
|
171
171
|
`Please set '--relaxed-security' or '--allow-insecure' with '${AUDIO_RECORD_FEAT_NAME}' option. ` +
|
|
172
172
|
`Read https://github.com/appium/appium/blob/master/docs/en/writing-running-appium/security.md for more details.`,
|
|
173
173
|
);
|
|
174
174
|
}
|
|
175
175
|
if (!audioInput) {
|
|
176
|
-
this.log.
|
|
176
|
+
throw this.log.errorWithException(
|
|
177
177
|
`The mandatory audioInput option is not provided. Please set it ` +
|
|
178
178
|
`to a correct value (e. g. ':1'). Use 'ffmpeg -f avfoundation -list_devices true -i ""' ` +
|
|
179
179
|
`command to list available input sources`,
|
|
@@ -213,7 +213,7 @@ export default {
|
|
|
213
213
|
|
|
214
214
|
const timeoutSeconds = parseInt(String(timeLimit), 10);
|
|
215
215
|
if (isNaN(timeoutSeconds) || timeoutSeconds > MAX_RECORDING_TIME_SEC || timeoutSeconds <= 0) {
|
|
216
|
-
this.log.
|
|
216
|
+
throw this.log.errorWithException(
|
|
217
217
|
`The timeLimit value must be in range [1, ${MAX_RECORDING_TIME_SEC}] seconds. ` +
|
|
218
218
|
`The value of '${timeLimit}' has been passed instead.`,
|
|
219
219
|
);
|
|
@@ -250,7 +250,7 @@ export default {
|
|
|
250
250
|
try {
|
|
251
251
|
resultPath = await this._audioRecorder.finish();
|
|
252
252
|
if (!(await fs.exists(resultPath))) {
|
|
253
|
-
this.log.
|
|
253
|
+
throw this.log.errorWithException(
|
|
254
254
|
`${FFMPEG_BINARY} has failed ` + `to store the actual audio recording at '${resultPath}'`,
|
|
255
255
|
);
|
|
256
256
|
}
|
|
@@ -240,7 +240,7 @@ export default {
|
|
|
240
240
|
pixelFormat,
|
|
241
241
|
});
|
|
242
242
|
if (!(await screenRecorder.interrupt(true))) {
|
|
243
|
-
this.log.
|
|
243
|
+
throw this.log.errorWithException('Unable to stop screen recording process');
|
|
244
244
|
}
|
|
245
245
|
if (this._recentScreenRecorder) {
|
|
246
246
|
await this._recentScreenRecorder.cleanup();
|
|
@@ -249,7 +249,7 @@ export default {
|
|
|
249
249
|
|
|
250
250
|
const timeoutSeconds = parseFloat(String(timeLimit));
|
|
251
251
|
if (isNaN(timeoutSeconds) || timeoutSeconds > MAX_RECORDING_TIME_SEC || timeoutSeconds <= 0) {
|
|
252
|
-
this.log.
|
|
252
|
+
throw this.log.errorWithException(
|
|
253
253
|
`The timeLimit value must be in range [1, ${MAX_RECORDING_TIME_SEC}] seconds. ` +
|
|
254
254
|
`The value of '${timeLimit}' has been passed instead.`,
|
|
255
255
|
);
|
|
@@ -333,7 +333,7 @@ export default {
|
|
|
333
333
|
try {
|
|
334
334
|
const videoPath = await this._recentScreenRecorder.finish();
|
|
335
335
|
if (!(await fs.exists(videoPath))) {
|
|
336
|
-
this.log.
|
|
336
|
+
throw this.log.errorWithException(
|
|
337
337
|
`The screen recorder utility has failed ` +
|
|
338
338
|
`to store the actual screen recording at '${videoPath}'`,
|
|
339
339
|
);
|
package/lib/driver.js
CHANGED
|
@@ -628,7 +628,7 @@ export class XCUITestDriver extends BaseDriver {
|
|
|
628
628
|
!this.isSafari() &&
|
|
629
629
|
!(await this.device.isAppInstalled(this.opts.bundleId))
|
|
630
630
|
) {
|
|
631
|
-
this.log.
|
|
631
|
+
throw this.log.errorWithException(`App with bundle identifier '${this.opts.bundleId}' unknown`);
|
|
632
632
|
}
|
|
633
633
|
|
|
634
634
|
if (this.isSimulator()) {
|
|
@@ -1385,10 +1385,10 @@ export class XCUITestDriver extends BaseDriver {
|
|
|
1385
1385
|
let verifyProcessArgument = (processArguments) => {
|
|
1386
1386
|
const {args, env} = processArguments;
|
|
1387
1387
|
if (!_.isNil(args) && !_.isArray(args)) {
|
|
1388
|
-
this.log.
|
|
1388
|
+
throw this.log.errorWithException('processArguments.args must be an array of strings');
|
|
1389
1389
|
}
|
|
1390
1390
|
if (!_.isNil(env) && !_.isPlainObject(env)) {
|
|
1391
|
-
this.log.
|
|
1391
|
+
throw this.log.errorWithException(
|
|
1392
1392
|
'processArguments.env must be an object <key,value> pair {a:b, c:d}',
|
|
1393
1393
|
);
|
|
1394
1394
|
}
|
|
@@ -1402,7 +1402,7 @@ export class XCUITestDriver extends BaseDriver {
|
|
|
1402
1402
|
caps.processArguments = JSON.parse(caps.processArguments);
|
|
1403
1403
|
verifyProcessArgument(caps.processArguments);
|
|
1404
1404
|
} catch (err) {
|
|
1405
|
-
this.log.
|
|
1405
|
+
throw this.log.errorWithException(
|
|
1406
1406
|
`processArguments must be a JSON format or an object with format {args : [], env : {a:b, c:d}}. ` +
|
|
1407
1407
|
`Both environment and argument can be null. Error: ${err}`,
|
|
1408
1408
|
);
|
|
@@ -1410,7 +1410,7 @@ export class XCUITestDriver extends BaseDriver {
|
|
|
1410
1410
|
} else if (_.isPlainObject(caps.processArguments)) {
|
|
1411
1411
|
verifyProcessArgument(caps.processArguments);
|
|
1412
1412
|
} else {
|
|
1413
|
-
this.log.
|
|
1413
|
+
throw this.log.errorWithException(
|
|
1414
1414
|
`'processArguments must be an object, or a string JSON object with format {args : [], env : {a:b, c:d}}. ` +
|
|
1415
1415
|
`Both environment and argument can be null.`,
|
|
1416
1416
|
);
|
|
@@ -1422,7 +1422,7 @@ export class XCUITestDriver extends BaseDriver {
|
|
|
1422
1422
|
(caps.keychainPath && !caps.keychainPassword) ||
|
|
1423
1423
|
(!caps.keychainPath && caps.keychainPassword)
|
|
1424
1424
|
) {
|
|
1425
|
-
this.log.
|
|
1425
|
+
throw this.log.errorWithException(
|
|
1426
1426
|
`If 'keychainPath' is set, 'keychainPassword' must also be set (and vice versa).`,
|
|
1427
1427
|
);
|
|
1428
1428
|
}
|
|
@@ -1439,7 +1439,7 @@ export class XCUITestDriver extends BaseDriver {
|
|
|
1439
1439
|
if (_.isString(caps.webDriverAgentUrl)) {
|
|
1440
1440
|
const {protocol, host} = url.parse(caps.webDriverAgentUrl);
|
|
1441
1441
|
if (_.isEmpty(protocol) || _.isEmpty(host)) {
|
|
1442
|
-
this.log.
|
|
1442
|
+
throw this.log.errorWithException(
|
|
1443
1443
|
`'webDriverAgentUrl' capability is expected to contain a valid WebDriverAgent server URL. ` +
|
|
1444
1444
|
`'${caps.webDriverAgentUrl}' is given instead`,
|
|
1445
1445
|
);
|
|
@@ -1448,7 +1448,9 @@ export class XCUITestDriver extends BaseDriver {
|
|
|
1448
1448
|
|
|
1449
1449
|
if (caps.browserName) {
|
|
1450
1450
|
if (caps.bundleId) {
|
|
1451
|
-
this.log.
|
|
1451
|
+
throw this.log.errorWithException(
|
|
1452
|
+
`'browserName' cannot be set together with 'bundleId' capability`
|
|
1453
|
+
);
|
|
1452
1454
|
}
|
|
1453
1455
|
// warn if the capabilities have both `app` and `browser, although this
|
|
1454
1456
|
// is common with selenium grid
|
|
@@ -1470,7 +1472,7 @@ export class XCUITestDriver extends BaseDriver {
|
|
|
1470
1472
|
}
|
|
1471
1473
|
}
|
|
1472
1474
|
} catch (e) {
|
|
1473
|
-
this.log.
|
|
1475
|
+
throw this.log.errorWithException(
|
|
1474
1476
|
`'${caps.permissions}' is expected to be a valid object with format ` +
|
|
1475
1477
|
`{"<bundleId1>": {"<serviceName1>": "<serviceStatus1>", ...}, ...}. Original error: ${e.message}`,
|
|
1476
1478
|
);
|
|
@@ -1478,7 +1480,7 @@ export class XCUITestDriver extends BaseDriver {
|
|
|
1478
1480
|
}
|
|
1479
1481
|
|
|
1480
1482
|
if (caps.platformVersion && !util.coerceVersion(caps.platformVersion, false)) {
|
|
1481
|
-
this.log.
|
|
1483
|
+
throw this.log.errorWithException(
|
|
1482
1484
|
`'platformVersion' must be a valid version number. ` +
|
|
1483
1485
|
`'${caps.platformVersion}' is given instead.`,
|
|
1484
1486
|
);
|
|
@@ -1617,7 +1619,7 @@ export class XCUITestDriver extends BaseDriver {
|
|
|
1617
1619
|
try {
|
|
1618
1620
|
appsList = this.helpers.parseCapsArray(otherApps);
|
|
1619
1621
|
} catch (e) {
|
|
1620
|
-
this.log.
|
|
1622
|
+
throw this.log.errorWithException(`Could not parse "otherApps" capability: ${e.message}`);
|
|
1621
1623
|
}
|
|
1622
1624
|
if (!appsList || !appsList.length) {
|
|
1623
1625
|
this.log.info(`Got zero apps from 'otherApps' capability value. Doing nothing`);
|
package/lib/utils.js
CHANGED
|
@@ -225,7 +225,7 @@ async function clearSystemFiles(wda) {
|
|
|
225
225
|
async function checkAppPresent(app) {
|
|
226
226
|
log.debug(`Checking whether app '${app}' is actually present on file system`);
|
|
227
227
|
if (!(await fs.exists(app))) {
|
|
228
|
-
log.
|
|
228
|
+
throw log.errorWithException(`Could not find app at '${app}'`);
|
|
229
229
|
}
|
|
230
230
|
log.debug('App is present');
|
|
231
231
|
}
|
|
@@ -295,13 +295,13 @@ function normalizeCommandTimeouts(value) {
|
|
|
295
295
|
throw new Error();
|
|
296
296
|
}
|
|
297
297
|
} catch (err) {
|
|
298
|
-
log.
|
|
298
|
+
throw log.errorWithException(
|
|
299
299
|
`"commandTimeouts" capability should be a valid JSON object. "${value}" was given instead`,
|
|
300
300
|
);
|
|
301
301
|
}
|
|
302
302
|
for (let [cmd, timeout] of _.toPairs(result)) {
|
|
303
303
|
if (!_.isInteger(timeout) || timeout <= 0) {
|
|
304
|
-
log.
|
|
304
|
+
throw log.errorWithException(
|
|
305
305
|
`The timeout for "${cmd}" should be a valid natural number of milliseconds. "${timeout}" was given instead`,
|
|
306
306
|
);
|
|
307
307
|
}
|
|
@@ -377,7 +377,7 @@ async function getPIDsListeningOnPort(port, filteringFunc = null) {
|
|
|
377
377
|
*/
|
|
378
378
|
async function encodeBase64OrUpload(localPath, remotePath = null, uploadOptions = {}) {
|
|
379
379
|
if (!(await fs.exists(localPath))) {
|
|
380
|
-
log.
|
|
380
|
+
throw log.errorWithException(`The file at '${localPath}' does not exist or is not accessible`);
|
|
381
381
|
}
|
|
382
382
|
|
|
383
383
|
if (_.isEmpty(remotePath)) {
|