appium-adb 14.3.4 → 14.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/build/lib/adb.d.ts +9 -4
- package/build/lib/adb.d.ts.map +1 -1
- package/build/lib/adb.js +8 -9
- package/build/lib/adb.js.map +1 -1
- package/build/lib/index.d.ts +1 -1
- package/build/lib/index.d.ts.map +1 -1
- package/build/lib/index.js +2 -2
- package/build/lib/index.js.map +1 -1
- package/build/lib/logcat.d.ts.map +1 -1
- package/build/lib/logcat.js +6 -14
- package/build/lib/logcat.js.map +1 -1
- package/build/lib/tools/aab-utils.d.ts.map +1 -1
- package/build/lib/tools/aab-utils.js +2 -2
- package/build/lib/tools/aab-utils.js.map +1 -1
- package/build/lib/tools/android-manifest.d.ts.map +1 -1
- package/build/lib/tools/android-manifest.js +11 -12
- package/build/lib/tools/android-manifest.js.map +1 -1
- package/build/lib/tools/apk-signing.d.ts.map +1 -1
- package/build/lib/tools/apk-signing.js +18 -19
- package/build/lib/tools/apk-signing.js.map +1 -1
- package/build/lib/tools/apk-utils.d.ts.map +1 -1
- package/build/lib/tools/apk-utils.js +39 -40
- package/build/lib/tools/apk-utils.js.map +1 -1
- package/build/lib/tools/apks-utils.d.ts.map +1 -1
- package/build/lib/tools/apks-utils.js +8 -9
- package/build/lib/tools/apks-utils.js.map +1 -1
- package/build/lib/tools/app-commands.d.ts.map +1 -1
- package/build/lib/tools/app-commands.js +37 -36
- package/build/lib/tools/app-commands.js.map +1 -1
- package/build/lib/tools/device-settings.d.ts.map +1 -1
- package/build/lib/tools/device-settings.js +17 -18
- package/build/lib/tools/device-settings.js.map +1 -1
- package/build/lib/tools/emulator-commands.d.ts.map +1 -1
- package/build/lib/tools/emulator-commands.js +22 -23
- package/build/lib/tools/emulator-commands.js.map +1 -1
- package/build/lib/tools/fs-commands.d.ts.map +1 -1
- package/build/lib/tools/fs-commands.js +5 -4
- package/build/lib/tools/fs-commands.js.map +1 -1
- package/build/lib/tools/general-commands.d.ts.map +1 -1
- package/build/lib/tools/general-commands.js +29 -29
- package/build/lib/tools/general-commands.js.map +1 -1
- package/build/lib/tools/keyboard-commands.d.ts.map +1 -1
- package/build/lib/tools/keyboard-commands.js +6 -4
- package/build/lib/tools/keyboard-commands.js.map +1 -1
- package/build/lib/tools/lockmgmt.d.ts.map +1 -1
- package/build/lib/tools/lockmgmt.js +17 -20
- package/build/lib/tools/lockmgmt.js.map +1 -1
- package/build/lib/tools/logcat-commands.d.ts.map +1 -1
- package/build/lib/tools/logcat-commands.js +5 -9
- package/build/lib/tools/logcat-commands.js.map +1 -1
- package/build/lib/tools/network-commands.d.ts.map +1 -1
- package/build/lib/tools/network-commands.js +1 -5
- package/build/lib/tools/network-commands.js.map +1 -1
- package/build/lib/tools/process-commands.d.ts.map +1 -1
- package/build/lib/tools/process-commands.js +11 -12
- package/build/lib/tools/process-commands.js.map +1 -1
- package/build/lib/tools/system-calls.d.ts +12 -5
- package/build/lib/tools/system-calls.d.ts.map +1 -1
- package/build/lib/tools/system-calls.js +56 -50
- package/build/lib/tools/system-calls.js.map +1 -1
- package/build/lib/tools/types.d.ts +1 -1
- package/build/lib/tools/types.d.ts.map +1 -1
- package/build/lib/utils/helpers/constants.d.ts +6 -0
- package/build/lib/utils/helpers/constants.d.ts.map +1 -0
- package/build/lib/utils/helpers/constants.js +9 -0
- package/build/lib/utils/helpers/constants.js.map +1 -0
- package/build/lib/utils/helpers/index.d.ts +6 -0
- package/build/lib/utils/helpers/index.d.ts.map +1 -0
- package/build/lib/utils/helpers/index.js +21 -0
- package/build/lib/utils/helpers/index.js.map +1 -0
- package/build/lib/utils/helpers/install.d.ts +16 -0
- package/build/lib/utils/helpers/install.d.ts.map +1 -0
- package/build/lib/utils/helpers/install.js +39 -0
- package/build/lib/utils/helpers/install.js.map +1 -0
- package/build/lib/utils/helpers/manifest.d.ts +10 -0
- package/build/lib/utils/helpers/manifest.d.ts.map +1 -0
- package/build/lib/utils/helpers/manifest.js +124 -0
- package/build/lib/utils/helpers/manifest.js.map +1 -0
- package/build/lib/utils/helpers/resource.d.ts +11 -0
- package/build/lib/utils/helpers/resource.d.ts.map +1 -0
- package/build/lib/utils/helpers/resource.js +59 -0
- package/build/lib/utils/helpers/resource.js.map +1 -0
- package/build/lib/utils/helpers/sdk.d.ts +20 -0
- package/build/lib/utils/helpers/sdk.d.ts.map +1 -0
- package/build/lib/utils/helpers/sdk.js +80 -0
- package/build/lib/utils/helpers/sdk.js.map +1 -0
- package/build/lib/utils/index.d.ts +40 -0
- package/build/lib/utils/index.d.ts.map +1 -0
- package/build/lib/utils/index.js +58 -0
- package/build/lib/utils/index.js.map +1 -0
- package/build/lib/utils/lodash/core.d.ts +9 -0
- package/build/lib/utils/lodash/core.d.ts.map +1 -0
- package/build/lib/utils/lodash/core.js +60 -0
- package/build/lib/utils/lodash/core.js.map +1 -0
- package/build/lib/utils/lodash/index.d.ts +2 -0
- package/build/lib/utils/lodash/index.d.ts.map +1 -0
- package/build/lib/utils/lodash/index.js +18 -0
- package/build/lib/utils/lodash/index.js.map +1 -0
- package/lib/adb.ts +14 -6
- package/lib/index.ts +1 -1
- package/lib/logcat.ts +6 -11
- package/lib/tools/aab-utils.ts +1 -1
- package/lib/tools/android-manifest.ts +4 -4
- package/lib/tools/apk-signing.ts +11 -7
- package/lib/tools/apk-utils.ts +41 -42
- package/lib/tools/apks-utils.ts +4 -5
- package/lib/tools/app-commands.ts +40 -34
- package/lib/tools/device-settings.ts +17 -15
- package/lib/tools/emulator-commands.ts +22 -23
- package/lib/tools/fs-commands.ts +5 -4
- package/lib/tools/general-commands.ts +32 -27
- package/lib/tools/keyboard-commands.ts +6 -4
- package/lib/tools/lockmgmt.ts +17 -13
- package/lib/tools/logcat-commands.ts +5 -6
- package/lib/tools/network-commands.ts +1 -2
- package/lib/tools/process-commands.ts +11 -9
- package/lib/tools/system-calls.ts +54 -46
- package/lib/tools/types.ts +1 -1
- package/lib/utils/helpers/constants.ts +6 -0
- package/lib/utils/helpers/index.ts +10 -0
- package/lib/utils/helpers/install.ts +50 -0
- package/lib/utils/helpers/manifest.ts +127 -0
- package/lib/utils/helpers/resource.ts +63 -0
- package/lib/utils/helpers/sdk.ts +81 -0
- package/lib/utils/index.ts +25 -0
- package/lib/utils/lodash/core.ts +65 -0
- package/lib/utils/lodash/index.ts +1 -0
- package/package.json +3 -5
- package/build/lib/helpers.d.ts +0 -47
- package/build/lib/helpers.d.ts.map +0 -1
- package/build/lib/helpers.js +0 -283
- package/build/lib/helpers.js.map +0 -1
- package/lib/helpers.ts +0 -323
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import _ from 'lodash';
|
|
2
1
|
import {fs, tempDir, util, system} from '@appium/support';
|
|
3
2
|
import {log} from '../logger';
|
|
4
3
|
import {waitForCondition} from 'asyncbox';
|
|
5
4
|
import path from 'node:path';
|
|
6
5
|
import type {ADB} from '../adb';
|
|
7
6
|
import type {ExecError} from 'teen_process';
|
|
7
|
+
import {defaults, intersectionWith} from '../utils';
|
|
8
8
|
import type {
|
|
9
9
|
StringRecord,
|
|
10
10
|
InstallState,
|
|
@@ -79,7 +79,7 @@ export async function resolveLaunchableActivity(
|
|
|
79
79
|
if (!preferCmd || (await this.getApiLevel()) < 24) {
|
|
80
80
|
const stdout = await this.shell(['dumpsys', 'package', pkg]);
|
|
81
81
|
const names = parseLaunchableActivityNames(stdout);
|
|
82
|
-
if (
|
|
82
|
+
if (util.isEmpty(names)) {
|
|
83
83
|
log.debug(stdout);
|
|
84
84
|
throw new Error(
|
|
85
85
|
`Unable to resolve the launchable activity of '${pkg}'. Is it installed on the device?`,
|
|
@@ -113,7 +113,7 @@ export async function resolveLaunchableActivity(
|
|
|
113
113
|
outputFormat: this.EXEC_OUTPUT_FORMAT.FULL,
|
|
114
114
|
},
|
|
115
115
|
);
|
|
116
|
-
for (const line of (stdout || '').split('\n').map(
|
|
116
|
+
for (const line of (stdout || '').split('\n').map((x) => x.trim())) {
|
|
117
117
|
if (this.isValidClass(line)) {
|
|
118
118
|
return line;
|
|
119
119
|
}
|
|
@@ -204,8 +204,8 @@ export async function grantAllPermissions(this: ADB, pkg: string, apk?: string):
|
|
|
204
204
|
dumpsysOutput = dumpsysOutput || (await this.shell(['dumpsys', 'package', pkg]));
|
|
205
205
|
const requestedPermissions = await this.getReqPermissions(pkg, dumpsysOutput);
|
|
206
206
|
const grantedPermissions = await this.getGrantedPermissions(pkg, dumpsysOutput);
|
|
207
|
-
const permissionsToGrant =
|
|
208
|
-
if (
|
|
207
|
+
const permissionsToGrant = requestedPermissions.filter((x) => !grantedPermissions.includes(x));
|
|
208
|
+
if (util.isEmpty(permissionsToGrant)) {
|
|
209
209
|
log.info(`${pkg} contains no permissions available for granting`);
|
|
210
210
|
} else {
|
|
211
211
|
await this.grantPermissions(pkg, permissionsToGrant);
|
|
@@ -355,7 +355,7 @@ export async function stopAndClear(this: ADB, pkg: string): Promise<void> {
|
|
|
355
355
|
await this.clear(pkg);
|
|
356
356
|
} catch (e) {
|
|
357
357
|
const err = e as Error;
|
|
358
|
-
throw new Error(`Cannot stop and clear ${pkg}. Original error: ${err.message}
|
|
358
|
+
throw new Error(`Cannot stop and clear ${pkg}. Original error: ${err.message}`, {cause: e});
|
|
359
359
|
}
|
|
360
360
|
}
|
|
361
361
|
|
|
@@ -378,7 +378,10 @@ export async function getPackageInfo(this: ADB, pkg: string): Promise<AppInfo> {
|
|
|
378
378
|
return result;
|
|
379
379
|
}
|
|
380
380
|
|
|
381
|
-
const installedPattern = new RegExp(
|
|
381
|
+
const installedPattern = new RegExp(
|
|
382
|
+
`^\\s*Package\\s+\\[${util.escapeRegExp(pkg)}\\][^:]+:$`,
|
|
383
|
+
'm',
|
|
384
|
+
);
|
|
382
385
|
result.isInstalled = installedPattern.test(stdout);
|
|
383
386
|
if (!result.isInstalled) {
|
|
384
387
|
return result;
|
|
@@ -404,9 +407,9 @@ export async function getPackageInfo(this: ADB, pkg: string): Promise<AppInfo> {
|
|
|
404
407
|
* @throws {Error} If there was an error while fetching the .apk
|
|
405
408
|
*/
|
|
406
409
|
export async function pullApk(this: ADB, pkg: string, tmpDir: string): Promise<string> {
|
|
407
|
-
const stdout =
|
|
410
|
+
const stdout = (await this.shell(['pm', 'path', pkg])).trim();
|
|
408
411
|
const packageMarker = 'package:';
|
|
409
|
-
if (!
|
|
412
|
+
if (!stdout.startsWith(packageMarker)) {
|
|
410
413
|
throw new Error(`Cannot pull the .apk package for '${pkg}'. Original error: ${stdout}`);
|
|
411
414
|
}
|
|
412
415
|
|
|
@@ -433,7 +436,7 @@ export async function activateApp(this: ADB, appId: string): Promise<void> {
|
|
|
433
436
|
// The monkey command could raise an issue as https://stackoverflow.com/questions/44860475/how-to-use-the-monkey-command-with-an-android-system-that-doesnt-have-physical
|
|
434
437
|
// but '--pct-syskeys 0' could cause another background process issue. https://github.com/appium/appium/issues/16941#issuecomment-1129837285
|
|
435
438
|
const cmd = ['monkey', '-p', appId, '-c', 'android.intent.category.LAUNCHER', '1'];
|
|
436
|
-
let output
|
|
439
|
+
let output: string;
|
|
437
440
|
try {
|
|
438
441
|
output = await this.shell(cmd);
|
|
439
442
|
log.debug(`Command stdout: ${output}`);
|
|
@@ -546,8 +549,8 @@ export async function listInstalledPackages(
|
|
|
546
549
|
const error = e as ExecError;
|
|
547
550
|
// https://github.com/appium/appium-uiautomator2-driver/issues/810
|
|
548
551
|
if (
|
|
549
|
-
|
|
550
|
-
|
|
552
|
+
(error.stderr || error.stdout || error.message)?.includes('access user') &&
|
|
553
|
+
util.isEmpty(user)
|
|
551
554
|
) {
|
|
552
555
|
stdout = await this.shell([...cmd, '--user', '0']);
|
|
553
556
|
} else {
|
|
@@ -597,7 +600,8 @@ export async function startUri(
|
|
|
597
600
|
throw new Error(res);
|
|
598
601
|
}
|
|
599
602
|
} catch (e) {
|
|
600
|
-
|
|
603
|
+
const err = e instanceof Error ? e : new Error(String(e));
|
|
604
|
+
throw new Error(`Error attempting to start URI. Original error: ${err.message}`, {cause: e});
|
|
601
605
|
}
|
|
602
606
|
}
|
|
603
607
|
|
|
@@ -613,12 +617,12 @@ export async function startApp(this: ADB, startAppOptions: StartAppOptions): Pro
|
|
|
613
617
|
throw new Error('pkg, and activity or intent action, are required to start an application');
|
|
614
618
|
}
|
|
615
619
|
|
|
616
|
-
const options =
|
|
620
|
+
const options = {...startAppOptions};
|
|
617
621
|
if (options.activity) {
|
|
618
622
|
options.activity = options.activity.replace('$', '\\$');
|
|
619
623
|
}
|
|
620
624
|
// initializing defaults
|
|
621
|
-
|
|
625
|
+
defaults(options, {
|
|
622
626
|
waitPkg: options.pkg,
|
|
623
627
|
waitForLaunch: true,
|
|
624
628
|
waitActivity: false,
|
|
@@ -637,7 +641,7 @@ export async function startApp(this: ADB, startAppOptions: StartAppOptions): Pro
|
|
|
637
641
|
const shellOpts: {timeout?: number} = {};
|
|
638
642
|
if (
|
|
639
643
|
options.waitDuration !== undefined &&
|
|
640
|
-
|
|
644
|
+
Number.isInteger(options.waitDuration) &&
|
|
641
645
|
options.waitDuration >= 0
|
|
642
646
|
) {
|
|
643
647
|
shellOpts.timeout = options.waitDuration;
|
|
@@ -683,6 +687,7 @@ export async function startApp(this: ADB, startAppOptions: StartAppOptions): Pro
|
|
|
683
687
|
`Cannot start the '${appDescriptor}' application. ` +
|
|
684
688
|
`Consider checking the driver's troubleshooting documentation. ` +
|
|
685
689
|
`Original error: ${error.message}`,
|
|
690
|
+
{cause: e},
|
|
686
691
|
);
|
|
687
692
|
}
|
|
688
693
|
}
|
|
@@ -717,6 +722,7 @@ export async function getFocusedPackageAndActivity(this: ADB): Promise<PackageAc
|
|
|
717
722
|
const error = e as Error;
|
|
718
723
|
throw new Error(
|
|
719
724
|
`Could not retrieve the currently focused package and activity. Original error: ${error.message}`,
|
|
725
|
+
{cause: e},
|
|
720
726
|
);
|
|
721
727
|
}
|
|
722
728
|
|
|
@@ -750,15 +756,15 @@ export async function getFocusedPackageAndActivity(this: ADB): Promise<PackageAc
|
|
|
750
756
|
if (focusedAppCandidates.length > 1 && currentFocusAppCandidates.length > 0) {
|
|
751
757
|
// https://github.com/appium/appium/issues/17106
|
|
752
758
|
return (
|
|
753
|
-
|
|
754
|
-
if (
|
|
759
|
+
intersectionWith(focusedAppCandidates, currentFocusAppCandidates, (value, other) => {
|
|
760
|
+
if (value.appPackage !== other.appPackage) {
|
|
755
761
|
return false;
|
|
756
762
|
}
|
|
757
763
|
// https://github.com/appium/appium-adb/issues/797
|
|
758
764
|
const [thisActivity, otherActivity] = [value.appActivity, other.appActivity].map((name) =>
|
|
759
765
|
name?.replace(value.appPackage || '', ''),
|
|
760
766
|
);
|
|
761
|
-
return Boolean(thisActivity && otherActivity &&
|
|
767
|
+
return Boolean(thisActivity && otherActivity && thisActivity === otherActivity);
|
|
762
768
|
})[0] ?? focusedAppCandidates[0]
|
|
763
769
|
);
|
|
764
770
|
}
|
|
@@ -801,7 +807,7 @@ export async function waitForActivityOrNot(
|
|
|
801
807
|
throw new Error('Package and activity required.');
|
|
802
808
|
}
|
|
803
809
|
|
|
804
|
-
const splitNames = (names: string) => names.split(',').map(
|
|
810
|
+
const splitNames = (names: string) => names.split(',').map((x) => x.trim());
|
|
805
811
|
const allPackages = splitNames(pkg);
|
|
806
812
|
const allActivities = splitNames(activity);
|
|
807
813
|
|
|
@@ -860,7 +866,7 @@ export async function waitForActivityOrNot(
|
|
|
860
866
|
);
|
|
861
867
|
log.debug(`Focused fully qualified activity name: ${fullyQualifiedActivity}`);
|
|
862
868
|
const isFound =
|
|
863
|
-
|
|
869
|
+
allPackages.includes(appPackage) &&
|
|
864
870
|
possibleActivityPatterns.some((p) => p.test(fullyQualifiedActivity));
|
|
865
871
|
if ((!waitForStop && isFound) || (waitForStop && !isFound)) {
|
|
866
872
|
return true;
|
|
@@ -984,36 +990,36 @@ export function buildStartCmd(startAppOptions: StartCmdOptions, apiLevel: number
|
|
|
984
990
|
* in order to determine category names for these.
|
|
985
991
|
*/
|
|
986
992
|
export function parseLaunchableActivityNames(dumpsys: string): string[] {
|
|
987
|
-
const mainActivityNameRe = new RegExp(`^\\s*${
|
|
993
|
+
const mainActivityNameRe = new RegExp(`^\\s*${util.escapeRegExp(MAIN_ACTION)}:$`);
|
|
988
994
|
const categoryNameRe = /^\s*Category:\s+"([a-zA-Z0-9._/-]+)"$/;
|
|
989
995
|
const blocks: string[][] = [];
|
|
990
996
|
let blockStartIndent: number | null | undefined;
|
|
991
997
|
let block: string[] = [];
|
|
992
|
-
for (const line of dumpsys.split('\n').map(
|
|
993
|
-
const currentIndent = line.length -
|
|
998
|
+
for (const line of dumpsys.split('\n').map((x) => x.trimEnd())) {
|
|
999
|
+
const currentIndent = line.length - line.trimStart().length;
|
|
994
1000
|
if (mainActivityNameRe.test(line)) {
|
|
995
1001
|
blockStartIndent = currentIndent;
|
|
996
|
-
if (!
|
|
1002
|
+
if (!util.isEmpty(block)) {
|
|
997
1003
|
blocks.push(block);
|
|
998
1004
|
block = [];
|
|
999
1005
|
}
|
|
1000
1006
|
continue;
|
|
1001
1007
|
}
|
|
1002
|
-
if (
|
|
1008
|
+
if (blockStartIndent == null) {
|
|
1003
1009
|
continue;
|
|
1004
1010
|
}
|
|
1005
1011
|
|
|
1006
1012
|
if (currentIndent > blockStartIndent) {
|
|
1007
1013
|
block.push(line);
|
|
1008
1014
|
} else {
|
|
1009
|
-
if (!
|
|
1015
|
+
if (!util.isEmpty(block)) {
|
|
1010
1016
|
blocks.push(block);
|
|
1011
1017
|
block = [];
|
|
1012
1018
|
}
|
|
1013
1019
|
blockStartIndent = null;
|
|
1014
1020
|
}
|
|
1015
1021
|
}
|
|
1016
|
-
if (!
|
|
1022
|
+
if (!util.isEmpty(block)) {
|
|
1017
1023
|
blocks.push(block);
|
|
1018
1024
|
}
|
|
1019
1025
|
|
|
@@ -1038,7 +1044,7 @@ export function parseLaunchableActivityNames(dumpsys: string): string[] {
|
|
|
1038
1044
|
continue;
|
|
1039
1045
|
}
|
|
1040
1046
|
|
|
1041
|
-
for (const activityNameStr of item.map(
|
|
1047
|
+
for (const activityNameStr of item.map((x) => x.trim()).filter(Boolean)) {
|
|
1042
1048
|
const fqActivityName = activityNameStr.split(/\s+/)[1];
|
|
1043
1049
|
if (!matchComponentName(fqActivityName)) {
|
|
1044
1050
|
continue;
|
|
@@ -1080,7 +1086,7 @@ export function extractMatchingPermissions(
|
|
|
1080
1086
|
grantedState: boolean | null = null,
|
|
1081
1087
|
): string[] {
|
|
1082
1088
|
const groupPatternByName = (groupName: string) =>
|
|
1083
|
-
new RegExp(`^(\\s*${
|
|
1089
|
+
new RegExp(`^(\\s*${util.escapeRegExp(groupName)} permissions:[\\s\\S]+)`, 'm');
|
|
1084
1090
|
const indentPattern = /\S|$/;
|
|
1085
1091
|
const permissionNamePattern = /android\.\w*\.?permission\.\w+/;
|
|
1086
1092
|
const grantedStatePattern = /\bgranted=(\w+)/;
|
|
@@ -1119,7 +1125,7 @@ export function extractMatchingPermissions(
|
|
|
1119
1125
|
}
|
|
1120
1126
|
|
|
1121
1127
|
const filteredResult = result
|
|
1122
|
-
.filter((item) =>
|
|
1128
|
+
.filter((item) => typeof grantedState !== 'boolean' || item.granted === grantedState)
|
|
1123
1129
|
.map((item) => item.permission);
|
|
1124
1130
|
log.debug(
|
|
1125
1131
|
`Retrieved ${util.pluralize('permission', filteredResult.length, true)} ` +
|
|
@@ -1150,14 +1156,14 @@ export async function broadcast(this: ADB, intent: string): Promise<void> {
|
|
|
1150
1156
|
*/
|
|
1151
1157
|
export async function listAppProcessIds(this: ADB, pkg: string): Promise<number[]> {
|
|
1152
1158
|
log.debug(`Getting IDs of all '${pkg}' package`);
|
|
1153
|
-
const pidRegex = new RegExp(`ProcessRecord\\{[\\w]+\\s+(\\d+):${
|
|
1159
|
+
const pidRegex = new RegExp(`ProcessRecord\\{[\\w]+\\s+(\\d+):${util.escapeRegExp(pkg)}\\/`);
|
|
1154
1160
|
const processesInfo = await this.shell(['dumpsys', 'activity', 'processes']);
|
|
1155
1161
|
const pids = processesInfo
|
|
1156
1162
|
.split('\n')
|
|
1157
1163
|
.map((line) => line.match(pidRegex))
|
|
1158
1164
|
.filter((match) => !!match)
|
|
1159
1165
|
.map(([, pidStr]) => parseInt(pidStr, 10));
|
|
1160
|
-
return
|
|
1166
|
+
return util.uniq(pids);
|
|
1161
1167
|
}
|
|
1162
1168
|
|
|
1163
1169
|
/**
|
|
@@ -1168,7 +1174,7 @@ export async function listAppProcessIds(this: ADB, pkg: string): Promise<number[
|
|
|
1168
1174
|
* @returns True if the given package is running.
|
|
1169
1175
|
*/
|
|
1170
1176
|
export async function isAppRunning(this: ADB, pkg: string): Promise<boolean> {
|
|
1171
|
-
return !
|
|
1177
|
+
return !util.isEmpty(await this.listAppProcessIds(pkg));
|
|
1172
1178
|
}
|
|
1173
1179
|
|
|
1174
1180
|
/**
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import {log} from '../logger';
|
|
2
|
-
import _ from 'lodash';
|
|
3
2
|
import {retryInterval} from 'asyncbox';
|
|
4
3
|
import {util} from '@appium/support';
|
|
5
4
|
import type {ADB} from '../adb';
|
|
@@ -155,10 +154,10 @@ export async function setHttpProxy(
|
|
|
155
154
|
proxyPort: string | number,
|
|
156
155
|
): Promise<void> {
|
|
157
156
|
const proxy = `${proxyHost}:${proxyPort}`;
|
|
158
|
-
if (
|
|
157
|
+
if (typeof proxyHost === 'undefined') {
|
|
159
158
|
throw new Error(`Call to setHttpProxy method with undefined proxy_host: ${proxy}`);
|
|
160
159
|
}
|
|
161
|
-
if (
|
|
160
|
+
if (typeof proxyPort === 'undefined') {
|
|
162
161
|
throw new Error(`Call to setHttpProxy method with undefined proxy_port ${proxy}`);
|
|
163
162
|
}
|
|
164
163
|
|
|
@@ -231,7 +230,7 @@ export async function getTimeZone(this: ADB): Promise<string> {
|
|
|
231
230
|
return await this.getDeviceProperty('persist.sys.timezone');
|
|
232
231
|
} catch (e) {
|
|
233
232
|
const err = e as Error;
|
|
234
|
-
throw new Error(`Error getting timezone. Original error: ${err.message}
|
|
233
|
+
throw new Error(`Error getting timezone. Original error: ${err.message}`, {cause: e});
|
|
235
234
|
}
|
|
236
235
|
}
|
|
237
236
|
|
|
@@ -247,7 +246,9 @@ export async function getPlatformVersion(this: ADB): Promise<string> {
|
|
|
247
246
|
return await this.getDeviceProperty('ro.build.version.release');
|
|
248
247
|
} catch (e) {
|
|
249
248
|
const err = e as Error;
|
|
250
|
-
throw new Error(`Error getting device platform version. ` + `Original error: ${err.message}
|
|
249
|
+
throw new Error(`Error getting device platform version. ` + `Original error: ${err.message}`, {
|
|
250
|
+
cause: e,
|
|
251
|
+
});
|
|
251
252
|
}
|
|
252
253
|
}
|
|
253
254
|
|
|
@@ -268,7 +269,7 @@ export async function getLocationProviders(this: ADB): Promise<string[]> {
|
|
|
268
269
|
}
|
|
269
270
|
|
|
270
271
|
// To emulate the legacy behavior
|
|
271
|
-
return
|
|
272
|
+
return (await this.shell(['cmd', 'location', 'is-location-enabled'])).includes('true')
|
|
272
273
|
? ['gps']
|
|
273
274
|
: [];
|
|
274
275
|
}
|
|
@@ -401,8 +402,8 @@ export async function ensureCurrentLocale(
|
|
|
401
402
|
country?: string,
|
|
402
403
|
script?: string,
|
|
403
404
|
): Promise<boolean> {
|
|
404
|
-
const hasLanguage =
|
|
405
|
-
const hasCountry =
|
|
405
|
+
const hasLanguage = typeof language === 'string';
|
|
406
|
+
const hasCountry = typeof country === 'string';
|
|
406
407
|
if (!hasLanguage && !hasCountry) {
|
|
407
408
|
log.warn('ensureCurrentLocale requires language or country');
|
|
408
409
|
return false;
|
|
@@ -439,12 +440,12 @@ export async function ensureCurrentLocale(
|
|
|
439
440
|
? `${lcLanguage}-${script.toLowerCase()}-${lcCountry}`
|
|
440
441
|
: `${lcLanguage}-${lcCountry}`;
|
|
441
442
|
log.debug(`Requested locale: ${expectedLocale}. Actual locale: '${actualLocale}'`);
|
|
442
|
-
const languagePattern = `^${
|
|
443
|
+
const languagePattern = `^${util.escapeRegExp(lcLanguage)}-${script ? util.escapeRegExp(script) + '-' : ''}`;
|
|
443
444
|
const checkLocalePattern = (p: string) => new RegExp(p, 'i').test(actualLocale);
|
|
444
445
|
if (hasLanguage && !hasCountry) {
|
|
445
446
|
return checkLocalePattern(languagePattern);
|
|
446
447
|
}
|
|
447
|
-
const countryPattern = `${script ? '-' +
|
|
448
|
+
const countryPattern = `${script ? '-' + util.escapeRegExp(script) : ''}-${util.escapeRegExp(lcCountry)}$`;
|
|
448
449
|
if (!hasLanguage && hasCountry) {
|
|
449
450
|
return checkLocalePattern(countryPattern);
|
|
450
451
|
}
|
|
@@ -509,9 +510,10 @@ export async function getDeviceIdleWhitelist(this: ADB): Promise<string[]> {
|
|
|
509
510
|
|
|
510
511
|
log.info('Listing packages in Doze whitelist');
|
|
511
512
|
const output = await this.shell(['dumpsys', 'deviceidle', 'whitelist']);
|
|
512
|
-
return
|
|
513
|
+
return output
|
|
514
|
+
.trim()
|
|
513
515
|
.split(/\n/)
|
|
514
|
-
.map((line) =>
|
|
516
|
+
.map((line) => line.trim())
|
|
515
517
|
.filter(Boolean);
|
|
516
518
|
}
|
|
517
519
|
|
|
@@ -525,7 +527,7 @@ export async function getDeviceIdleWhitelist(this: ADB): Promise<string[]> {
|
|
|
525
527
|
* @returns `true` if the command to add package(s) has been executed
|
|
526
528
|
*/
|
|
527
529
|
export async function addToDeviceIdleWhitelist(this: ADB, ...packages: string[]): Promise<boolean> {
|
|
528
|
-
if (
|
|
530
|
+
if (util.isEmpty(packages) || (await this.getApiLevel()) < 23) {
|
|
529
531
|
// Doze mode has only been added since Android 6
|
|
530
532
|
return false;
|
|
531
533
|
}
|
|
@@ -621,7 +623,7 @@ export async function broadcastAirplaneMode(this: ADB, on: boolean): Promise<voi
|
|
|
621
623
|
} catch (e) {
|
|
622
624
|
const err = e as ExecError;
|
|
623
625
|
// https://github.com/appium/appium/issues/17422
|
|
624
|
-
if (
|
|
626
|
+
if ((err.stderr ?? '').includes('SecurityException')) {
|
|
625
627
|
try {
|
|
626
628
|
await this.shell(args, {privileged: true});
|
|
627
629
|
return;
|
|
@@ -708,7 +710,7 @@ export async function getScreenOrientation(this: ADB): Promise<number | null> {
|
|
|
708
710
|
* @returns Either the same error or the decorated one
|
|
709
711
|
*/
|
|
710
712
|
function decorateWriteSecureSettingsException(e: Error): Error {
|
|
711
|
-
if (
|
|
713
|
+
if (e.message?.includes('requires:android.permission.WRITE_SECURE_SETTINGS')) {
|
|
712
714
|
e.message = `Check https://github.com/appium/appium/issues/13802 for throubleshooting. ${e.message}`;
|
|
713
715
|
}
|
|
714
716
|
return e;
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import {log} from '../logger';
|
|
2
|
-
import _ from 'lodash';
|
|
3
2
|
import net from 'node:net';
|
|
4
3
|
import {util, fs} from '@appium/support';
|
|
5
4
|
import path from 'node:path';
|
|
@@ -25,7 +24,7 @@ import type {
|
|
|
25
24
|
*/
|
|
26
25
|
export async function isEmulatorConnected(this: ADB): Promise<boolean> {
|
|
27
26
|
const emulators = await this.getConnectedEmulators();
|
|
28
|
-
return
|
|
27
|
+
return emulators.some((x) => x && x.udid === this.curDeviceId);
|
|
29
28
|
}
|
|
30
29
|
|
|
31
30
|
/**
|
|
@@ -71,10 +70,10 @@ export async function rotate(this: ADB): Promise<void> {
|
|
|
71
70
|
* @param state - Either 'on' or 'off'.
|
|
72
71
|
*/
|
|
73
72
|
export async function powerAC(this: ADB, state: PowerAcStates = 'on'): Promise<void> {
|
|
74
|
-
if (
|
|
73
|
+
if (!(Object.values(this.POWER_AC_STATES) as string[]).includes(state)) {
|
|
75
74
|
throw new TypeError(
|
|
76
75
|
`Wrong power AC state sent '${state}'. ` +
|
|
77
|
-
`Supported values: ${
|
|
76
|
+
`Supported values: ${Object.values(this.POWER_AC_STATES)}]`,
|
|
78
77
|
);
|
|
79
78
|
}
|
|
80
79
|
await this.adbExecEmu(['power', 'ac', state]);
|
|
@@ -88,12 +87,12 @@ export async function powerAC(this: ADB, state: PowerAcStates = 'on'): Promise<v
|
|
|
88
87
|
* @throws - If sensor type or sensor value is not defined
|
|
89
88
|
*/
|
|
90
89
|
export async function sensorSet(this: ADB, sensor: string, value: Sensors): Promise<void> {
|
|
91
|
-
if (!
|
|
90
|
+
if (!(Object.values(this.SENSORS) as string[]).includes(sensor)) {
|
|
92
91
|
throw new TypeError(
|
|
93
|
-
`Unsupported sensor sent '${sensor}'. ` + `Supported values: ${
|
|
92
|
+
`Unsupported sensor sent '${sensor}'. ` + `Supported values: ${Object.values(this.SENSORS)}]`,
|
|
94
93
|
);
|
|
95
94
|
}
|
|
96
|
-
if (
|
|
95
|
+
if (value == null) {
|
|
97
96
|
throw new TypeError(
|
|
98
97
|
`Missing/invalid sensor value argument. ` +
|
|
99
98
|
`You need to provide a valid value to set to the sensor in ` +
|
|
@@ -136,10 +135,10 @@ export async function sendSMS(
|
|
|
136
135
|
phoneNumber: string | number,
|
|
137
136
|
message = '',
|
|
138
137
|
): Promise<void> {
|
|
139
|
-
if (
|
|
138
|
+
if (!message) {
|
|
140
139
|
throw new TypeError('SMS message must not be empty');
|
|
141
140
|
}
|
|
142
|
-
if (!
|
|
141
|
+
if (!Number.isInteger(phoneNumber) && util.isEmpty(phoneNumber)) {
|
|
143
142
|
throw new TypeError('Phone number most not be empty');
|
|
144
143
|
}
|
|
145
144
|
await this.adbExecEmu(['sms', 'send', `${phoneNumber}`, message]);
|
|
@@ -158,12 +157,12 @@ export async function gsmCall(
|
|
|
158
157
|
phoneNumber: string | number,
|
|
159
158
|
action: GsmCallActions,
|
|
160
159
|
): Promise<void> {
|
|
161
|
-
if (!
|
|
160
|
+
if (!Object.values(this.GSM_CALL_ACTIONS).includes(action)) {
|
|
162
161
|
throw new TypeError(
|
|
163
|
-
`Invalid gsm action param ${action}. Supported values: ${
|
|
162
|
+
`Invalid gsm action param ${action}. Supported values: ${Object.values(this.GSM_CALL_ACTIONS)}`,
|
|
164
163
|
);
|
|
165
164
|
}
|
|
166
|
-
if (!
|
|
165
|
+
if (!Number.isInteger(phoneNumber) && util.isEmpty(phoneNumber)) {
|
|
167
166
|
throw new TypeError('Phone number most not be empty');
|
|
168
167
|
}
|
|
169
168
|
await this.adbExecEmu(['gsm', action, `${phoneNumber}`]);
|
|
@@ -177,9 +176,9 @@ export async function gsmCall(
|
|
|
177
176
|
*/
|
|
178
177
|
export async function gsmSignal(this: ADB, strength: GsmSignalStrength = 4): Promise<void> {
|
|
179
178
|
const strengthInt = parseInt(`${strength}`, 10);
|
|
180
|
-
if (!
|
|
179
|
+
if (!(this.GSM_SIGNAL_STRENGTHS as readonly number[]).includes(strengthInt)) {
|
|
181
180
|
throw new TypeError(
|
|
182
|
-
`Invalid signal strength param ${strength}. Supported values: ${
|
|
181
|
+
`Invalid signal strength param ${strength}. Supported values: ${Object.values(this.GSM_SIGNAL_STRENGTHS)}`,
|
|
183
182
|
);
|
|
184
183
|
}
|
|
185
184
|
log.info('gsm signal-profile <strength> changes the reported strength on next (15s) update.');
|
|
@@ -194,9 +193,9 @@ export async function gsmSignal(this: ADB, strength: GsmSignalStrength = 4): Pro
|
|
|
194
193
|
*/
|
|
195
194
|
export async function gsmVoice(this: ADB, state: GsmVoiceStates = 'on'): Promise<void> {
|
|
196
195
|
// gsm voice <state> allows you to change the state of your GPRS connection
|
|
197
|
-
if (!
|
|
196
|
+
if (!Object.values(this.GSM_VOICE_STATES).includes(state)) {
|
|
198
197
|
throw new TypeError(
|
|
199
|
-
`Invalid gsm voice state param ${state}. Supported values: ${
|
|
198
|
+
`Invalid gsm voice state param ${state}. Supported values: ${Object.values(this.GSM_VOICE_STATES)}`,
|
|
200
199
|
);
|
|
201
200
|
}
|
|
202
201
|
await this.adbExecEmu(['gsm', 'voice', state]);
|
|
@@ -211,9 +210,9 @@ export async function gsmVoice(this: ADB, state: GsmVoiceStates = 'on'): Promise
|
|
|
211
210
|
*/
|
|
212
211
|
export async function networkSpeed(this: ADB, speed: NetworkSpeed = 'full'): Promise<void> {
|
|
213
212
|
// network speed <speed> allows you to set the network speed emulation.
|
|
214
|
-
if (!
|
|
213
|
+
if (!Object.values(this.NETWORK_SPEED).includes(speed)) {
|
|
215
214
|
throw new Error(
|
|
216
|
-
`Invalid network speed param ${speed}. Supported values: ${
|
|
215
|
+
`Invalid network speed param ${speed}. Supported values: ${Object.values(this.NETWORK_SPEED)}`,
|
|
217
216
|
);
|
|
218
217
|
}
|
|
219
218
|
await this.adbExecEmu(['network', 'speed', speed]);
|
|
@@ -306,7 +305,7 @@ export async function execEmuConsoleCommand(
|
|
|
306
305
|
if (!isCommandSent) {
|
|
307
306
|
clearTimeout(initTimeoutObj);
|
|
308
307
|
serverResponse = [];
|
|
309
|
-
const cmdStr =
|
|
308
|
+
const cmdStr = Array.isArray(cmd) ? util.quote(cmd) : `${cmd}`;
|
|
310
309
|
log.debug(`Executing Emulator console command: ${cmdStr}`);
|
|
311
310
|
client.write(cmdStr);
|
|
312
311
|
client.write(eol);
|
|
@@ -338,7 +337,7 @@ export async function execEmuConsoleCommand(
|
|
|
338
337
|
clearTimeout(execTimeoutObj);
|
|
339
338
|
client.end();
|
|
340
339
|
const outputArr = output.split(eol);
|
|
341
|
-
return reject(
|
|
340
|
+
return reject((outputArr.at(-1) || '').trim());
|
|
342
341
|
}
|
|
343
342
|
});
|
|
344
343
|
});
|
|
@@ -386,7 +385,7 @@ export async function getEmuImageProperties(this: ADB, avdName: string): Promise
|
|
|
386
385
|
const avd = avds.find(({name}) => name === avdName);
|
|
387
386
|
if (!avd) {
|
|
388
387
|
let msg = `Cannot find '${avdName}' emulator. `;
|
|
389
|
-
if (
|
|
388
|
+
if (avds.length === 0) {
|
|
390
389
|
msg += `No emulators have been detected on your system`;
|
|
391
390
|
} else {
|
|
392
391
|
msg += `Available avd names are: ${avds.map(({name}) => name)}`;
|
|
@@ -407,7 +406,7 @@ export async function checkAvdExist(this: ADB, avdName: string): Promise<boolean
|
|
|
407
406
|
const avds = await listEmulators();
|
|
408
407
|
if (!avds.some(({name}) => name === avdName)) {
|
|
409
408
|
let msg = `Avd '${avdName}' is not available. `;
|
|
410
|
-
if (
|
|
409
|
+
if (avds.length === 0) {
|
|
411
410
|
msg += `No emulators have been detected on your system`;
|
|
412
411
|
} else {
|
|
413
412
|
msg += `Please select your avd name from one of these: '${avds.map(({name}) => name)}'`;
|
|
@@ -476,7 +475,7 @@ async function getAvdConfigPaths(avdsRoot: string): Promise<EmuInfo[]> {
|
|
|
476
475
|
const avdName = path.basename(confPath).split('.').slice(0, -1).join('.');
|
|
477
476
|
return {name: avdName, config: confPath};
|
|
478
477
|
})
|
|
479
|
-
.filter(({name}) =>
|
|
478
|
+
.filter(({name}) => name?.trim());
|
|
480
479
|
}
|
|
481
480
|
|
|
482
481
|
/**
|
package/lib/tools/fs-commands.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import _ from 'lodash';
|
|
2
1
|
import path from 'node:path';
|
|
3
2
|
import type {ADB} from '../adb';
|
|
4
3
|
import type {TeenProcessExecOptions} from 'teen_process';
|
|
@@ -13,7 +12,7 @@ export async function fileExists(this: ADB, remotePath: string): Promise<boolean
|
|
|
13
12
|
const passFlag = '__PASS__';
|
|
14
13
|
const checkCmd = `[ -e '${shellEscapeSingleQuotes(remotePath)}' ] && echo ${passFlag}`;
|
|
15
14
|
try {
|
|
16
|
-
return
|
|
15
|
+
return (await this.shell([checkCmd])).includes(passFlag);
|
|
17
16
|
} catch {
|
|
18
17
|
return false;
|
|
19
18
|
}
|
|
@@ -60,12 +59,14 @@ export async function fileSize(this: ADB, remotePath: string): Promise<number> {
|
|
|
60
59
|
}
|
|
61
60
|
// https://regex101.com/r/fOs4P4/8
|
|
62
61
|
const match = /[rwxsStT\-+]{10}[\s\d]*\s[^\s]+\s+[^\s]+\s+(\d+)/.exec(files[0]);
|
|
63
|
-
if (!match ||
|
|
62
|
+
if (!match || Number.isNaN(parseInt(match[1], 10))) {
|
|
64
63
|
throw new Error(`Unable to parse size from list output: '${files[0]}'`);
|
|
65
64
|
}
|
|
66
65
|
return parseInt(match[1], 10);
|
|
67
66
|
} catch (err) {
|
|
68
|
-
throw new Error(`Unable to get file size for '${remotePath}': ${(err as Error).message}
|
|
67
|
+
throw new Error(`Unable to get file size for '${remotePath}': ${(err as Error).message}`, {
|
|
68
|
+
cause: err,
|
|
69
|
+
});
|
|
69
70
|
}
|
|
70
71
|
}
|
|
71
72
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {log} from '../logger';
|
|
2
|
-
import _ from 'lodash';
|
|
3
2
|
import {fs, util} from '@appium/support';
|
|
4
3
|
import {SubProcess, exec, type ExecError} from 'teen_process';
|
|
5
4
|
import type {ADB} from '../adb';
|
|
6
5
|
import type {ScreenrecordOptions, StringRecord} from './types';
|
|
6
|
+
import {memoize} from '../utils';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Get the path to adb executable amd assign it
|
|
@@ -64,33 +64,37 @@ export async function initBundletool(this: ADB): Promise<void> {
|
|
|
64
64
|
* calls return the same value as the first one.
|
|
65
65
|
*/
|
|
66
66
|
export async function getApiLevel(this: ADB): Promise<number> {
|
|
67
|
-
if (
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
let apiLevel = parseInt(strOutput.trim(), 10);
|
|
67
|
+
if (Number.isInteger(this._apiLevel)) {
|
|
68
|
+
return this._apiLevel as number;
|
|
69
|
+
}
|
|
71
70
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
const apiLevelDiff = apiLevel - 28;
|
|
76
|
-
const codename = String.fromCharCode(charCodeQ + apiLevelDiff);
|
|
77
|
-
if (apiLevelDiff >= 0 && (await this.getPlatformVersion()).toLowerCase() === codename) {
|
|
78
|
-
log.debug(
|
|
79
|
-
`Release version is ${codename.toUpperCase()} but found API Level ${apiLevel}. Setting API Level to ${apiLevel + 1}`,
|
|
80
|
-
);
|
|
81
|
-
apiLevel++;
|
|
82
|
-
}
|
|
71
|
+
try {
|
|
72
|
+
const strOutput = await this.getDeviceProperty('ro.build.version.sdk');
|
|
73
|
+
let apiLevel = parseInt(strOutput.trim(), 10);
|
|
83
74
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
75
|
+
// Workaround for preview/beta platform API level
|
|
76
|
+
const charCodeQ = 'q'.charCodeAt(0);
|
|
77
|
+
// 28 is the first API Level, where Android SDK started returning letters in response to getPlatformVersion
|
|
78
|
+
const apiLevelDiff = apiLevel - 28;
|
|
79
|
+
const codename = String.fromCharCode(charCodeQ + apiLevelDiff);
|
|
80
|
+
if (apiLevelDiff >= 0 && (await this.getPlatformVersion()).toLowerCase() === codename) {
|
|
81
|
+
log.debug(
|
|
82
|
+
`Release version is ${codename.toUpperCase()} but found API Level ${apiLevel}. Setting API Level to ${apiLevel + 1}`,
|
|
83
|
+
);
|
|
84
|
+
apiLevel++;
|
|
91
85
|
}
|
|
86
|
+
|
|
87
|
+
this._apiLevel = apiLevel;
|
|
88
|
+
log.debug(`Device API level: ${this._apiLevel}`);
|
|
89
|
+
if (isNaN(this._apiLevel)) {
|
|
90
|
+
throw new Error(`The actual output '${strOutput}' cannot be converted to an integer`);
|
|
91
|
+
}
|
|
92
|
+
return this._apiLevel;
|
|
93
|
+
} catch (e) {
|
|
94
|
+
throw new Error(`Error getting device API level. Original error: ${(e as Error).message}`, {
|
|
95
|
+
cause: e,
|
|
96
|
+
});
|
|
92
97
|
}
|
|
93
|
-
return this._apiLevel as number;
|
|
94
98
|
}
|
|
95
99
|
|
|
96
100
|
/**
|
|
@@ -164,7 +168,7 @@ export async function restart(this: ADB): Promise<void> {
|
|
|
164
168
|
await this.startLogcat(this._logcatStartupParams);
|
|
165
169
|
} catch (e) {
|
|
166
170
|
const err = e as Error;
|
|
167
|
-
throw new Error(`Restart failed. Original error: ${err.message}
|
|
171
|
+
throw new Error(`Restart failed. Original error: ${err.message}`, {cause: e});
|
|
168
172
|
}
|
|
169
173
|
}
|
|
170
174
|
|
|
@@ -235,7 +239,7 @@ export function screenrecord(
|
|
|
235
239
|
export async function listFeatures(this: ADB): Promise<string[]> {
|
|
236
240
|
this._memoizedFeatures =
|
|
237
241
|
this._memoizedFeatures ||
|
|
238
|
-
|
|
242
|
+
memoize(
|
|
239
243
|
async () => await this.adbExec(['features']),
|
|
240
244
|
() => this.curDeviceId,
|
|
241
245
|
);
|
|
@@ -250,7 +254,7 @@ export async function listFeatures(this: ADB): Promise<string[]> {
|
|
|
250
254
|
.filter(Boolean);
|
|
251
255
|
} catch (e) {
|
|
252
256
|
const err = e as ExecError;
|
|
253
|
-
if (
|
|
257
|
+
if ((err.stderr ?? '').includes('unknown command')) {
|
|
254
258
|
return [];
|
|
255
259
|
}
|
|
256
260
|
throw err;
|
|
@@ -327,6 +331,7 @@ export async function takeScreenshot(this: ADB, displayId?: number | string): Pr
|
|
|
327
331
|
throw new Error(
|
|
328
332
|
`Screenshot of the ${displayDescr} failed. ` +
|
|
329
333
|
`Code: '${err.code ?? 'unknown'}', output: '${outputStr}'`,
|
|
334
|
+
{cause: e},
|
|
330
335
|
);
|
|
331
336
|
}
|
|
332
337
|
if (stdout.length === 0) {
|