appium-android-driver 13.3.1 → 13.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/build/lib/commands/app-management.d.ts.map +1 -1
- package/build/lib/commands/app-management.js +13 -12
- package/build/lib/commands/app-management.js.map +1 -1
- package/build/lib/commands/bidi/models.d.ts.map +1 -1
- package/build/lib/commands/bidi/models.js +1 -5
- package/build/lib/commands/bidi/models.js.map +1 -1
- package/build/lib/commands/bluetooth.d.ts.map +1 -1
- package/build/lib/commands/bluetooth.js +1 -5
- package/build/lib/commands/bluetooth.js.map +1 -1
- package/build/lib/commands/context/exports.d.ts.map +1 -1
- package/build/lib/commands/context/exports.js +11 -15
- package/build/lib/commands/context/exports.js.map +1 -1
- package/build/lib/commands/context/helpers.d.ts.map +1 -1
- package/build/lib/commands/context/helpers.js +43 -22
- package/build/lib/commands/context/helpers.js.map +1 -1
- package/build/lib/commands/device/common.d.ts.map +1 -1
- package/build/lib/commands/device/common.js +9 -8
- package/build/lib/commands/device/common.js.map +1 -1
- package/build/lib/commands/device/utils.d.ts.map +1 -1
- package/build/lib/commands/device/utils.js +2 -6
- package/build/lib/commands/device/utils.js.map +1 -1
- package/build/lib/commands/deviceidle.d.ts.map +1 -1
- package/build/lib/commands/deviceidle.js +2 -6
- package/build/lib/commands/deviceidle.js.map +1 -1
- package/build/lib/commands/execute.d.ts.map +1 -1
- package/build/lib/commands/execute.js +2 -6
- package/build/lib/commands/execute.js.map +1 -1
- package/build/lib/commands/file-actions.d.ts.map +1 -1
- package/build/lib/commands/file-actions.js +2 -3
- package/build/lib/commands/file-actions.js.map +1 -1
- package/build/lib/commands/find.d.ts.map +1 -1
- package/build/lib/commands/find.js +4 -7
- package/build/lib/commands/find.js.map +1 -1
- package/build/lib/commands/geolocation.d.ts.map +1 -1
- package/build/lib/commands/geolocation.js +3 -4
- package/build/lib/commands/geolocation.js.map +1 -1
- package/build/lib/commands/image-injection.d.ts.map +1 -1
- package/build/lib/commands/image-injection.js +1 -2
- package/build/lib/commands/image-injection.js.map +1 -1
- package/build/lib/commands/intent.d.ts.map +1 -1
- package/build/lib/commands/intent.js +14 -18
- package/build/lib/commands/intent.js.map +1 -1
- package/build/lib/commands/keyboard.d.ts.map +1 -1
- package/build/lib/commands/keyboard.js +6 -6
- package/build/lib/commands/keyboard.js.map +1 -1
- package/build/lib/commands/lock/exports.d.ts.map +1 -1
- package/build/lib/commands/lock/exports.js +1 -5
- package/build/lib/commands/lock/exports.js.map +1 -1
- package/build/lib/commands/lock/helpers.d.ts.map +1 -1
- package/build/lib/commands/lock/helpers.js +5 -9
- package/build/lib/commands/lock/helpers.js.map +1 -1
- package/build/lib/commands/log.d.ts.map +1 -1
- package/build/lib/commands/log.js +9 -9
- package/build/lib/commands/log.js.map +1 -1
- package/build/lib/commands/media-projection.d.ts.map +1 -1
- package/build/lib/commands/media-projection.js +5 -6
- package/build/lib/commands/media-projection.js.map +1 -1
- package/build/lib/commands/network.d.ts.map +1 -1
- package/build/lib/commands/network.js +12 -16
- package/build/lib/commands/network.js.map +1 -1
- package/build/lib/commands/nfc.d.ts.map +1 -1
- package/build/lib/commands/nfc.js +1 -5
- package/build/lib/commands/nfc.js.map +1 -1
- package/build/lib/commands/performance.d.ts.map +1 -1
- package/build/lib/commands/performance.js +7 -12
- package/build/lib/commands/performance.js.map +1 -1
- package/build/lib/commands/permissions.d.ts.map +1 -1
- package/build/lib/commands/permissions.js +21 -23
- package/build/lib/commands/permissions.js.map +1 -1
- package/build/lib/commands/recordscreen.d.ts.map +1 -1
- package/build/lib/commands/recordscreen.js +10 -11
- package/build/lib/commands/recordscreen.js.map +1 -1
- package/build/lib/commands/resources.d.ts.map +1 -1
- package/build/lib/commands/resources.js +6 -10
- package/build/lib/commands/resources.js.map +1 -1
- package/build/lib/commands/shell.d.ts.map +1 -1
- package/build/lib/commands/shell.js +7 -6
- package/build/lib/commands/shell.js.map +1 -1
- package/build/lib/commands/streamscreen.d.ts.map +1 -1
- package/build/lib/commands/streamscreen.js +14 -15
- package/build/lib/commands/streamscreen.js.map +1 -1
- package/build/lib/commands/system-bars.d.ts.map +1 -1
- package/build/lib/commands/system-bars.js +19 -21
- package/build/lib/commands/system-bars.js.map +1 -1
- package/build/lib/commands/time.d.ts.map +1 -1
- package/build/lib/commands/time.js +23 -8
- package/build/lib/commands/time.js.map +1 -1
- package/build/lib/driver.d.ts.map +1 -1
- package/build/lib/driver.js +2 -6
- package/build/lib/driver.js.map +1 -1
- package/build/lib/logger.d.ts +2 -1
- package/build/lib/logger.d.ts.map +1 -1
- package/build/lib/logger.js.map +1 -1
- package/build/lib/utils.d.ts.map +1 -1
- package/build/lib/utils.js +9 -12
- package/build/lib/utils.js.map +1 -1
- package/lib/commands/app-management.ts +19 -9
- package/lib/commands/bidi/models.ts +1 -2
- package/lib/commands/bluetooth.ts +1 -2
- package/lib/commands/context/exports.ts +12 -14
- package/lib/commands/context/helpers.ts +47 -22
- package/lib/commands/device/common.ts +10 -8
- package/lib/commands/device/utils.ts +2 -3
- package/lib/commands/deviceidle.ts +2 -3
- package/lib/commands/execute.ts +2 -3
- package/lib/commands/file-actions.ts +2 -3
- package/lib/commands/find.ts +4 -5
- package/lib/commands/geolocation.ts +3 -4
- package/lib/commands/image-injection.ts +1 -2
- package/lib/commands/intent.ts +14 -15
- package/lib/commands/keyboard.ts +6 -3
- package/lib/commands/lock/exports.ts +1 -2
- package/lib/commands/lock/helpers.ts +5 -6
- package/lib/commands/log.ts +9 -9
- package/lib/commands/media-projection.ts +5 -6
- package/lib/commands/network.ts +12 -13
- package/lib/commands/nfc.ts +1 -2
- package/lib/commands/performance.ts +7 -11
- package/lib/commands/permissions.ts +23 -20
- package/lib/commands/recordscreen.ts +10 -11
- package/lib/commands/resources.ts +7 -8
- package/lib/commands/shell.ts +7 -3
- package/lib/commands/streamscreen.ts +14 -16
- package/lib/commands/system-bars.ts +20 -20
- package/lib/commands/time.ts +26 -8
- package/lib/driver.ts +2 -4
- package/lib/logger.ts +2 -1
- package/lib/utils.ts +9 -9
- package/package.json +2 -5
|
@@ -2,7 +2,6 @@ import {util} from '@appium/support';
|
|
|
2
2
|
import {Chromedriver} from 'appium-chromedriver';
|
|
3
3
|
import {errors, PROTOCOLS} from 'appium/driver';
|
|
4
4
|
import type {StringRecord} from '@appium/types';
|
|
5
|
-
import _ from 'lodash';
|
|
6
5
|
import {
|
|
7
6
|
CHROMIUM_WIN,
|
|
8
7
|
KNOWN_CHROME_PACKAGE_NAMES,
|
|
@@ -69,7 +68,7 @@ export async function setContext(this: AndroidDriver, name?: string | null): Pro
|
|
|
69
68
|
const webviewsMapping = await getWebViewsMapping.bind(this)(this.opts);
|
|
70
69
|
const contexts = this.assignContexts(webviewsMapping);
|
|
71
70
|
// if the context we want doesn't exist, fail
|
|
72
|
-
if (!
|
|
71
|
+
if (!contexts?.includes(newContext)) {
|
|
73
72
|
throw new errors.NoSuchContextError();
|
|
74
73
|
}
|
|
75
74
|
|
|
@@ -243,7 +242,7 @@ export async function startChromedriverProxy(
|
|
|
243
242
|
await setupExistingChromedriver.bind(this)(cd, context);
|
|
244
243
|
} else {
|
|
245
244
|
// XXX: this suppresses errors about putting arbitrary stuff on opts
|
|
246
|
-
const opts =
|
|
245
|
+
const opts = structuredClone(this.opts) as any;
|
|
247
246
|
opts.chromeUseRunningApp = true;
|
|
248
247
|
|
|
249
248
|
// if requested, tell chromedriver to attach to the android package we have
|
|
@@ -258,7 +257,7 @@ export async function startChromedriverProxy(
|
|
|
258
257
|
}
|
|
259
258
|
if (!opts.extractChromeAndroidPackageFromContextName) {
|
|
260
259
|
if (
|
|
261
|
-
|
|
260
|
+
this.opts.enableWebviewDetailsCollection !== undefined &&
|
|
262
261
|
!this.opts.enableWebviewDetailsCollection
|
|
263
262
|
) {
|
|
264
263
|
// When enableWebviewDetailsCollection capability is explicitly disabled, try to identify
|
|
@@ -266,14 +265,13 @@ export async function startChromedriverProxy(
|
|
|
266
265
|
// since webviewsMapping does not have info object
|
|
267
266
|
const contexts = webviewsMapping.map((wm) => wm.webviewName);
|
|
268
267
|
for (const knownPackage of KNOWN_CHROME_PACKAGE_NAMES) {
|
|
269
|
-
if (
|
|
268
|
+
if (contexts?.includes(`${WEBVIEW_BASE}${knownPackage}`)) {
|
|
270
269
|
continue;
|
|
271
270
|
}
|
|
272
271
|
const appState = await this.queryAppState(knownPackage);
|
|
273
272
|
if (
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
appState,
|
|
273
|
+
[APP_STATE.RUNNING_IN_BACKGROUND, APP_STATE.RUNNING_IN_FOREGROUND].includes(
|
|
274
|
+
appState as any,
|
|
277
275
|
)
|
|
278
276
|
) {
|
|
279
277
|
opts.chromeAndroidPackage = knownPackage;
|
|
@@ -286,7 +284,7 @@ export async function startChromedriverProxy(
|
|
|
286
284
|
}
|
|
287
285
|
} else {
|
|
288
286
|
for (const wm of webviewsMapping) {
|
|
289
|
-
if (wm.webviewName === context &&
|
|
287
|
+
if (wm.webviewName === context && wm?.info?.['Android-Package'] !== undefined) {
|
|
290
288
|
// XXX: should be a type guard here
|
|
291
289
|
opts.chromeAndroidPackage = (wm.info as NonNullable<WebviewsMapping['info']>)[
|
|
292
290
|
'Android-Package'
|
|
@@ -367,7 +365,7 @@ export async function onChromedriverStop(this: AndroidDriver, context: string):
|
|
|
367
365
|
*/
|
|
368
366
|
export async function stopChromedriverProxies(this: AndroidDriver): Promise<void> {
|
|
369
367
|
this.suspendChromedriverProxy(); // make sure we turn off the proxy flag
|
|
370
|
-
for (const context of
|
|
368
|
+
for (const context of Object.keys(this.sessionChromedrivers)) {
|
|
371
369
|
const cd = this.sessionChromedrivers[context];
|
|
372
370
|
this.log.debug(`Stopping chromedriver for context ${context}`);
|
|
373
371
|
// stop listening for the stopped state event
|
|
@@ -388,7 +386,7 @@ export async function stopChromedriverProxies(this: AndroidDriver): Promise<void
|
|
|
388
386
|
* @returns True if the context is a Chromedriver context, false otherwise
|
|
389
387
|
*/
|
|
390
388
|
export function isChromedriverContext(this: AndroidDriver, viewName: string): boolean {
|
|
391
|
-
return
|
|
389
|
+
return viewName?.includes(WEBVIEW_WIN) || viewName === CHROMIUM_WIN;
|
|
392
390
|
}
|
|
393
391
|
|
|
394
392
|
/**
|
|
@@ -399,7 +397,7 @@ export async function notifyBiDiContextChange(this: AndroidDriver): Promise<void
|
|
|
399
397
|
const name = await this.getCurrentContext();
|
|
400
398
|
this.eventEmitter.emit(
|
|
401
399
|
BIDI_EVENT_NAME,
|
|
402
|
-
makeContextUpdatedEvent(
|
|
400
|
+
makeContextUpdatedEvent(String(this.opts.automationName)?.toLowerCase(), name),
|
|
403
401
|
);
|
|
404
402
|
this.eventEmitter.emit(BIDI_EVENT_NAME, makeObsoleteContextUpdatedEvent(name));
|
|
405
403
|
}
|
|
@@ -435,7 +433,7 @@ export async function mobileGetChromeCapabilities(this: AndroidDriver): Promise<
|
|
|
435
433
|
export async function startChromeSession(this: AndroidDriver): Promise<void> {
|
|
436
434
|
this.log.info('Starting a chrome-based browser session');
|
|
437
435
|
// XXX: this suppresses errors about putting arbitrary stuff on opts
|
|
438
|
-
const opts =
|
|
436
|
+
const opts = structuredClone(this.opts) as any;
|
|
439
437
|
|
|
440
438
|
const knownPackages = [
|
|
441
439
|
'org.chromium.chrome.shell',
|
|
@@ -445,7 +443,7 @@ export async function startChromeSession(this: AndroidDriver): Promise<void> {
|
|
|
445
443
|
'org.chromium.webview_shell',
|
|
446
444
|
];
|
|
447
445
|
|
|
448
|
-
if (
|
|
446
|
+
if (knownPackages?.includes(this.opts.appPackage as string)) {
|
|
449
447
|
opts.chromeBundleId = this.opts.appPackage;
|
|
450
448
|
} else {
|
|
451
449
|
opts.chromeAndroidActivity = this.opts.appActivity;
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import {util, timing} from '@appium/support';
|
|
2
|
-
import _ from 'lodash';
|
|
3
2
|
import axios from 'axios';
|
|
4
3
|
import net from 'node:net';
|
|
5
4
|
import {findAPortNotInUse} from 'portscanner';
|
|
@@ -166,7 +165,7 @@ export async function getWebViewsMapping(
|
|
|
166
165
|
this.log.debug(`Getting a list of available webviews`);
|
|
167
166
|
|
|
168
167
|
let waitMs = waitForWebviewMs;
|
|
169
|
-
if (!
|
|
168
|
+
if (!Number.isFinite(waitMs) || typeof waitMs !== 'number') {
|
|
170
169
|
waitMs = parseInt(`${waitMs}`, 10) || 0;
|
|
171
170
|
}
|
|
172
171
|
|
|
@@ -256,7 +255,7 @@ export async function setupNewChromedriver(
|
|
|
256
255
|
}
|
|
257
256
|
|
|
258
257
|
const details = context ? getWebviewDetails(this.adb, context) : undefined;
|
|
259
|
-
if (!
|
|
258
|
+
if (!util.isEmpty(details)) {
|
|
260
259
|
this.log.debug(
|
|
261
260
|
'Passing web view details to the Chromedriver constructor: ' +
|
|
262
261
|
JSON.stringify(details, null, 2),
|
|
@@ -264,7 +263,7 @@ export async function setupNewChromedriver(
|
|
|
264
263
|
}
|
|
265
264
|
|
|
266
265
|
const chromedriverOpts: ChromedriverOpts & {details?: WebViewDetails} = {
|
|
267
|
-
port:
|
|
266
|
+
port: opts.chromedriverPort == null ? undefined : String(opts.chromedriverPort),
|
|
268
267
|
executable: opts.chromedriverExecutable,
|
|
269
268
|
adb: this.adb,
|
|
270
269
|
cmdArgs: opts.chromedriverArgs as string[] | undefined,
|
|
@@ -285,10 +284,10 @@ export async function setupNewChromedriver(
|
|
|
285
284
|
opts.chromeOptions = opts.chromeOptions || {};
|
|
286
285
|
// try out any prefixed chromeOptions,
|
|
287
286
|
// and strip the prefix
|
|
288
|
-
for (const opt of
|
|
287
|
+
for (const opt of Object.keys(opts)) {
|
|
289
288
|
if (opt.endsWith(':chromeOptions')) {
|
|
290
289
|
this?.log?.warn(`Merging '${opt}' into 'chromeOptions'. This may cause unexpected behavior`);
|
|
291
|
-
|
|
290
|
+
merge(opts.chromeOptions, (opts as Record<string, any>)[opt]);
|
|
292
291
|
}
|
|
293
292
|
}
|
|
294
293
|
|
|
@@ -296,12 +295,12 @@ export async function setupNewChromedriver(
|
|
|
296
295
|
opts.chromeLoggingPrefs = opts.chromeLoggingPrefs ?? {};
|
|
297
296
|
|
|
298
297
|
// Strip the prefix and store it
|
|
299
|
-
for (const opt of
|
|
298
|
+
for (const opt of Object.keys(opts)) {
|
|
300
299
|
if (opt.endsWith(':loggingPrefs')) {
|
|
301
300
|
this.log.warn(
|
|
302
301
|
`Merging '${opt}' into 'chromeLoggingPrefs'. This may cause unexpected behavior`,
|
|
303
302
|
);
|
|
304
|
-
|
|
303
|
+
merge(opts.chromeLoggingPrefs, (opts as Record<string, any>)[opt]);
|
|
305
304
|
}
|
|
306
305
|
}
|
|
307
306
|
|
|
@@ -363,7 +362,7 @@ export async function setupExistingChromedriver<T extends Chromedriver>(
|
|
|
363
362
|
export function shouldDismissChromeWelcome(this: AndroidDriver): boolean {
|
|
364
363
|
return (
|
|
365
364
|
!!this.opts.chromeOptions &&
|
|
366
|
-
|
|
365
|
+
Array.isArray(this.opts.chromeOptions.args) &&
|
|
367
366
|
this.opts.chromeOptions.args.includes('--no-first-run')
|
|
368
367
|
);
|
|
369
368
|
}
|
|
@@ -462,7 +461,7 @@ function createChromedriverCaps(
|
|
|
462
461
|
|
|
463
462
|
caps.chromeOptions.androidPackage = androidPackage;
|
|
464
463
|
}
|
|
465
|
-
if (
|
|
464
|
+
if (typeof opts.chromeUseRunningApp === 'boolean') {
|
|
466
465
|
caps.chromeOptions.androidUseRunningApp = opts.chromeUseRunningApp;
|
|
467
466
|
}
|
|
468
467
|
if (opts.chromeAndroidPackage) {
|
|
@@ -476,14 +475,14 @@ function createChromedriverCaps(
|
|
|
476
475
|
} else if (webViewDetails?.process?.name && webViewDetails?.process?.id) {
|
|
477
476
|
caps.chromeOptions.androidProcess = webViewDetails.process.name;
|
|
478
477
|
}
|
|
479
|
-
if (
|
|
478
|
+
if (String(opts.browserName)?.toLowerCase() === 'chromium-webview') {
|
|
480
479
|
caps.chromeOptions.androidActivity = opts.appActivity;
|
|
481
480
|
}
|
|
482
481
|
if (opts.pageLoadStrategy) {
|
|
483
482
|
caps.pageLoadStrategy = opts.pageLoadStrategy;
|
|
484
483
|
}
|
|
485
|
-
const isChrome =
|
|
486
|
-
if (
|
|
484
|
+
const isChrome = String(caps.chromeOptions.androidPackage)?.toLowerCase() === 'chrome';
|
|
485
|
+
if (KNOWN_CHROME_PACKAGE_NAMES?.includes(caps.chromeOptions.androidPackage as any) || isChrome) {
|
|
487
486
|
// if we have extracted package from context name, it could come in as bare
|
|
488
487
|
// "chrome", and so we should make sure the details are correct, including
|
|
489
488
|
// not using an activity or process id
|
|
@@ -496,7 +495,7 @@ function createChromedriverCaps(
|
|
|
496
495
|
// add device id from adb
|
|
497
496
|
caps.chromeOptions.androidDeviceSerial = deviceId;
|
|
498
497
|
|
|
499
|
-
if (
|
|
498
|
+
if (util.isPlainObject(opts.loggingPrefs) || util.isPlainObject(opts.chromeLoggingPrefs)) {
|
|
500
499
|
if (opts.loggingPrefs) {
|
|
501
500
|
this.log.warn(
|
|
502
501
|
`The 'loggingPrefs' cap is deprecated; use the 'chromeLoggingPrefs' cap instead`,
|
|
@@ -520,7 +519,7 @@ function createChromedriverCaps(
|
|
|
520
519
|
delete opts.chromeOptions.Arguments;
|
|
521
520
|
}
|
|
522
521
|
|
|
523
|
-
if (opts.webSocketUrl &&
|
|
522
|
+
if (opts.webSocketUrl && caps.webSocketUrl == null && this.opts.chromedriverForwardBiDi) {
|
|
524
523
|
caps.webSocketUrl = opts.webSocketUrl;
|
|
525
524
|
}
|
|
526
525
|
|
|
@@ -529,14 +528,14 @@ function createChromedriverCaps(
|
|
|
529
528
|
);
|
|
530
529
|
|
|
531
530
|
const protectedCapNames: string[] = [];
|
|
532
|
-
for (const [opt, val] of
|
|
533
|
-
if (
|
|
531
|
+
for (const [opt, val] of Object.entries(opts.chromeOptions)) {
|
|
532
|
+
if (caps.chromeOptions[opt] === undefined) {
|
|
534
533
|
caps.chromeOptions[opt] = val;
|
|
535
534
|
} else {
|
|
536
535
|
protectedCapNames.push(opt);
|
|
537
536
|
}
|
|
538
537
|
}
|
|
539
|
-
if (!
|
|
538
|
+
if (!util.isEmpty(protectedCapNames)) {
|
|
540
539
|
this.log.info(
|
|
541
540
|
'The following Chromedriver capabilities cannot be overridden ' +
|
|
542
541
|
'by the provided chromeOptions:',
|
|
@@ -613,7 +612,7 @@ async function collectWebviewsDetails(
|
|
|
613
612
|
webviewsMapping: WebviewProps[],
|
|
614
613
|
opts: DetailCollectionOptions = {},
|
|
615
614
|
): Promise<void> {
|
|
616
|
-
if (
|
|
615
|
+
if (util.isEmpty(webviewsMapping)) {
|
|
617
616
|
return;
|
|
618
617
|
}
|
|
619
618
|
|
|
@@ -729,9 +728,9 @@ async function getPotentialWebviewProcs(this: AndroidDriver): Promise<string[]>
|
|
|
729
728
|
|
|
730
729
|
names.push(sockPath);
|
|
731
730
|
}
|
|
732
|
-
if (
|
|
731
|
+
if (util.isEmpty(names)) {
|
|
733
732
|
this.log.debug('Found no active devtools sockets');
|
|
734
|
-
if (!
|
|
733
|
+
if (!util.isEmpty(allMatches)) {
|
|
735
734
|
this.log.debug(`Other sockets are: ${JSON.stringify(allMatches, null, 2)}`);
|
|
736
735
|
}
|
|
737
736
|
} else {
|
|
@@ -741,7 +740,7 @@ async function getPotentialWebviewProcs(this: AndroidDriver): Promise<string[]>
|
|
|
741
740
|
);
|
|
742
741
|
}
|
|
743
742
|
// sometimes the webview process shows up multiple times per app
|
|
744
|
-
return
|
|
743
|
+
return util.uniq(names);
|
|
745
744
|
}
|
|
746
745
|
|
|
747
746
|
/**
|
|
@@ -879,4 +878,30 @@ function isCompatibleCdpHost(host: string): boolean {
|
|
|
879
878
|
);
|
|
880
879
|
}
|
|
881
880
|
|
|
881
|
+
function merge<T extends Record<string, any>>(target: T, source: any): T {
|
|
882
|
+
if (!util.isPlainObject(source)) {
|
|
883
|
+
return target;
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
for (const key of Object.keys(source)) {
|
|
887
|
+
// Prevent prototype pollution when merging untrusted capability objects
|
|
888
|
+
if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
|
|
889
|
+
continue;
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
const sourceValue = (source as Record<string, any>)[key];
|
|
893
|
+
|
|
894
|
+
if (util.isPlainObject(sourceValue)) {
|
|
895
|
+
if (!util.isPlainObject((target as Record<string, any>)[key])) {
|
|
896
|
+
(target as Record<string, any>)[key] = {};
|
|
897
|
+
}
|
|
898
|
+
merge((target as any)[key], sourceValue);
|
|
899
|
+
} else {
|
|
900
|
+
(target as Record<string, any>)[key] = sourceValue;
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
return target;
|
|
905
|
+
}
|
|
906
|
+
|
|
882
907
|
// #endregion
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import * as semver from 'semver';
|
|
2
|
-
import _ from 'lodash';
|
|
3
2
|
import path from 'node:path';
|
|
4
3
|
import {setMockLocationApp} from '../geolocation';
|
|
5
4
|
import {SETTINGS_HELPER_ID} from 'io.appium.settings';
|
|
@@ -38,7 +37,7 @@ export async function getDeviceInfoFromCaps(this: AndroidDriver): Promise<ADBDev
|
|
|
38
37
|
|
|
39
38
|
// udid was given, lets try to init with that device
|
|
40
39
|
if (udid) {
|
|
41
|
-
if (!
|
|
40
|
+
if (!devices?.some((device) => device.udid === udid)) {
|
|
42
41
|
throw this.log.errorWithException(
|
|
43
42
|
`Device ${udid} was not in the list of connected devices`,
|
|
44
43
|
);
|
|
@@ -72,11 +71,13 @@ export async function getDeviceInfoFromCaps(this: AndroidDriver): Promise<ADBDev
|
|
|
72
71
|
const semverDO = deviceOS;
|
|
73
72
|
|
|
74
73
|
const bothVersionsCanBeCoerced = semver.valid(deviceOS) && semver.valid(platformVersion);
|
|
75
|
-
const bothVersionsAreStrings =
|
|
74
|
+
const bothVersionsAreStrings =
|
|
75
|
+
typeof deviceOS === 'string' && typeof platformVersion === 'string';
|
|
76
76
|
if (
|
|
77
77
|
(bothVersionsCanBeCoerced &&
|
|
78
78
|
(semverDO as semver.SemVer).version === (semverPV as semver.SemVer).version) ||
|
|
79
|
-
(bothVersionsAreStrings &&
|
|
79
|
+
(bothVersionsAreStrings &&
|
|
80
|
+
String(deviceOS)?.toLowerCase() === String(platformVersion)?.toLowerCase())
|
|
80
81
|
) {
|
|
81
82
|
// Got an exact match - proceed immediately
|
|
82
83
|
udid = device.udid;
|
|
@@ -91,18 +92,19 @@ export async function getDeviceInfoFromCaps(this: AndroidDriver): Promise<ADBDev
|
|
|
91
92
|
const dvMajor = (semverDO as semver.SemVer).major;
|
|
92
93
|
const dvMinor = (semverDO as semver.SemVer).minor;
|
|
93
94
|
if (
|
|
94
|
-
((!
|
|
95
|
+
((!String(this.opts.platformVersion)?.includes('.') && pvMajor === dvMajor) ||
|
|
95
96
|
(pvMajor === dvMajor && pvMinor === dvMinor)) &&
|
|
96
97
|
// Got a partial match - make sure we consider the most recent
|
|
97
98
|
// device version available on the host system
|
|
98
|
-
((partialMatchCandidate &&
|
|
99
|
+
((partialMatchCandidate &&
|
|
100
|
+
semver.gt(deviceOS, Object.values(partialMatchCandidate)[0])) ||
|
|
99
101
|
!partialMatchCandidate)
|
|
100
102
|
) {
|
|
101
103
|
partialMatchCandidate = {[device.udid]: deviceOS};
|
|
102
104
|
}
|
|
103
105
|
}
|
|
104
106
|
if (!udid && partialMatchCandidate) {
|
|
105
|
-
udid =
|
|
107
|
+
udid = Object.keys(partialMatchCandidate)[0];
|
|
106
108
|
adb.setDeviceId(udid);
|
|
107
109
|
}
|
|
108
110
|
|
|
@@ -241,7 +243,7 @@ export async function initDevice(this: AndroidDriver): Promise<void> {
|
|
|
241
243
|
if (!this.isEmulator()) {
|
|
242
244
|
setupPromises.push(
|
|
243
245
|
(async () => {
|
|
244
|
-
if (mockLocationApp ||
|
|
246
|
+
if (mockLocationApp || mockLocationApp === undefined) {
|
|
245
247
|
await setMockLocationApp.bind(this)(mockLocationApp || SETTINGS_HELPER_ID);
|
|
246
248
|
} else {
|
|
247
249
|
await this.mobileResetGeolocation();
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import _ from 'lodash';
|
|
2
1
|
import {util} from '@appium/support';
|
|
3
2
|
import {ADB} from 'appium-adb';
|
|
4
3
|
import {retryInterval} from 'asyncbox';
|
|
@@ -39,7 +38,7 @@ export function ensureNetworkSpeed(this: AndroidDriver, networkSpeed: string): s
|
|
|
39
38
|
}
|
|
40
39
|
this.log.warn(
|
|
41
40
|
`Wrong network speed param '${networkSpeed}', using default: ${this.adb.NETWORK_SPEED.FULL}. ` +
|
|
42
|
-
`Supported values: ${
|
|
41
|
+
`Supported values: ${Object.values(this.adb.NETWORK_SPEED)}`,
|
|
43
42
|
);
|
|
44
43
|
return this.adb.NETWORK_SPEED.FULL;
|
|
45
44
|
}
|
|
@@ -53,7 +52,7 @@ export function prepareAvdArgs(this: AndroidDriver): string[] {
|
|
|
53
52
|
const {networkSpeed, isHeadless, avdArgs} = this.opts;
|
|
54
53
|
const result: string[] = [];
|
|
55
54
|
if (avdArgs) {
|
|
56
|
-
if (
|
|
55
|
+
if (Array.isArray(avdArgs)) {
|
|
57
56
|
result.push(...avdArgs);
|
|
58
57
|
} else {
|
|
59
58
|
result.push(...util.shellParse(`${avdArgs}`));
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import {errors} from 'appium/driver';
|
|
2
|
-
import _ from 'lodash';
|
|
3
2
|
import type {AndroidDriver} from '../driver';
|
|
4
3
|
|
|
5
4
|
const SUPPORTED_ACTIONS = ['whitelistAdd', 'whitelistRemove'] as const;
|
|
@@ -20,11 +19,11 @@ export async function mobileDeviceidle(
|
|
|
20
19
|
action: (typeof SUPPORTED_ACTIONS)[number],
|
|
21
20
|
packages?: string | string[],
|
|
22
21
|
): Promise<void> {
|
|
23
|
-
if (!(
|
|
22
|
+
if (!(typeof packages === 'string' || Array.isArray(packages))) {
|
|
24
23
|
throw new errors.InvalidArgumentError(`packages argument must be a string or an array`);
|
|
25
24
|
}
|
|
26
25
|
|
|
27
|
-
const packagesArr =
|
|
26
|
+
const packagesArr = Array.isArray(packages) ? packages : [packages];
|
|
28
27
|
const commonArgs = ['dumpsys', 'deviceidle', 'whitelist'];
|
|
29
28
|
let argsGenerator: (pkg: string) => string[];
|
|
30
29
|
switch (action) {
|
package/lib/commands/execute.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import _ from 'lodash';
|
|
2
1
|
import {errors, PROTOCOLS} from 'appium/driver';
|
|
3
2
|
import {util} from '@appium/support';
|
|
4
3
|
import type {StringRecord, Element} from '@appium/types';
|
|
@@ -23,7 +22,7 @@ export async function execute(
|
|
|
23
22
|
script: string,
|
|
24
23
|
args?: ExecuteMethodArgs,
|
|
25
24
|
): Promise<any> {
|
|
26
|
-
if (
|
|
25
|
+
if (script?.startsWith(EXECUTE_SCRIPT_PREFIX)) {
|
|
27
26
|
const formattedScript = script.trim().replace(/^mobile:\s*/, `${EXECUTE_SCRIPT_PREFIX} `);
|
|
28
27
|
const executeMethodArgs = preprocessExecuteMethodArgs(args);
|
|
29
28
|
return await this.executeMethod(formattedScript, [executeMethodArgs]);
|
|
@@ -48,7 +47,7 @@ export async function execute(
|
|
|
48
47
|
* @returns Preprocessed arguments as a StringRecord.
|
|
49
48
|
*/
|
|
50
49
|
function preprocessExecuteMethodArgs(args?: ExecuteMethodArgs): StringRecord {
|
|
51
|
-
const executeMethodArgs = (
|
|
50
|
+
const executeMethodArgs = (Array.isArray(args) ? args[0] : args) ?? ({} as StringRecord);
|
|
52
51
|
|
|
53
52
|
/**
|
|
54
53
|
* Renames the deprecated `element` key to `elementId`. Historically,
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import _ from 'lodash';
|
|
2
1
|
import {fs, util, zip, tempDir} from '@appium/support';
|
|
3
2
|
import path from 'node:path';
|
|
4
3
|
import {errors} from 'appium/driver';
|
|
@@ -98,7 +97,7 @@ export async function pushFile(
|
|
|
98
97
|
}
|
|
99
98
|
const localFile = await tempDir.path({prefix: 'appium', suffix: '.tmp'});
|
|
100
99
|
let base64String: string;
|
|
101
|
-
if (
|
|
100
|
+
if (Array.isArray(base64Data)) {
|
|
102
101
|
// some clients (ahem) java, send a byte array encoding utf8 characters
|
|
103
102
|
// instead of a string, which would be infinitely better!
|
|
104
103
|
base64String = Buffer.from(base64Data).toString('utf8');
|
|
@@ -327,7 +326,7 @@ function createFSTests(adb: ADB) {
|
|
|
327
326
|
const checkCmd = `[ -${op} '${escapePath(p)}' ] && echo ${passFlag}`;
|
|
328
327
|
const fullCmd = runAs ? `run-as ${runAs} ${checkCmd}` : checkCmd;
|
|
329
328
|
try {
|
|
330
|
-
return
|
|
329
|
+
return (await adb.shell([fullCmd])).includes(passFlag);
|
|
331
330
|
} catch {
|
|
332
331
|
return false;
|
|
333
332
|
}
|
package/lib/commands/find.ts
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
2
|
-
|
|
3
|
-
import _ from 'lodash';
|
|
4
1
|
import {errors, isErrorType} from 'appium/driver';
|
|
5
2
|
import type {AndroidDriver} from '../driver';
|
|
6
3
|
import type {Element} from '@appium/types';
|
|
7
4
|
import type {FindElementOpts} from './types';
|
|
5
|
+
import {util} from '@appium/support';
|
|
8
6
|
|
|
9
7
|
/**
|
|
10
8
|
* @param strategy The element location strategy to use (e.g., 'id', 'xpath', 'class name').
|
|
@@ -70,7 +68,7 @@ export async function findElOrEls(
|
|
|
70
68
|
}
|
|
71
69
|
|
|
72
70
|
// we want to return false if we want to potentially try again
|
|
73
|
-
return !
|
|
71
|
+
return !util.isEmpty(element);
|
|
74
72
|
};
|
|
75
73
|
|
|
76
74
|
try {
|
|
@@ -89,7 +87,7 @@ export async function findElOrEls(
|
|
|
89
87
|
if (mult) {
|
|
90
88
|
return element as Element[];
|
|
91
89
|
}
|
|
92
|
-
if (
|
|
90
|
+
if (util.isEmpty(element)) {
|
|
93
91
|
throw new errors.NoSuchElementError();
|
|
94
92
|
}
|
|
95
93
|
return element as Element;
|
|
@@ -109,5 +107,6 @@ export async function doFindElementOrEls(
|
|
|
109
107
|
this: AndroidDriver,
|
|
110
108
|
params: FindElementOpts,
|
|
111
109
|
): Promise<Element | Element[]> {
|
|
110
|
+
void params;
|
|
112
111
|
throw new errors.NotImplementedError('Not implemented');
|
|
113
112
|
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import _ from 'lodash';
|
|
2
1
|
import {asyncmap} from 'asyncbox';
|
|
3
2
|
import {fs, tempDir} from '@appium/support';
|
|
4
3
|
import path from 'node:path';
|
|
@@ -208,10 +207,10 @@ async function resetMockLocation(this: AndroidDriver): Promise<void> {
|
|
|
208
207
|
pkgIds = JSON.parse(await this.adb.shell(['cat', MOCK_APP_IDS_STORE]));
|
|
209
208
|
} catch {}
|
|
210
209
|
}
|
|
211
|
-
const thirdPartyPkgIds = await thirdPartyPkgIdsPromise;
|
|
210
|
+
const thirdPartyPkgIds = new Set(await thirdPartyPkgIdsPromise);
|
|
212
211
|
// Only include currently installed packages
|
|
213
|
-
const resultPkgs =
|
|
214
|
-
if (
|
|
212
|
+
const resultPkgs = pkgIds.filter((pkg) => thirdPartyPkgIds.has(pkg));
|
|
213
|
+
if (resultPkgs.length <= 1) {
|
|
215
214
|
await this.adb.shell([
|
|
216
215
|
'appops',
|
|
217
216
|
'set',
|
|
@@ -2,7 +2,6 @@ import {errors} from 'appium/driver';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import {fs, tempDir} from '@appium/support';
|
|
4
4
|
import crypto from 'node:crypto';
|
|
5
|
-
import _ from 'lodash';
|
|
6
5
|
import type {AndroidDriver} from '../driver';
|
|
7
6
|
|
|
8
7
|
const EMULATOR_RESOURCES_ROOT = ['emulator', 'resources'];
|
|
@@ -92,7 +91,7 @@ export async function mobileInjectEmulatorCameraImage(
|
|
|
92
91
|
throw new Error('The image injection feature is only available on emulators');
|
|
93
92
|
}
|
|
94
93
|
|
|
95
|
-
if (
|
|
94
|
+
if (typeof payload !== 'string' || payload.length <= PNG_MAGIC_LENGTH) {
|
|
96
95
|
throw new errors.InvalidArgumentError(
|
|
97
96
|
`You must provide a valid base64-encoded .PNG data as the 'payload' argument`,
|
|
98
97
|
);
|
package/lib/commands/intent.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import _ from 'lodash';
|
|
2
1
|
import {errors} from 'appium/driver';
|
|
3
2
|
import {util} from '@appium/support';
|
|
4
3
|
import type {AndroidDriver} from '../driver';
|
|
@@ -126,7 +125,7 @@ export async function mobileStartActivity(
|
|
|
126
125
|
flags?: string,
|
|
127
126
|
): Promise<string> {
|
|
128
127
|
const cmd = ['am', 'start-activity'];
|
|
129
|
-
if (
|
|
128
|
+
if (util.hasValue(user)) {
|
|
130
129
|
cmd.push('--user', String(user));
|
|
131
130
|
}
|
|
132
131
|
if (wait) {
|
|
@@ -135,13 +134,13 @@ export async function mobileStartActivity(
|
|
|
135
134
|
if (stop) {
|
|
136
135
|
cmd.push('-S');
|
|
137
136
|
}
|
|
138
|
-
if (
|
|
137
|
+
if (util.hasValue(windowingMode)) {
|
|
139
138
|
cmd.push('--windowingMode', String(windowingMode));
|
|
140
139
|
}
|
|
141
|
-
if (
|
|
140
|
+
if (util.hasValue(activityType)) {
|
|
142
141
|
cmd.push('--activityType', String(activityType));
|
|
143
142
|
}
|
|
144
|
-
if (
|
|
143
|
+
if (util.hasValue(display)) {
|
|
145
144
|
cmd.push('--display', String(display));
|
|
146
145
|
}
|
|
147
146
|
cmd.push(
|
|
@@ -200,7 +199,7 @@ export async function mobileBroadcast(
|
|
|
200
199
|
flags?: string,
|
|
201
200
|
): Promise<string> {
|
|
202
201
|
const cmd = ['am', 'broadcast'];
|
|
203
|
-
if (
|
|
202
|
+
if (util.hasValue(user)) {
|
|
204
203
|
cmd.push('--user', String(user));
|
|
205
204
|
}
|
|
206
205
|
if (receiverPermission) {
|
|
@@ -265,7 +264,7 @@ export async function mobileStartService(
|
|
|
265
264
|
): Promise<string> {
|
|
266
265
|
const cmd = ['am'];
|
|
267
266
|
cmd.push(foreground ? 'start-foreground-service' : 'start-service');
|
|
268
|
-
if (
|
|
267
|
+
if (util.hasValue(user)) {
|
|
269
268
|
cmd.push('--user', String(user));
|
|
270
269
|
}
|
|
271
270
|
cmd.push(
|
|
@@ -320,7 +319,7 @@ export async function mobileStopService(
|
|
|
320
319
|
flags?: string,
|
|
321
320
|
): Promise<string> {
|
|
322
321
|
const cmd = ['am', 'stop-service'];
|
|
323
|
-
if (
|
|
322
|
+
if (util.hasValue(user)) {
|
|
324
323
|
cmd.push('--user', String(user));
|
|
325
324
|
}
|
|
326
325
|
cmd.push(
|
|
@@ -366,12 +365,12 @@ function parseIntentSpec(opts: IntentOpts = {}): string[] {
|
|
|
366
365
|
if (mimeType) {
|
|
367
366
|
resultArgs.push('-t', mimeType);
|
|
368
367
|
}
|
|
369
|
-
if (
|
|
368
|
+
if (util.hasValue(identifier)) {
|
|
370
369
|
resultArgs.push('-i', identifier);
|
|
371
370
|
}
|
|
372
371
|
if (categories) {
|
|
373
|
-
if (
|
|
374
|
-
resultArgs.push(...
|
|
372
|
+
if (Array.isArray(categories)) {
|
|
373
|
+
resultArgs.push(...categories.flatMap((cName) => ['-c', cName]));
|
|
375
374
|
} else {
|
|
376
375
|
resultArgs.push('-c', categories);
|
|
377
376
|
}
|
|
@@ -383,11 +382,11 @@ function parseIntentSpec(opts: IntentOpts = {}): string[] {
|
|
|
383
382
|
resultArgs.push('-p', opts.package);
|
|
384
383
|
}
|
|
385
384
|
if (extras) {
|
|
386
|
-
if (!
|
|
385
|
+
if (!Array.isArray(extras)) {
|
|
387
386
|
throw new errors.InvalidArgumentError(`'extras' must be an array`);
|
|
388
387
|
}
|
|
389
388
|
for (const item of extras) {
|
|
390
|
-
if (!
|
|
389
|
+
if (!Array.isArray(item)) {
|
|
391
390
|
throw new errors.InvalidArgumentError(`Extra argument '${item}' must be an array`);
|
|
392
391
|
}
|
|
393
392
|
const [type, key, value] = item;
|
|
@@ -397,14 +396,14 @@ function parseIntentSpec(opts: IntentOpts = {}): string[] {
|
|
|
397
396
|
`Supported intent argument types are: ${SUPPORTED_EXTRA_TYPES.join(', ')}`,
|
|
398
397
|
);
|
|
399
398
|
}
|
|
400
|
-
if (
|
|
399
|
+
if (util.isEmpty(key) || (typeof key === 'string' && key.trim() === '')) {
|
|
401
400
|
throw new errors.InvalidArgumentError(
|
|
402
401
|
`Extra argument's key in '${JSON.stringify(item)}' must be a valid string identifier`,
|
|
403
402
|
);
|
|
404
403
|
}
|
|
405
404
|
if (type === NO_VALUE_ARG_TYPE) {
|
|
406
405
|
resultArgs.push(`--e${type}`, key);
|
|
407
|
-
} else if (
|
|
406
|
+
} else if (value === undefined) {
|
|
408
407
|
throw new errors.InvalidArgumentError(
|
|
409
408
|
`Intent argument type '${type}' in '${JSON.stringify(item)}' requires a ` +
|
|
410
409
|
`valid value to be provided`,
|
package/lib/commands/keyboard.ts
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
2
|
-
import _ from 'lodash';
|
|
3
1
|
import {errors} from 'appium/driver';
|
|
4
2
|
import {UNICODE_IME, EMPTY_IME} from 'io.appium.settings';
|
|
5
3
|
import type {AndroidDriver} from '../driver';
|
|
@@ -32,7 +30,7 @@ export async function isKeyboardShown(this: AndroidDriver): Promise<boolean> {
|
|
|
32
30
|
*/
|
|
33
31
|
export async function keys(this: AndroidDriver, keys: string | string[]): Promise<void> {
|
|
34
32
|
// Protocol sends an array; rethink approach
|
|
35
|
-
const keysStr =
|
|
33
|
+
const keysStr = Array.isArray(keys) ? keys.join('') : keys;
|
|
36
34
|
await this.doSendKeys({
|
|
37
35
|
text: keysStr,
|
|
38
36
|
replace: false,
|
|
@@ -47,6 +45,7 @@ export async function keys(this: AndroidDriver, keys: string | string[]): Promis
|
|
|
47
45
|
* @throws {errors.NotImplementedError} This method is not implemented.
|
|
48
46
|
*/
|
|
49
47
|
export async function doSendKeys(this: AndroidDriver, params: SendKeysOpts): Promise<void> {
|
|
48
|
+
void params;
|
|
50
49
|
throw new errors.NotImplementedError('Not implemented');
|
|
51
50
|
}
|
|
52
51
|
|
|
@@ -81,6 +80,8 @@ export async function pressKeyCode(
|
|
|
81
80
|
keycode: string | number,
|
|
82
81
|
metastate?: number,
|
|
83
82
|
): Promise<void> {
|
|
83
|
+
void keycode;
|
|
84
|
+
void metastate;
|
|
84
85
|
throw new errors.NotImplementedError('Not implemented');
|
|
85
86
|
}
|
|
86
87
|
|
|
@@ -97,6 +98,8 @@ export async function longPressKeyCode(
|
|
|
97
98
|
keycode: string | number,
|
|
98
99
|
metastate?: number,
|
|
99
100
|
): Promise<void> {
|
|
101
|
+
void keycode;
|
|
102
|
+
void metastate;
|
|
100
103
|
throw new errors.NotImplementedError('Not implemented');
|
|
101
104
|
}
|
|
102
105
|
|