appium-android-driver 5.0.6 → 5.0.9

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 (40) hide show
  1. package/README.md +0 -1
  2. package/build/lib/commands/actions.js +16 -20
  3. package/build/lib/commands/app-management.js +15 -17
  4. package/build/lib/commands/context.js +46 -66
  5. package/build/lib/commands/coverage.js +2 -6
  6. package/build/lib/commands/element.js +3 -9
  7. package/build/lib/commands/emu-console.js +3 -5
  8. package/build/lib/commands/execute.js +3 -6
  9. package/build/lib/commands/file-actions.js +22 -29
  10. package/build/lib/commands/general.js +18 -30
  11. package/build/lib/commands/ime.js +8 -21
  12. package/build/lib/commands/network.js +14 -27
  13. package/build/lib/commands/performance.js +3 -6
  14. package/build/lib/commands/recordscreen.js +27 -38
  15. package/build/lib/commands/shell.js +6 -7
  16. package/build/lib/commands/streamscreen.js +29 -43
  17. package/build/lib/commands/system-bars.js +10 -16
  18. package/build/lib/commands/touch.js +4 -10
  19. package/build/lib/driver.js +61 -67
  20. package/build/lib/webview-helpers.js +2 -7
  21. package/lib/commands/actions.js +18 -19
  22. package/lib/commands/app-management.js +13 -12
  23. package/lib/commands/context.js +36 -40
  24. package/lib/commands/coverage.js +1 -4
  25. package/lib/commands/element.js +2 -3
  26. package/lib/commands/emu-console.js +2 -2
  27. package/lib/commands/execute.js +2 -3
  28. package/lib/commands/file-actions.js +31 -25
  29. package/lib/commands/general.js +16 -18
  30. package/lib/commands/ime.js +7 -8
  31. package/lib/commands/network.js +15 -16
  32. package/lib/commands/performance.js +2 -3
  33. package/lib/commands/recordscreen.js +23 -22
  34. package/lib/commands/shell.js +6 -6
  35. package/lib/commands/streamscreen.js +22 -23
  36. package/lib/commands/system-bars.js +11 -10
  37. package/lib/commands/touch.js +3 -4
  38. package/lib/driver.js +45 -46
  39. package/lib/webview-helpers.js +1 -7
  40. package/package.json +2 -2
@@ -1,5 +1,4 @@
1
1
  import _ from 'lodash';
2
- import log from '../logger';
3
2
  import Chromedriver from 'appium-chromedriver';
4
3
  import PortFinder from 'portfinder';
5
4
  import B from 'bluebird';
@@ -26,8 +25,7 @@ commands.getCurrentContext = async function getCurrentContext () { // eslint-dis
26
25
  };
27
26
 
28
27
  commands.getContexts = async function getContexts () {
29
- const opts = Object.assign({isChromeSession: this.isChromeSession}, this.opts);
30
- const webviewsMapping = await webviewHelpers.getWebViewsMapping(this.adb, opts);
28
+ const webviewsMapping = await webviewHelpers.getWebViewsMapping(this.adb, this.opts);
31
29
  return this.assignContexts(webviewsMapping);
32
30
  };
33
31
 
@@ -43,8 +41,7 @@ commands.setContext = async function setContext (name) {
43
41
  return;
44
42
  }
45
43
 
46
- const opts = Object.assign({isChromeSession: this.isChromeSession}, this.opts);
47
- const webviewsMapping = await webviewHelpers.getWebViewsMapping(this.adb, opts);
44
+ const webviewsMapping = await webviewHelpers.getWebViewsMapping(this.adb, this.opts);
48
45
  const contexts = this.assignContexts(webviewsMapping);
49
46
  // if the context we want doesn't exist, fail
50
47
  if (!_.includes(contexts, name)) {
@@ -106,8 +103,7 @@ commands.mobileGetContexts = async function mobileGetContexts () {
106
103
  androidDeviceSocket: this.opts.androidDeviceSocket,
107
104
  ensureWebviewsHavePages: true,
108
105
  webviewDevtoolsPort: this.opts.webviewDevtoolsPort,
109
- enableWebviewDetailsCollection: true,
110
- isChromeSession: this.isChromeSession
106
+ enableWebviewDetailsCollection: true
111
107
  };
112
108
  return await webviewHelpers.getWebViewsMapping(this.adb, opts);
113
109
  };
@@ -116,7 +112,7 @@ helpers.assignContexts = function assignContexts (webviewsMapping) {
116
112
  const opts = Object.assign({isChromeSession: this.isChromeSession}, this.opts);
117
113
  const webviews = webviewHelpers.parseWebviewNames(webviewsMapping, opts);
118
114
  this.contexts = [NATIVE_WIN, ...webviews];
119
- log.debug(`Available contexts: ${JSON.stringify(this.contexts)}`);
115
+ this.log.debug(`Available contexts: ${JSON.stringify(this.contexts)}`);
120
116
  return this.contexts;
121
117
  };
122
118
 
