appium-android-driver 5.0.5 → 5.0.8
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/README.md +0 -1
- package/build/lib/commands/actions.js +16 -20
- package/build/lib/commands/app-management.js +10 -18
- package/build/lib/commands/context.js +43 -56
- package/build/lib/commands/coverage.js +2 -6
- package/build/lib/commands/element.js +3 -9
- package/build/lib/commands/emu-console.js +3 -5
- package/build/lib/commands/execute.js +3 -6
- package/build/lib/commands/file-actions.js +22 -29
- package/build/lib/commands/general.js +18 -30
- package/build/lib/commands/ime.js +8 -21
- package/build/lib/commands/network.js +14 -27
- package/build/lib/commands/performance.js +3 -6
- package/build/lib/commands/recordscreen.js +27 -38
- package/build/lib/commands/shell.js +6 -7
- package/build/lib/commands/streamscreen.js +29 -43
- package/build/lib/commands/system-bars.js +10 -16
- package/build/lib/commands/touch.js +4 -10
- package/build/lib/driver.js +36 -60
- package/build/lib/webview-helpers.js +3 -3
- package/lib/commands/actions.js +18 -19
- package/lib/commands/app-management.js +10 -11
- package/lib/commands/context.js +33 -34
- package/lib/commands/coverage.js +1 -4
- package/lib/commands/element.js +2 -3
- package/lib/commands/emu-console.js +2 -2
- package/lib/commands/execute.js +2 -3
- package/lib/commands/file-actions.js +31 -25
- package/lib/commands/general.js +16 -18
- package/lib/commands/ime.js +7 -8
- package/lib/commands/network.js +15 -16
- package/lib/commands/performance.js +2 -3
- package/lib/commands/recordscreen.js +23 -22
- package/lib/commands/shell.js +6 -6
- package/lib/commands/streamscreen.js +22 -23
- package/lib/commands/system-bars.js +11 -10
- package/lib/commands/touch.js +3 -4
- package/lib/driver.js +35 -36
- package/lib/webview-helpers.js +3 -1
- package/package.json +2 -2
package/lib/commands/context.js
CHANGED
|
@@ -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';
|
|
@@ -116,7 +115,7 @@ helpers.assignContexts = function assignContexts (webviewsMapping) {
|
|
|
116
115
|
const opts = Object.assign({isChromeSession: this.isChromeSession}, this.opts);
|
|
117
116
|
const webviews = webviewHelpers.parseWebviewNames(webviewsMapping, opts);
|
|
118
117
|
this.contexts = [NATIVE_WIN, ...webviews];
|
|
119
|
-
log.debug(`Available contexts: ${JSON.stringify(this.contexts)}`);
|
|
118
|
+
this.log.debug(`Available contexts: ${JSON.stringify(this.contexts)}`);
|
|
120
119
|
return this.contexts;
|
|
121
120
|
};
|
|
122
121
|
|
|
@@ -132,7 +131,7 @@ helpers.switchContext = async function switchContext (name, webviewsMapping) {
|
|
|
132
131
|
// to true then kill chromedriver session using stopChromedriverProxies or
|
|
133
132
|
// else simply suspend proxying to the latter
|
|
134
133
|
if (this.opts.recreateChromeDriverSessions) {
|
|
135
|
-
log.debug('recreateChromeDriverSessions set to true; killing existing chromedrivers');
|
|
134
|
+
this.log.debug('recreateChromeDriverSessions set to true; killing existing chromedrivers');
|
|
136
135
|
await this.stopChromedriverProxies();
|
|
137
136
|
} else {
|
|
138
137
|
await this.suspendChromedriverProxy();
|
|
@@ -164,15 +163,15 @@ helpers.isWebContext = function isWebContext () {
|
|
|
164
163
|
|
|
165
164
|
// Turn on proxying to an existing Chromedriver session or a new one
|
|
166
165
|
helpers.startChromedriverProxy = async function startChromedriverProxy (context, webviewsMapping) {
|
|
167
|
-
log.debug(`Connecting to chrome-backed webview context '${context}'`);
|
|
166
|
+
this.log.debug(`Connecting to chrome-backed webview context '${context}'`);
|
|
168
167
|
|
|
169
168
|
let cd;
|
|
170
169
|
if (this.sessionChromedrivers[context]) {
|
|
171
170
|
// in the case where we've already set up a chromedriver for a context,
|
|
172
171
|
// we want to reconnect to it, not create a whole new one
|
|
173
|
-
log.debug(`Found existing Chromedriver for context '${context}'. Using it.`);
|
|
172
|
+
this.log.debug(`Found existing Chromedriver for context '${context}'. Using it.`);
|
|
174
173
|
cd = this.sessionChromedrivers[context];
|
|
175
|
-
await setupExistingChromedriver(cd);
|
|
174
|
+
await setupExistingChromedriver(this.log, cd);
|
|
176
175
|
} else {
|
|
177
176
|
let opts = _.cloneDeep(this.opts);
|
|
178
177
|
opts.chromeUseRunningApp = true;
|
|
@@ -200,7 +199,7 @@ helpers.startChromedriverProxy = async function startChromedriverProxy (context,
|
|
|
200
199
|
const appState = await this.queryAppState(knownPackage);
|
|
201
200
|
if (_.includes([APP_STATE.RUNNING_IN_BACKGROUND, APP_STATE.RUNNING_IN_FOREGROUND], appState)) {
|
|
202
201
|
opts.chromeAndroidPackage = knownPackage;
|
|
203
|
-
log.debug(`Identified chromeAndroidPackage as '${opts.chromeAndroidPackage}' ` +
|
|
202
|
+
this.log.debug(`Identified chromeAndroidPackage as '${opts.chromeAndroidPackage}' ` +
|
|
204
203
|
`for context '${context}' by querying states of Chrome app packages`);
|
|
205
204
|
break;
|
|
206
205
|
}
|
|
@@ -209,7 +208,7 @@ helpers.startChromedriverProxy = async function startChromedriverProxy (context,
|
|
|
209
208
|
for (const wm of webviewsMapping) {
|
|
210
209
|
if (wm.webviewName === context && _.has(wm?.info, 'Android-Package')) {
|
|
211
210
|
opts.chromeAndroidPackage = wm.info['Android-Package'];
|
|
212
|
-
log.debug(`Identified chromeAndroidPackage as '${opts.chromeAndroidPackage}' ` +
|
|
211
|
+
this.log.debug(`Identified chromeAndroidPackage as '${opts.chromeAndroidPackage}' ` +
|
|
213
212
|
`for context '${context}' by CDP`);
|
|
214
213
|
break;
|
|
215
214
|
}
|
|
@@ -246,7 +245,7 @@ helpers.suspendChromedriverProxy = function suspendChromedriverProxy () {
|
|
|
246
245
|
|
|
247
246
|
// Handle an out-of-band Chromedriver stop event
|
|
248
247
|
helpers.onChromedriverStop = async function onChromedriverStop (context) {
|
|
249
|
-
log.warn(`Chromedriver for context ${context} stopped unexpectedly`);
|
|
248
|
+
this.log.warn(`Chromedriver for context ${context} stopped unexpectedly`);
|
|
250
249
|
if (context === this.curContext) {
|
|
251
250
|
// we exited unexpectedly while automating the current context and so want
|
|
252
251
|
// to shut down the session and respond with an error
|
|
@@ -255,8 +254,8 @@ helpers.onChromedriverStop = async function onChromedriverStop (context) {
|
|
|
255
254
|
} else {
|
|
256
255
|
// if a Chromedriver in the non-active context barfs, we don't really
|
|
257
256
|
// 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
|
-
|
|
257
|
+
this.log.warn("Chromedriver quit unexpectedly, but it wasn't the active " +
|
|
258
|
+
'context, ignoring');
|
|
260
259
|
delete this.sessionChromedrivers[context];
|
|
261
260
|
}
|
|
262
261
|
};
|
|
@@ -267,13 +266,13 @@ helpers.stopChromedriverProxies = async function stopChromedriverProxies () {
|
|
|
267
266
|
this.suspendChromedriverProxy(); // make sure we turn off the proxy flag
|
|
268
267
|
for (let context of _.keys(this.sessionChromedrivers)) {
|
|
269
268
|
let cd = this.sessionChromedrivers[context];
|
|
270
|
-
log.debug(`Stopping chromedriver for context ${context}`);
|
|
269
|
+
this.log.debug(`Stopping chromedriver for context ${context}`);
|
|
271
270
|
// stop listening for the stopped state event
|
|
272
271
|
cd.removeAllListeners(Chromedriver.EVENT_CHANGED);
|
|
273
272
|
try {
|
|
274
273
|
await cd.stop();
|
|
275
274
|
} catch (err) {
|
|
276
|
-
log.warn(`Error stopping Chromedriver: ${err.message}`);
|
|
275
|
+
this.log.warn(`Error stopping Chromedriver: ${err.message}`);
|
|
277
276
|
}
|
|
278
277
|
delete this.sessionChromedrivers[context];
|
|
279
278
|
}
|
|
@@ -290,10 +289,10 @@ helpers.shouldDismissChromeWelcome = function shouldDismissChromeWelcome () {
|
|
|
290
289
|
};
|
|
291
290
|
|
|
292
291
|
helpers.dismissChromeWelcome = async function dismissChromeWelcome () {
|
|
293
|
-
log.info('Trying to dismiss Chrome welcome');
|
|
292
|
+
this.log.info('Trying to dismiss Chrome welcome');
|
|
294
293
|
let activity = await this.getCurrentActivity();
|
|
295
294
|
if (activity !== 'org.chromium.chrome.browser.firstrun.FirstRunActivity') {
|
|
296
|
-
log.info('Chrome welcome dialog never showed up! Continuing');
|
|
295
|
+
this.log.info('Chrome welcome dialog never showed up! Continuing');
|
|
297
296
|
return;
|
|
298
297
|
}
|
|
299
298
|
let el = await this.findElOrEls('id', 'com.android.chrome:id/terms_accept', false);
|
|
@@ -304,12 +303,12 @@ helpers.dismissChromeWelcome = async function dismissChromeWelcome () {
|
|
|
304
303
|
} catch (e) {
|
|
305
304
|
// DO NOTHING, THIS DEVICE DIDNT LAUNCH THE SIGNIN DIALOG
|
|
306
305
|
// IT MUST BE A NON GMS DEVICE
|
|
307
|
-
log.warn(`This device did not show Chrome SignIn dialog, ${e.message}`);
|
|
306
|
+
this.log.warn(`This device did not show Chrome SignIn dialog, ${e.message}`);
|
|
308
307
|
}
|
|
309
308
|
};
|
|
310
309
|
|
|
311
310
|
helpers.startChromeSession = async function startChromeSession () {
|
|
312
|
-
log.info('Starting a chrome-based browser session');
|
|
311
|
+
this.log.info('Starting a chrome-based browser session');
|
|
313
312
|
let opts = _.cloneDeep(this.opts);
|
|
314
313
|
|
|
315
314
|
const knownPackages = [
|
|
@@ -352,7 +351,7 @@ helpers.startChromeSession = async function startChromeSession () {
|
|
|
352
351
|
* Internal library functions
|
|
353
352
|
* -------------------------- */
|
|
354
353
|
|
|
355
|
-
async function setupExistingChromedriver (chromedriver) {
|
|
354
|
+
async function setupExistingChromedriver (log, chromedriver) {
|
|
356
355
|
// check the status by sending a simple window-based command to ChromeDriver
|
|
357
356
|
// if there is an error, we want to recreate the ChromeDriver session
|
|
358
357
|
if (!await chromedriver.hasWorkingWebview()) {
|
|
@@ -366,28 +365,29 @@ async function setupExistingChromedriver (chromedriver) {
|
|
|
366
365
|
/**
|
|
367
366
|
* Find a free port to have Chromedriver listen on.
|
|
368
367
|
*
|
|
369
|
-
* @param {array}
|
|
368
|
+
* @param {array} portSpec - Array which is a list of ports. A list item may
|
|
370
369
|
* also itself be an array of length 2 specifying a start and end port of
|
|
371
370
|
* a range. Some valid port specs:
|
|
372
371
|
* - [8000, 8001, 8002]
|
|
373
372
|
* - [[8000, 8005]]
|
|
374
373
|
* - [8000, [9000, 9100]]
|
|
374
|
+
* @param {Object?} log Logger instance
|
|
375
375
|
*
|
|
376
376
|
* @return {number} A free port
|
|
377
377
|
*/
|
|
378
|
-
async function getChromedriverPort (portSpec) {
|
|
378
|
+
async function getChromedriverPort (portSpec, log = null) {
|
|
379
379
|
const getPort = B.promisify(PortFinder.getPort, {context: PortFinder});
|
|
380
380
|
|
|
381
381
|
// if the user didn't give us any specific information about chromedriver
|
|
382
382
|
// port ranges, just find any free port
|
|
383
383
|
if (!portSpec) {
|
|
384
384
|
const port = await getPort();
|
|
385
|
-
log
|
|
385
|
+
log?.debug(`A port was not given, using random free port: ${port}`);
|
|
386
386
|
return port;
|
|
387
387
|
}
|
|
388
388
|
|
|
389
389
|
// otherwise find the free port based on a list or range provided by the user
|
|
390
|
-
log
|
|
390
|
+
log?.debug(`Finding a free port for chromedriver using spec ${JSON.stringify(portSpec)}`);
|
|
391
391
|
let foundPort = null;
|
|
392
392
|
for (const potentialPort of portSpec) {
|
|
393
393
|
let port, stopPort;
|
|
@@ -398,11 +398,11 @@ async function getChromedriverPort (portSpec) {
|
|
|
398
398
|
stopPort = port;
|
|
399
399
|
}
|
|
400
400
|
try {
|
|
401
|
-
log
|
|
401
|
+
log?.debug(`Checking port range ${port}:${stopPort}`);
|
|
402
402
|
foundPort = await getPort({port, stopPort});
|
|
403
403
|
break;
|
|
404
404
|
} catch (e) {
|
|
405
|
-
log
|
|
405
|
+
log?.debug(`Nothing in port range ${port}:${stopPort} was available`);
|
|
406
406
|
}
|
|
407
407
|
}
|
|
408
408
|
|
|
@@ -411,7 +411,7 @@ async function getChromedriverPort (portSpec) {
|
|
|
411
411
|
`chromedriverPorts spec ${JSON.stringify(portSpec)}`);
|
|
412
412
|
}
|
|
413
413
|
|
|
414
|
-
log
|
|
414
|
+
log?.debug(`Using free port ${foundPort} for chromedriver`);
|
|
415
415
|
return foundPort;
|
|
416
416
|
}
|
|
417
417
|
|
|
@@ -419,27 +419,27 @@ helpers.isChromedriverAutodownloadEnabled = function isChromedriverAutodownloadE
|
|
|
419
419
|
if (this.isFeatureEnabled(CHROMEDRIVER_AUTODOWNLOAD_FEATURE)) {
|
|
420
420
|
return true;
|
|
421
421
|
}
|
|
422
|
-
log
|
|
422
|
+
this?.log?.debug(`Automated Chromedriver download is disabled. ` +
|
|
423
423
|
`Use '${CHROMEDRIVER_AUTODOWNLOAD_FEATURE}' server feature to enable it`);
|
|
424
424
|
return false;
|
|
425
425
|
};
|
|
426
426
|
|
|
427
427
|
helpers.setupNewChromedriver = async function setupNewChromedriver (opts, curDeviceId, adb, context = null) {
|
|
428
428
|
if (opts.chromeDriverPort) {
|
|
429
|
-
log
|
|
429
|
+
this?.log?.warn(`The 'chromeDriverPort' capability is deprecated. Please use 'chromedriverPort' instead`);
|
|
430
430
|
opts.chromedriverPort = opts.chromeDriverPort;
|
|
431
431
|
}
|
|
432
432
|
|
|
433
433
|
if (opts.chromedriverPort) {
|
|
434
|
-
log
|
|
434
|
+
this?.log?.debug(`Using user-specified port ${opts.chromedriverPort} for chromedriver`);
|
|
435
435
|
} else {
|
|
436
436
|
// if a single port wasn't given, we'll look for a free one
|
|
437
|
-
opts.chromedriverPort = await getChromedriverPort(opts.chromedriverPorts);
|
|
437
|
+
opts.chromedriverPort = await getChromedriverPort(opts.chromedriverPorts, this?.log);
|
|
438
438
|
}
|
|
439
439
|
|
|
440
440
|
const details = context ? webviewHelpers.getWebviewDetails(adb, context) : undefined;
|
|
441
441
|
if (!_.isEmpty(details)) {
|
|
442
|
-
log
|
|
442
|
+
this?.log?.debug('Passing web view details to the Chromedriver constructor: ' +
|
|
443
443
|
JSON.stringify(details, null, 2));
|
|
444
444
|
}
|
|
445
445
|
|
|
@@ -455,8 +455,7 @@ helpers.setupNewChromedriver = async function setupNewChromedriver (opts, curDev
|
|
|
455
455
|
useSystemExecutable: opts.chromedriverUseSystemExecutable,
|
|
456
456
|
disableBuildCheck: opts.chromedriverDisableBuildCheck,
|
|
457
457
|
details,
|
|
458
|
-
isAutodownloadEnabled: (
|
|
459
|
-
? this.isChromedriverAutodownloadEnabled() : undefined,
|
|
458
|
+
isAutodownloadEnabled: this?.isChromedriverAutodownloadEnabled?.()
|
|
460
459
|
});
|
|
461
460
|
|
|
462
461
|
// make sure there are chromeOptions
|
|
@@ -465,13 +464,13 @@ helpers.setupNewChromedriver = async function setupNewChromedriver (opts, curDev
|
|
|
465
464
|
// and strip the prefix
|
|
466
465
|
for (const opt of _.keys(opts)) {
|
|
467
466
|
if (opt.endsWith(':chromeOptions')) {
|
|
468
|
-
log
|
|
467
|
+
this?.log?.warn(`Merging '${opt}' into 'chromeOptions'. This may cause unexpected behavior`);
|
|
469
468
|
_.merge(opts.chromeOptions, opts[opt]);
|
|
470
469
|
}
|
|
471
470
|
}
|
|
472
471
|
|
|
473
472
|
const caps = webviewHelpers.createChromedriverCaps(opts, curDeviceId, details);
|
|
474
|
-
log
|
|
473
|
+
this?.log?.debug(`Before starting chromedriver, androidPackage is '${caps.chromeOptions.androidPackage}'`);
|
|
475
474
|
await chromedriver.start(caps);
|
|
476
475
|
return chromedriver;
|
|
477
476
|
};
|
package/lib/commands/coverage.js
CHANGED
|
@@ -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
|
};
|
package/lib/commands/element.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
40
|
+
throw new errors.InvalidArgumentError(`The 'command' argument is mandatory`);
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
return await this.adb.execEmuConsoleCommand(command, {
|
package/lib/commands/execute.js
CHANGED
|
@@ -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
|
-
|
|
4
|
+
const extensions = {};
|
|
6
5
|
|
|
7
6
|
extensions.execute = async function execute (script, args) {
|
|
8
7
|
if (script.match(/^mobile:/)) {
|
|
9
|
-
|
|
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
|
-
|
|
28
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
83
|
-
|
|
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
|
-
|
|
130
|
-
|
|
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}'.
|
|
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
|
-
|
|
290
|
+
throw new errors.InvalidArgumentError(`The 'remotePath' argument is mandatory`);
|
|
287
291
|
}
|
|
288
292
|
if (remotePath.endsWith('/')) {
|
|
289
|
-
|
|
290
|
-
|
|
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
|
};
|
package/lib/commands/general.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
};
|
package/lib/commands/ime.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import log from '../logger';
|
|
2
1
|
import { errors } from '@appium/base-driver';
|
|
3
2
|
|
|
4
3
|
let commands = {}, helpers = {}, extensions = {};
|
|
@@ -9,32 +8,32 @@ commands.isIMEActivated = async function isIMEActivated () { // eslint-disable-l
|
|
|
9
8
|
};
|
|
10
9
|
|
|
11
10
|
commands.availableIMEEngines = async function availableIMEEngines () {
|
|
12
|
-
log.debug('Retrieving available IMEs');
|
|
11
|
+
this.log.debug('Retrieving available IMEs');
|
|
13
12
|
let engines = await this.adb.availableIMEs();
|
|
14
|
-
log.debug(`Engines: ${JSON.stringify(engines)}`);
|
|
13
|
+
this.log.debug(`Engines: ${JSON.stringify(engines)}`);
|
|
15
14
|
return engines;
|
|
16
15
|
};
|
|
17
16
|
|
|
18
17
|
commands.getActiveIMEEngine = async function getActiveIMEEngine () {
|
|
19
|
-
log.debug('Retrieving current default IME');
|
|
18
|
+
this.log.debug('Retrieving current default IME');
|
|
20
19
|
return await this.adb.defaultIME();
|
|
21
20
|
};
|
|
22
21
|
|
|
23
22
|
commands.activateIMEEngine = async function activateIMEEngine (imeId) {
|
|
24
|
-
log.debug(`Attempting to activate IME ${imeId}`);
|
|
23
|
+
this.log.debug(`Attempting to activate IME ${imeId}`);
|
|
25
24
|
let availableEngines = await this.adb.availableIMEs();
|
|
26
25
|
if (availableEngines.indexOf(imeId) === -1) {
|
|
27
|
-
log.debug('IME not found, failing');
|
|
26
|
+
this.log.debug('IME not found, failing');
|
|
28
27
|
throw new errors.IMENotAvailableError();
|
|
29
28
|
}
|
|
30
|
-
log.debug('Found installed IME, attempting to activate');
|
|
29
|
+
this.log.debug('Found installed IME, attempting to activate');
|
|
31
30
|
await this.adb.enableIME(imeId);
|
|
32
31
|
await this.adb.setIME(imeId);
|
|
33
32
|
};
|
|
34
33
|
|
|
35
34
|
commands.deactivateIMEEngine = async function deactivateIMEEngine () {
|
|
36
35
|
let currentEngine = await this.getActiveIMEEngine();
|
|
37
|
-
log.debug(`Attempting to deactivate ${currentEngine}`);
|
|
36
|
+
this.log.debug(`Attempting to deactivate ${currentEngine}`);
|
|
38
37
|
await this.adb.disableIME(currentEngine);
|
|
39
38
|
};
|
|
40
39
|
|