appium-xcuitest-driver 7.33.0 → 7.34.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 (35) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/build/lib/commands/condition.d.ts.map +1 -1
  3. package/build/lib/commands/condition.js +11 -11
  4. package/build/lib/commands/condition.js.map +1 -1
  5. package/build/lib/commands/file-movement.d.ts +4 -3
  6. package/build/lib/commands/file-movement.d.ts.map +1 -1
  7. package/build/lib/commands/file-movement.js +80 -57
  8. package/build/lib/commands/file-movement.js.map +1 -1
  9. package/build/lib/commands/pcap.js +2 -2
  10. package/build/lib/commands/pcap.js.map +1 -1
  11. package/build/lib/commands/performance.js +6 -6
  12. package/build/lib/commands/performance.js.map +1 -1
  13. package/build/lib/commands/proxy-helper.js +2 -2
  14. package/build/lib/commands/proxy-helper.js.map +1 -1
  15. package/build/lib/commands/record-audio.js +4 -4
  16. package/build/lib/commands/record-audio.js.map +1 -1
  17. package/build/lib/commands/recordscreen.js +3 -3
  18. package/build/lib/commands/recordscreen.js.map +1 -1
  19. package/build/lib/driver.d.ts +2 -2
  20. package/build/lib/driver.d.ts.map +1 -1
  21. package/build/lib/driver.js +11 -11
  22. package/build/lib/driver.js.map +1 -1
  23. package/build/lib/utils.js +4 -4
  24. package/build/lib/utils.js.map +1 -1
  25. package/lib/commands/condition.js +13 -11
  26. package/lib/commands/file-movement.js +80 -57
  27. package/lib/commands/pcap.js +2 -2
  28. package/lib/commands/performance.js +6 -6
  29. package/lib/commands/proxy-helper.js +2 -2
  30. package/lib/commands/record-audio.js +4 -4
  31. package/lib/commands/recordscreen.js +3 -3
  32. package/lib/driver.js +13 -11
  33. package/lib/utils.js +4 -4
  34. package/npm-shrinkwrap.json +170 -41
  35. package/package.json +2 -2
@@ -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
- async function createService(udid, remotePath) {
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
- * @param {import('../driver').Simulator} device - The device object, which represents the device under test.
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(device, remotePath, base64Data) {
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
- * @param {import('../real-device').RealDevice} device - The device object, which represents the device under test.
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(device, remotePath, base64Data) {
161
- const {service, relativePath} = await createService(device.udid, remotePath);
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}'. Original error: ${e.message}`);
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
- async function deleteFileOrFolder(device, remotePath, isSimulator) {
173
- return isSimulator
174
- ? await deleteFromSimulator(device, remotePath)
175
- : await deleteFromRealDevice(device, remotePath);
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
- * @param {import('../driver').Simulator} device - The device object, which represents the device under test.
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(device, remotePath, isFile) {
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.errorAndThrow(
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
- * @param {import('../real-device').RealDevice} device - The device object, which represents the device under test.
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(device, remotePath, isFile) {
248
- const {service, relativePath} = await createService(device.udid, remotePath);
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
- * @param {import('../driver').Simulator} device - The device object, which represents the device under test.
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(device, remotePath) {
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
- * @param {import('../real-device').RealDevice} device - The device object, which represents the device under test.
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(device, remotePath) {
325
- const {service, relativePath} = await createService(device.udid, remotePath);
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(/** @type {import('../driver').Simulator} */ (this.device), remotePath, base64Data)
365
- : await pushFileToRealDevice(/** @type {import('../real-device').RealDevice} */ (this.device), remotePath, base64Data);
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(/** @type {import('../driver').Simulator} */ (this.device), remotePath, true)
400
- : await pullFromRealDevice(/** @type {import('../real-device').RealDevice} */ (this.device), remotePath, true);
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.device, remotePath, this.isSimulator());
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.device, remotePath, this.isSimulator());
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(/** @type {import('../driver').Simulator} */ (this.device), remotePath, false)
461
- : await pullFromRealDevice(/** @type {import('../real-device').RealDevice} */ (this.device), remotePath, false);
483
+ ? await pullFromSimulator.bind(this)(remotePath, false)
484
+ : await pullFromRealDevice.bind(this)(remotePath, false);
462
485
  },
463
486
 
464
487
  /**
@@ -83,7 +83,7 @@ export default {
83
83
  */
84
84
  async mobileStartPcap(timeLimitSec = 180, forceRestart = false) {
85
85
  if (this.isSimulator()) {
86
- this.log.errorAndThrow('Network traffic capture only works on real devices');
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.errorAndThrow(
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.errorAndThrow(
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.errorAndThrow(
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.errorAndThrow(PERF_RECORD_SECURITY_MESSAGE);
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.errorAndThrow(PERF_RECORD_SECURITY_MESSAGE);
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.errorAndThrow(
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.errorAndThrow(
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.errorAndThrow('Proxying requires an endpoint');
79
+ throw this.log.errorWithException('Proxying requires an endpoint');
80
80
  } else if (!SUPPORTED_METHODS.has(method)) {
81
- this.log.errorAndThrow(
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.errorAndThrow(
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.errorAndThrow(
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.errorAndThrow(
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.errorAndThrow(
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.errorAndThrow('Unable to stop screen recording process');
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.errorAndThrow(
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.errorAndThrow(
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.errorAndThrow(`App with bundle identifier '${this.opts.bundleId}' unknown`);
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.errorAndThrow('processArguments.args must be an array of strings');
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.errorAndThrow(
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.errorAndThrow(
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.errorAndThrow(
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.errorAndThrow(
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.errorAndThrow(
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.errorAndThrow(`'browserName' cannot be set together with 'bundleId' capability`);
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.errorAndThrow(
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.errorAndThrow(
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.errorAndThrow(`Could not parse "otherApps" capability: ${e.message}`);
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.errorAndThrow(`Could not find app at '${app}'`);
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.errorAndThrow(
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.errorAndThrow(
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.errorAndThrow(`The file at '${localPath}' does not exist or is not accessible`);
380
+ throw log.errorWithException(`The file at '${localPath}' does not exist or is not accessible`);
381
381
  }
382
382
 
383
383
  if (_.isEmpty(remotePath)) {