@@ -132,7 +128,7 @@ helpers.switchContext = async function switchContext (name, webviewsMapping) {
132
128
  // to true then kill chromedriver session using stopChromedriverProxies or
133
129
  // else simply suspend proxying to the latter
134
130
  if (this.opts.recreateChromeDriverSessions) {
135
- log.debug('recreateChromeDriverSessions set to true; killing existing chromedrivers');
131
+ this.log.debug('recreateChromeDriverSessions set to true; killing existing chromedrivers');
136
132
  await this.stopChromedriverProxies();
137
133
  } else {
138
134
  await this.suspendChromedriverProxy();
@@ -164,15 +160,15 @@ helpers.isWebContext = function isWebContext () {
164
160
 
165
161
  // Turn on proxying to an existing Chromedriver session or a new one
166
162
  helpers.startChromedriverProxy = async function startChromedriverProxy (context, webviewsMapping) {
167
- log.debug(`Connecting to chrome-backed webview context '${context}'`);
163
+ this.log.debug(`Connecting to chrome-backed webview context '${context}'`);
168
164
 
169
165
  let cd;
170
166
  if (this.sessionChromedrivers[context]) {
171
167
  // in the case where we've already set up a chromedriver for a context,
172
168
  // we want to reconnect to it, not create a whole new one
173
- log.debug(`Found existing Chromedriver for context '${context}'. Using it.`);
169
+ this.log.debug(`Found existing Chromedriver for context '${context}'. Using it.`);
174
170
  cd = this.sessionChromedrivers[context];
175
- await setupExistingChromedriver(cd);
171
+ await setupExistingChromedriver(this.log, cd);
176
172
  } else {
177
173
  let opts = _.cloneDeep(this.opts);
178
174
  opts.chromeUseRunningApp = true;
@@ -200,7 +196,7 @@ helpers.startChromedriverProxy = async function startChromedriverProxy (context,
200
196
  const appState = await this.queryAppState(knownPackage);
201
197
  if (_.includes([APP_STATE.RUNNING_IN_BACKGROUND, APP_STATE.RUNNING_IN_FOREGROUND], appState)) {
202
198
  opts.chromeAndroidPackage = knownPackage;
203
- log.debug(`Identified chromeAndroidPackage as '${opts.chromeAndroidPackage}' ` +
199
+ this.log.debug(`Identified chromeAndroidPackage as '${opts.chromeAndroidPackage}' ` +
204
200
  `for context '${context}' by querying states of Chrome app packages`);
205
201
  break;
206
202
  }
@@ -209,7 +205,7 @@ helpers.startChromedriverProxy = async function startChromedriverProxy (context,
209
205
  for (const wm of webviewsMapping) {
210
206
  if (wm.webviewName === context && _.has(wm?.info, 'Android-Package')) {
211
207
  opts.chromeAndroidPackage = wm.info['Android-Package'];
212
- log.debug(`Identified chromeAndroidPackage as '${opts.chromeAndroidPackage}' ` +
208
+ this.log.debug(`Identified chromeAndroidPackage as '${opts.chromeAndroidPackage}' ` +
213
209
  `for context '${context}' by CDP`);
214
210
  break;
215
211
  }
@@ -246,7 +242,7 @@ helpers.suspendChromedriverProxy = function suspendChromedriverProxy () {
246
242
 
247
243
  // Handle an out-of-band Chromedriver stop event
248
244
  helpers.onChromedriverStop = async function onChromedriverStop (context) {
249
- log.warn(`Chromedriver for context ${context} stopped unexpectedly`);
245
+ this.log.warn(`Chromedriver for context ${context} stopped unexpectedly`);
250
246
  if (context === this.curContext) {
251
247
  // we exited unexpectedly while automating the current context and so want
252
248
  // to shut down the session and respond with an error
@@ -255,8 +251,8 @@ helpers.onChromedriverStop = async function onChromedriverStop (context) {
255
251
  } else {
256
252
  // if a Chromedriver in the non-active context barfs, we don't really
257
253
  // care, we'll just make a new one next time we need the context.
258
- log.warn("Chromedriver quit unexpectedly, but it wasn't the active " +
259
- 'context, ignoring');
254
+ this.log.warn("Chromedriver quit unexpectedly, but it wasn't the active " +
255
+ 'context, ignoring');
260
256
  delete this.sessionChromedrivers[context];
261
257
  }
262
258
  };
@@ -267,13 +263,13 @@ helpers.stopChromedriverProxies = async function stopChromedriverProxies () {
267
263
  this.suspendChromedriverProxy(); // make sure we turn off the proxy flag
268
264
  for (let context of _.keys(this.sessionChromedrivers)) {
269
265
  let cd = this.sessionChromedrivers[context];
270
- log.debug(`Stopping chromedriver for context ${context}`);
266
+ this.log.debug(`Stopping chromedriver for context ${context}`);
271
267
  // stop listening for the stopped state event
272
268
  cd.removeAllListeners(Chromedriver.EVENT_CHANGED);
273
269
  try {
274
270
  await cd.stop();
275
271
  } catch (err) {
276
- log.warn(`Error stopping Chromedriver: ${err.message}`);
272
+ this.log.warn(`Error stopping Chromedriver: ${err.message}`);
277
273
  }
278
274
  delete this.sessionChromedrivers[context];
279
275
  }
@@ -290,10 +286,10 @@ helpers.shouldDismissChromeWelcome = function shouldDismissChromeWelcome () {
290
286
  };
291
287
 
292
288
  helpers.dismissChromeWelcome = async function dismissChromeWelcome () {
293
- log.info('Trying to dismiss Chrome welcome');
289
+ this.log.info('Trying to dismiss Chrome welcome');
294
290
  let activity = await this.getCurrentActivity();
295
291
  if (activity !== 'org.chromium.chrome.browser.firstrun.FirstRunActivity') {
296
- log.info('Chrome welcome dialog never showed up! Continuing');
292
+ this.log.info('Chrome welcome dialog never showed up! Continuing');
297
293
  return;
298
294
  }
299
295
  let el = await this.findElOrEls('id', 'com.android.chrome:id/terms_accept', false);
@@ -304,12 +300,12 @@ helpers.dismissChromeWelcome = async function dismissChromeWelcome () {
304
300
  } catch (e) {
305
301
  // DO NOTHING, THIS DEVICE DIDNT LAUNCH THE SIGNIN DIALOG
306
302
  // IT MUST BE A NON GMS DEVICE
307
- log.warn(`This device did not show Chrome SignIn dialog, ${e.message}`);
303
+ this.log.warn(`This device did not show Chrome SignIn dialog, ${e.message}`);
308
304
  }
309
305
  };
310
306
 
311
307
  helpers.startChromeSession = async function startChromeSession () {
312
- log.info('Starting a chrome-based browser session');
308
+ this.log.info('Starting a chrome-based browser session');
313
309
  let opts = _.cloneDeep(this.opts);
314
310
 
315
311
  const knownPackages = [
@@ -352,7 +348,7 @@ helpers.startChromeSession = async function startChromeSession () {
352
348
  * Internal library functions
353
349
  * -------------------------- */
354
350
 
355
- async function setupExistingChromedriver (chromedriver) {
351
+ async function setupExistingChromedriver (log, chromedriver) {
356
352
  // check the status by sending a simple window-based command to ChromeDriver
357
353
  // if there is an error, we want to recreate the ChromeDriver session
358
354
  if (!await chromedriver.hasWorkingWebview()) {
@@ -366,28 +362,29 @@ async function setupExistingChromedriver (chromedriver) {
366
362
  /**
367
363
  * Find a free port to have Chromedriver listen on.
368
364
  *
369
- * @param {array} [portSpec] - Array which is a list of ports. A list item may
365
+ * @param {array} portSpec - Array which is a list of ports. A list item may
370
366
  * also itself be an array of length 2 specifying a start and end port of
371
367
  * a range. Some valid port specs:
372
368
  * - [8000, 8001, 8002]
373
369
  * - [[8000, 8005]]
374
370
  * - [8000, [9000, 9100]]
371
+ * @param {Object?} log Logger instance
375
372
  *
376
373
  * @return {number} A free port
377
374
  */
378
- async function getChromedriverPort (portSpec) {
375
+ async function getChromedriverPort (portSpec, log = null) {
379
376
  const getPort = B.promisify(PortFinder.getPort, {context: PortFinder});
380
377
 
381
378
  // if the user didn't give us any specific information about chromedriver
382
379
  // port ranges, just find any free port
383
380
  if (!portSpec) {
384
381
  const port = await getPort();
385
- log.debug(`A port was not given, using random free port: ${port}`);
382
+ log?.debug(`A port was not given, using random free port: ${port}`);
386
383
  return port;
387
384
  }
388
385
 
389
386
  // otherwise find the free port based on a list or range provided by the user
390
- log.debug(`Finding a free port for chromedriver using spec ${JSON.stringify(portSpec)}`);
387
+ log?.debug(`Finding a free port for chromedriver using spec ${JSON.stringify(portSpec)}`);
391
388
  let foundPort = null;
392
389
  for (const potentialPort of portSpec) {
393
390
  let port, stopPort;
@@ -398,11 +395,11 @@ async function getChromedriverPort (portSpec) {
398
395
  stopPort = port;
399
396
  }
400
397
  try {
401
- log.debug(`Checking port range ${port}:${stopPort}`);
398
+ log?.debug(`Checking port range ${port}:${stopPort}`);
402
399
  foundPort = await getPort({port, stopPort});
403
400
  break;
404
401
  } catch (e) {
405
- log.debug(`Nothing in port range ${port}:${stopPort} was available`);
402
+ log?.debug(`Nothing in port range ${port}:${stopPort} was available`);
406
403
  }
407
404
  }
408
405
 
@@ -411,7 +408,7 @@ async function getChromedriverPort (portSpec) {
411
408
  `chromedriverPorts spec ${JSON.stringify(portSpec)}`);
412
409
  }
413
410
 
414
- log.debug(`Using free port ${foundPort} for chromedriver`);
411
+ log?.debug(`Using free port ${foundPort} for chromedriver`);
415
412
  return foundPort;
416
413
  }
417
414
 
@@ -419,27 +416,27 @@ helpers.isChromedriverAutodownloadEnabled = function isChromedriverAutodownloadE
419
416
  if (this.isFeatureEnabled(CHROMEDRIVER_AUTODOWNLOAD_FEATURE)) {
420
417
  return true;
421
418
  }
422
- log.debug(`Automated Chromedriver download is disabled. ` +
419
+ this?.log?.debug(`Automated Chromedriver download is disabled. ` +
423
420
  `Use '${CHROMEDRIVER_AUTODOWNLOAD_FEATURE}' server feature to enable it`);
424
421
  return false;
425
422
  };
426
423
 
427
424
  helpers.setupNewChromedriver = async function setupNewChromedriver (opts, curDeviceId, adb, context = null) {
428
425
  if (opts.chromeDriverPort) {
429
- log.warn(`The 'chromeDriverPort' capability is deprecated. Please use 'chromedriverPort' instead`);
426
+ this?.log?.warn(`The 'chromeDriverPort' capability is deprecated. Please use 'chromedriverPort' instead`);
430
427
  opts.chromedriverPort = opts.chromeDriverPort;
431
428
  }
432
429
 
433
430
  if (opts.chromedriverPort) {
434
- log.debug(`Using user-specified port ${opts.chromedriverPort} for chromedriver`);
431
+ this?.log?.debug(`Using user-specified port ${opts.chromedriverPort} for chromedriver`);
435
432
  } else {
436
433
  // if a single port wasn't given, we'll look for a free one
437
- opts.chromedriverPort = await getChromedriverPort(opts.chromedriverPorts);
434
+ opts.chromedriverPort = await getChromedriverPort(opts.chromedriverPorts, this?.log);
438
435
  }
439
436
 
440
437
  const details = context ? webviewHelpers.getWebviewDetails(adb, context) : undefined;
441
438
  if (!_.isEmpty(details)) {
442
- log.debug('Passing web view details to the Chromedriver constructor: ' +
439
+ this?.log?.debug('Passing web view details to the Chromedriver constructor: ' +
443
440
  JSON.stringify(details, null, 2));
444
441
  }
445
442
 
@@ -455,8 +452,7 @@ helpers.setupNewChromedriver = async function setupNewChromedriver (opts, curDev
455
452
  useSystemExecutable: opts.chromedriverUseSystemExecutable,
456
453
  disableBuildCheck: opts.chromedriverDisableBuildCheck,
457
454
  details,
458
- isAutodownloadEnabled: (this || {}).isChromedriverAutodownloadEnabled
459
- ? this.isChromedriverAutodownloadEnabled() : undefined,
455
+ isAutodownloadEnabled: this?.isChromedriverAutodownloadEnabled?.()
460
456
  });
461
457
 
462
458
  // make sure there are chromeOptions
@@ -465,13 +461,13 @@ helpers.setupNewChromedriver = async function setupNewChromedriver (opts, curDev
465
461
  // and strip the prefix
466
462
  for (const opt of _.keys(opts)) {
467
463
  if (opt.endsWith(':chromeOptions')) {
468
- log.warn(`Merging '${opt}' into 'chromeOptions'. This may cause unexpected behavior`);
464
+ this?.log?.warn(`Merging '${opt}' into 'chromeOptions'. This may cause unexpected behavior`);
469
465
  _.merge(opts.chromeOptions, opts[opt]);
470
466
  }
471
467
  }
472
468
 
473
469
  const caps = webviewHelpers.createChromedriverCaps(opts, curDeviceId, details);
474
- log.debug(`Before starting chromedriver, androidPackage is '${caps.chromeOptions.androidPackage}'`);
470
+ this?.log?.debug(`Before starting chromedriver, androidPackage is '${caps.chromeOptions.androidPackage}'`);
475
471
  await chromedriver.start(caps);
476
472
  return chromedriver;
477
473
  };
@@ -1,6 +1,3 @@
1
- import log from '../logger';
2
-
3
-
4
1
  const commands = {};
5
2
 
6
3
 
@@ -11,7 +8,7 @@ commands.endCoverage = async function endCoverage (intentToBroadcast, ecOnDevice
11
8
  await this.adb.broadcastProcessEnd(intentToBroadcast, this.appProcess);
12
9
  return await this.pullFile(ecOnDevicePath);
13
10
  } catch (err) {
14
- log.warn(`Error ending test coverage: ${err.message}`);
11
+ this.log.warn(`Error ending test coverage: ${err.message}`);
15
12
  }
16
13
  return '';
17
14
  };
@@ -1,6 +1,5 @@
1
1
  import androidHelpers from '../android-helpers';
2
2
  import { retryInterval } from 'asyncbox';
3
- import logger from '../logger';
4
3
  import { util } from '@appium/support';
5
4
 
6
5
 
@@ -90,12 +89,12 @@ commands.clear = async function clear (elementId) {
90
89
  length = 100;
91
90
  }
92
91
  await this.click(elementId);
93
- logger.debug(`Sending up to ${length} clear characters to device`);
92
+ this.log.debug(`Sending up to ${length} clear characters to device`);
94
93
  return await retryInterval(5, 500, async () => {
95
94
  let remainingLength = length;
96
95
  while (remainingLength > 0) {
97
96
  let lengthToSend = remainingLength < 50 ? remainingLength : 50;
98
- logger.debug(`Sending ${lengthToSend} clear characters to device`);
97
+ this.log.debug(`Sending ${lengthToSend} clear characters to device`);
99
98
  await this.adb.clearTextField(lengthToSend);
100
99
  remainingLength -= lengthToSend;
101
100
  }
@@ -1,4 +1,4 @@
1
- import log from '../logger';
1
+ import { errors } from '@appium/base-driver';
2
2
 
3
3
  const EMU_CONSOLE_FEATURE = 'emulator_console';
4
4
 
@@ -37,7 +37,7 @@ commands.mobileExecEmuConsoleCommand = async function mobileExecEmuConsoleComman
37
37
  } = opts;
38
38
 
39
39
  if (!command) {
40
- log.errorAndThrow(`The 'command' argument is mandatory`);
40
+ throw new errors.InvalidArgumentError(`The 'command' argument is mandatory`);
41
41
  }
42
42
 
43
43
  return await this.adb.execEmuConsoleCommand(command, {
@@ -1,12 +1,11 @@
1
1
  import _ from 'lodash';
2
2
  import { errors, PROTOCOLS } from '@appium/base-driver';
3
- import logger from '../logger';
4
3
 
5
- let extensions = {};
4
+ const extensions = {};
6
5
 
7
6
  extensions.execute = async function execute (script, args) {
8
7
  if (script.match(/^mobile:/)) {
9
- logger.info(`Executing native command '${script}'`);
8
+ this.log.info(`Executing native command '${script}'`);
10
9
  script = script.replace(/^mobile:/, '').trim();
11
10
  return await this.executeMobile(script, _.isArray(args) ? args[0] : args);
12
11
  }
@@ -1,7 +1,7 @@
1
1
  import _ from 'lodash';
2
- import { fs, util, zip, tempDir} from '@appium/support';
3
- import log from '../logger';
2
+ import { fs, util, zip, tempDir } from '@appium/support';
4
3
  import path from 'path';
4
+ import { errors } from '@appium/base-driver';
5
5
 
6
6
 
7
7
  const CONTAINER_PATH_MARKER = '@';
@@ -24,8 +24,8 @@ const commands = {};
24
24
  function parseContainerPath (remotePath) {
25
25
  const match = CONTAINER_PATH_PATTERN.exec(remotePath);
26
26
  if (!match) {
27
- log.errorAndThrow(`It is expected that package identifier is separated from the relative path with a single slash. ` +
28
- `'${remotePath}' is given instead`);
27
+ throw new Error(`It is expected that package identifier is separated from the relative path with a single slash. ` +
28
+ `'${remotePath}' is given instead`);
29
29
  }
30
30
  return [match[1], path.posix.resolve(`/data/data/${match[1]}`, match[2])];
31
31
  }
@@ -36,10 +36,11 @@ function parseContainerPath (remotePath) {
36
36
  * Exceptions are ignored and written into the log.
37
37
  *
38
38
  * @param {ADB} adb ADB instance
39
+ * @param {Object?} log Logger instance
39
40
  * @param {string} remotePath The file/folder path on the remote device
40
41
  */
41
- async function scanMedia (adb, remotePath) {
42
- log.debug(`Performing media scan of '${remotePath}'`);
42
+ async function scanMedia (adb, remotePath, log = null) {
43
+ log?.debug(`Performing media scan of '${remotePath}'`);
43
44
  try {
44
45
  // https://github.com/appium/appium/issues/16184
45
46
  if (await adb.getApiLevel() >= 29) {
@@ -52,7 +53,7 @@ async function scanMedia (adb, remotePath) {
52
53
  ]);
53
54
  }
54
55
  } catch (e) {
55
- log.warn(`Ignoring an unexpected error upon media scanning of '${remotePath}': ${e.stderr || e.message}`);
56
+ log?.warn(`Ignoring an unexpected error upon media scanning of '${remotePath}': ${e.stderr || e.message}`);
56
57
  }
57
58
  }
58
59
 
@@ -79,13 +80,13 @@ function escapePath (p) {
79
80
  */
80
81
  commands.pullFile = async function pullFile (remotePath) {
81
82
  if (remotePath.endsWith('/')) {
82
- log.errorAndThrow(`It is expected that remote path points to a file and not to a folder. ` +
83
- `'${remotePath}' is given instead`);
83
+ throw new errors.InvalidArgumentError(`It is expected that remote path points to a file and not to a folder. ` +
84
+ `'${remotePath}' is given instead`);
84
85
  }
85
86
  let tmpDestination = null;
86
87
  if (remotePath.startsWith(CONTAINER_PATH_MARKER)) {
87
88
  const [packageId, pathInContainer] = parseContainerPath(remotePath);
88
- log.debug(`Parsed package identifier '${packageId}' from '${remotePath}'. Will get the data from '${pathInContainer}'`);
89
+ this.log.debug(`Parsed package identifier '${packageId}' from '${remotePath}'. Will get the data from '${pathInContainer}'`);
89
90
  tmpDestination = `/data/local/tmp/${path.posix.basename(pathInContainer)}`;
90
91
  try {
91
92
  await this.adb.shell(['run-as', packageId, `chmod 777 '${escapePath(pathInContainer)}'`]);
@@ -94,7 +95,7 @@ commands.pullFile = async function pullFile (remotePath) {
94
95
  `cp -f '${escapePath(pathInContainer)}' '${escapePath(tmpDestination)}'`
95
96
  ]);
96
97
  } catch (e) {
97
- log.errorAndThrow(`Cannot access the container of '${packageId}' application. ` +
98
+ this.log.errorAndThrow(`Cannot access the container of '${packageId}' application. ` +
98
99
  `Is the application installed and has 'debuggable' build option set to true? ` +
99
100
  `Original error: ${e.message}`);
100
101
  }
@@ -126,8 +127,10 @@ commands.pullFile = async function pullFile (remotePath) {
126
127
  */
127
128
  commands.pushFile = async function pushFile (remotePath, base64Data) {
128
129
  if (remotePath.endsWith('/')) {
129
- log.errorAndThrow(`It is expected that remote path points to a file and not to a folder. ` +
130
- `'${remotePath}' is given instead`);
130
+ throw new errors.InvalidArgumentError(
131
+ `It is expected that remote path points to a file and not to a folder. ` +
132
+ `'${remotePath}' is given instead`
133
+ );
131
134
  }
132
135
  const localFile = await tempDir.path({prefix: 'appium', suffix: '.tmp'});
133
136
  if (_.isArray(base64Data)) {
@@ -141,7 +144,8 @@ commands.pushFile = async function pushFile (remotePath, base64Data) {
141
144
  await fs.writeFile(localFile, content.toString('binary'), 'binary');
142
145
  if (remotePath.startsWith(CONTAINER_PATH_MARKER)) {
143
146
  const [packageId, pathInContainer] = parseContainerPath(remotePath);
144
- log.debug(`Parsed package identifier '${packageId}' from '${remotePath}'. Will put the data into '${pathInContainer}'`);
147
+ this.log.debug(`Parsed package identifier '${packageId}' from '${remotePath}'. ` +
148
+ `Will put the data into '${pathInContainer}'`);
145
149
  tmpDestination = `/data/local/tmp/${path.posix.basename(pathInContainer)}`;
146
150
  try {
147
151
  await this.adb.shell(
@@ -155,7 +159,7 @@ commands.pushFile = async function pushFile (remotePath, base64Data) {
155
159
  `cp -f '${escapePath(tmpDestination)}' '${escapePath(pathInContainer)}'`
156
160
  ]);
157
161
  } catch (e) {
158
- log.errorAndThrow(`Cannot access the container of '${packageId}' application. ` +
162
+ this.log.errorAndThrow(`Cannot access the container of '${packageId}' application. ` +
159
163
  `Is the application installed and has 'debuggable' build option set to true? ` +
160
164
  `Original error: ${e.message}`);
161
165
  }
@@ -165,7 +169,7 @@ commands.pushFile = async function pushFile (remotePath, base64Data) {
165
169
 
166
170
  // if we have pushed a file, it might be a media file, so ensure that
167
171
  // apps know about it
168
- await scanMedia(this.adb, remotePath);
172
+ await scanMedia(this.adb, remotePath, this.log);
169
173
  }
170
174
  } finally {
171
175
  if (await fs.exists(localFile)) {
@@ -224,7 +228,7 @@ async function deleteFileOrFolder (adb, remotePath) {
224
228
  let pkgId = null;
225
229
  if (remotePath.startsWith(CONTAINER_PATH_MARKER)) {
226
230
  const [packageId, pathInContainer] = parseContainerPath(remotePath);
227
- log.debug(`Parsed package identifier '${packageId}' from '${remotePath}'`);
231
+ this.log.debug(`Parsed package identifier '${packageId}' from '${remotePath}'`);
228
232
  dstPath = pathInContainer;
229
233
  pkgId = packageId;
230
234
  }
@@ -233,22 +237,22 @@ async function deleteFileOrFolder (adb, remotePath) {
233
237
  try {
234
238
  await adb.shell(['run-as', pkgId, 'ls']);
235
239
  } catch (e) {
236
- log.errorAndThrow(`Cannot access the container of '${pkgId}' application. ` +
240
+ this.log.errorAndThrow(`Cannot access the container of '${pkgId}' application. ` +
237
241
  `Is the application installed and has 'debuggable' build option set to true? ` +
238
242
  `Original error: ${e.message}`);
239
243
  }
240
244
  }
241
245
 
242
246
  if (!await isPresent(dstPath, pkgId)) {
243
- log.info(`The item at '${dstPath}' does not exist. Perhaps, already deleted?`);
247
+ this.log.info(`The item at '${dstPath}' does not exist. Perhaps, already deleted?`);
244
248
  return false;
245
249
  }
246
250
 
247
251
  const expectsFile = !remotePath.endsWith('/');
248
252
  if (expectsFile && !await isFile(dstPath, pkgId)) {
249
- log.errorAndThrow(`The item at '${dstPath}' is not a file`);
253
+ this.log.errorAndThrow(`The item at '${dstPath}' is not a file`);
250
254
  } else if (!expectsFile && !await isDir(dstPath, pkgId)) {
251
- log.errorAndThrow(`The item at '${dstPath}' is not a folder`);
255
+ this.log.errorAndThrow(`The item at '${dstPath}' is not a folder`);
252
256
  }
253
257
 
254
258
  if (pkgId) {
@@ -258,7 +262,7 @@ async function deleteFileOrFolder (adb, remotePath) {
258
262
  await adb.shell(['rm', `-f${expectsFile ? '' : 'r'}`, dstPath]);
259
263
  }
260
264
  if (await isPresent(dstPath, pkgId)) {
261
- log.errorAndThrow(`The item at '${dstPath}' still exists after being deleted. ` +
265
+ this.log.errorAndThrow(`The item at '${dstPath}' still exists after being deleted. ` +
262
266
  `Is it writable?`);
263
267
  }
264
268
  return true;
@@ -283,11 +287,13 @@ async function deleteFileOrFolder (adb, remotePath) {
283
287
  commands.mobileDeleteFile = async function mobileDeleteFile (opts = {}) {
284
288
  const {remotePath} = opts;
285
289
  if (!remotePath) {
286
- log.errorAndThrow(`The 'remotePath' argument is mandatory`);
290
+ throw new errors.InvalidArgumentError(`The 'remotePath' argument is mandatory`);
287
291
  }
288
292
  if (remotePath.endsWith('/')) {
289
- log.errorAndThrow(`It is expected that remote path points to a folder and not to a file. ` +
290
- `'${remotePath}' is given instead`);
293
+ throw new errors.InvalidArgumentError(
294
+ `It is expected that remote path points to a folder and not to a file. ` +
295
+ `'${remotePath}' is given instead`
296
+ );
291
297
  }
292
298
  return await deleteFileOrFolder(this.adb, remotePath);
293
299
  };
@@ -1,9 +1,9 @@
1
1
  import _ from 'lodash';
2
2
  import androidHelpers from '../android-helpers';
3
3
  import { util } from '@appium/support';
4
- import log from '../logger';
5
4
  import moment from 'moment';
6
5
  import { longSleep } from 'asyncbox';
6
+ import { errors } from '@appium/base-driver';
7
7
 
8
8
  const MOMENT_FORMAT_ISO8601 = 'YYYY-MM-DDTHH:mm:ssZ';
9
9
 
@@ -36,13 +36,13 @@ commands.doSendKeys = async function doSendKeys (params) {
36
36
  * @return {string} Formatted datetime string or the raw command output if formatting fails
37
37
  */
38
38
  commands.getDeviceTime = async function getDeviceTime (format = MOMENT_FORMAT_ISO8601) {
39
- log.debug('Attempting to capture android device date and time. ' +
39
+ this.log.debug('Attempting to capture android device date and time. ' +
40
40
  `The format specifier is '${format}'`);
41
41
  const deviceTimestamp = (await this.adb.shell(['date', '+%Y-%m-%dT%T%z'])).trim();
42
- log.debug(`Got device timestamp: ${deviceTimestamp}`);
42
+ this.log.debug(`Got device timestamp: ${deviceTimestamp}`);
43
43
  const parsedTimestamp = moment.utc(deviceTimestamp, 'YYYY-MM-DDTHH:mm:ssZZ');
44
44
  if (!parsedTimestamp.isValid()) {
45
- log.warn('Cannot parse the returned timestamp. Returning as is');
45
+ this.log.warn('Cannot parse the returned timestamp. Returning as is');
46
46
  return deviceTimestamp;
47
47
  }
48
48
  return parsedTimestamp.utcOffset(parsedTimestamp._tzm || 0).format(format);
@@ -128,7 +128,7 @@ commands.background = async function background (seconds) {
128
128
  const progressCb = ({elapsedMs, progress}) => {
129
129
  const waitSecs = (elapsedMs / 1000).toFixed(0);
130
130
  const progressPct = (progress * 100).toFixed(2);
131
- log.debug(`Waited ${waitSecs}s so far (${progressPct}%)`);
131
+ this.log.debug(`Waited ${waitSecs}s so far (${progressPct}%)`);
132
132
  };
133
133
  await longSleep(sleepMs, {thresholdMs, intervalMs, progressCb});
134
134
 
@@ -138,7 +138,7 @@ commands.background = async function background (seconds) {
138
138
  args = this._cachedActivityArgs[`${appPackage}/${appActivity}`];
139
139
  } else {
140
140
  try {
141
- log.debug(`Activating app '${appPackage}' in order to restore it`);
141
+ this.log.debug(`Activating app '${appPackage}' in order to restore it`);
142
142
  await this.activateApp(appPackage);
143
143
  return true;
144
144
  } catch (ign) {}
@@ -165,14 +165,14 @@ commands.background = async function background (seconds) {
165
165
  stopApp: false};
166
166
  }
167
167
  args = await util.filterObject(args);
168
- log.debug(`Bringing application back to foreground with arguments: ${JSON.stringify(args)}`);
168
+ this.log.debug(`Bringing application back to foreground with arguments: ${JSON.stringify(args)}`);
169
169
  return await this.adb.startApp(args);
170
170
  };
171
171
 
172
172
  commands.getStrings = async function getStrings (language) {
173
173
  if (!language) {
174
174
  language = await this.adb.getDeviceLanguage();
175
- log.info(`No language specified, returning strings for: ${language}`);
175
+ this.log.info(`No language specified, returning strings for: ${language}`);
176
176
  }
177
177
 
178
178
  // Clients require the resulting mapping to have both keys
@@ -207,7 +207,7 @@ commands.launchApp = async function launchApp () {
207
207
  commands.startActivity = async function startActivity (appPackage, appActivity,
208
208
  appWaitPackage, appWaitActivity, intentAction, intentCategory, intentFlags,
209
209
  optionalIntentArguments, dontStopAppOnReset) {
210
- log.debug(`Starting package '${appPackage}' and activity '${appActivity}'`);
210
+ this.log.debug(`Starting package '${appPackage}' and activity '${appActivity}'`);
211
211
 
212
212
  // dontStopAppOnReset is both an argument here, and a desired capability
213
213
  // if the argument is set, use it, otherwise use the cap
@@ -277,7 +277,7 @@ commands.getDisplayDensity = async function getDisplayDensity () {
277
277
  if (!isNaN(val)) {
278
278
  return val;
279
279
  }
280
- log.debug(`Parsed density value was NaN: "${out}"`);
280
+ this.log.debug(`Parsed density value was NaN: "${out}"`);
281
281
  }
282
282
  // fallback to trying property for emulators
283
283
  out = await this.adb.shell(['getprop', 'qemu.sf.lcd_density']);
@@ -286,16 +286,16 @@ commands.getDisplayDensity = async function getDisplayDensity () {
286
286
  if (!isNaN(val)) {
287
287
  return val;
288
288
  }
289
- log.debug(`Parsed density value was NaN: "${out}"`);
289
+ this.log.debug(`Parsed density value was NaN: "${out}"`);
290
290
  }
291
291
  // couldn't get anything, so error out
292
- log.errorAndThrow('Failed to get display density property.');
292
+ this.log.errorAndThrow('Failed to get display density property.');
293
293
  };
294
294
 
295
295
  commands.mobilePerformEditorAction = async function mobilePerformEditorAction (opts = {}) {
296
296
  const {action} = opts;
297
297
  if (!util.hasValue(action)) {
298
- log.errorAndThrow(`'action' argument is required`);
298
+ throw new errors.InvalidArgumentError(`'action' argument is required`);
299
299
  }
300
300
 
301
301
  await this.adb.performEditorAction(action);
@@ -328,7 +328,7 @@ commands.mobileChangePermissions = async function mobileChangePermissions (opts
328
328
  action = PERMISSION_ACTION.GRANT,
329
329
  } = opts;
330
330
  if (!util.hasValue(permissions)) {
331
- log.errorAndThrow(`'permissions' argument is required`);
331
+ throw new errors.InvalidArgumentError(`'permissions' argument is required`);
332
332
  }
333
333
 
334
334
  let actionFunc;
@@ -340,9 +340,8 @@ commands.mobileChangePermissions = async function mobileChangePermissions (opts
340
340
  actionFunc = (appPackage, permission) => this.adb.revokePermission(appPackage, permission);
341
341
  break;
342
342
  default:
343
- log.errorAndThrow(`Unknown action '${action}'. ` +
343
+ throw new errors.InvalidArgumentError(`Unknown action '${action}'. ` +
344
344
  `Only ${JSON.stringify(_.values(PERMISSION_ACTION))} actions are supported`);
345
- break;
346
345
  }
347
346
  for (const permission of (_.isArray(permissions) ? permissions : [permissions])) {
348
347
  await actionFunc(appPackage, permission);
@@ -389,9 +388,8 @@ commands.mobileGetPermissions = async function mobileGetPermissions (opts = {})
389
388
  actionFunc = (appPackage) => this.adb.getDeniedPermissions(appPackage);
390
389
  break;
391
390
  default:
392
- log.errorAndThrow(`Unknown permissions type '${type}'. ` +
391
+ throw new errors.InvalidArgumentError(`Unknown permissions type '${type}'. ` +
393
392
  `Only ${JSON.stringify(_.values(PERMISSIONS_TYPE))} types are supported`);
394
- break;
395
393
  }
396
394
  return await actionFunc(appPackage);
397
395
  };