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.
Files changed (134) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/build/lib/adb.d.ts +9 -4
  3. package/build/lib/adb.d.ts.map +1 -1
  4. package/build/lib/adb.js +8 -9
  5. package/build/lib/adb.js.map +1 -1
  6. package/build/lib/index.d.ts +1 -1
  7. package/build/lib/index.d.ts.map +1 -1
  8. package/build/lib/index.js +2 -2
  9. package/build/lib/index.js.map +1 -1
  10. package/build/lib/logcat.d.ts.map +1 -1
  11. package/build/lib/logcat.js +6 -14
  12. package/build/lib/logcat.js.map +1 -1
  13. package/build/lib/tools/aab-utils.d.ts.map +1 -1
  14. package/build/lib/tools/aab-utils.js +2 -2
  15. package/build/lib/tools/aab-utils.js.map +1 -1
  16. package/build/lib/tools/android-manifest.d.ts.map +1 -1
  17. package/build/lib/tools/android-manifest.js +11 -12
  18. package/build/lib/tools/android-manifest.js.map +1 -1
  19. package/build/lib/tools/apk-signing.d.ts.map +1 -1
  20. package/build/lib/tools/apk-signing.js +18 -19
  21. package/build/lib/tools/apk-signing.js.map +1 -1
  22. package/build/lib/tools/apk-utils.d.ts.map +1 -1
  23. package/build/lib/tools/apk-utils.js +39 -40
  24. package/build/lib/tools/apk-utils.js.map +1 -1
  25. package/build/lib/tools/apks-utils.d.ts.map +1 -1
  26. package/build/lib/tools/apks-utils.js +8 -9
  27. package/build/lib/tools/apks-utils.js.map +1 -1
  28. package/build/lib/tools/app-commands.d.ts.map +1 -1
  29. package/build/lib/tools/app-commands.js +37 -36
  30. package/build/lib/tools/app-commands.js.map +1 -1
  31. package/build/lib/tools/device-settings.d.ts.map +1 -1
  32. package/build/lib/tools/device-settings.js +17 -18
  33. package/build/lib/tools/device-settings.js.map +1 -1
  34. package/build/lib/tools/emulator-commands.d.ts.map +1 -1
  35. package/build/lib/tools/emulator-commands.js +22 -23
  36. package/build/lib/tools/emulator-commands.js.map +1 -1
  37. package/build/lib/tools/fs-commands.d.ts.map +1 -1
  38. package/build/lib/tools/fs-commands.js +5 -4
  39. package/build/lib/tools/fs-commands.js.map +1 -1
  40. package/build/lib/tools/general-commands.d.ts.map +1 -1
  41. package/build/lib/tools/general-commands.js +29 -29
  42. package/build/lib/tools/general-commands.js.map +1 -1
  43. package/build/lib/tools/keyboard-commands.d.ts.map +1 -1
  44. package/build/lib/tools/keyboard-commands.js +6 -4
  45. package/build/lib/tools/keyboard-commands.js.map +1 -1
  46. package/build/lib/tools/lockmgmt.d.ts.map +1 -1
  47. package/build/lib/tools/lockmgmt.js +17 -20
  48. package/build/lib/tools/lockmgmt.js.map +1 -1
  49. package/build/lib/tools/logcat-commands.d.ts.map +1 -1
  50. package/build/lib/tools/logcat-commands.js +5 -9
  51. package/build/lib/tools/logcat-commands.js.map +1 -1
  52. package/build/lib/tools/network-commands.d.ts.map +1 -1
  53. package/build/lib/tools/network-commands.js +1 -5
  54. package/build/lib/tools/network-commands.js.map +1 -1
  55. package/build/lib/tools/process-commands.d.ts.map +1 -1
  56. package/build/lib/tools/process-commands.js +11 -12
  57. package/build/lib/tools/process-commands.js.map +1 -1
  58. package/build/lib/tools/system-calls.d.ts +12 -5
  59. package/build/lib/tools/system-calls.d.ts.map +1 -1
  60. package/build/lib/tools/system-calls.js +56 -50
  61. package/build/lib/tools/system-calls.js.map +1 -1
  62. package/build/lib/tools/types.d.ts +1 -1
  63. package/build/lib/tools/types.d.ts.map +1 -1
  64. package/build/lib/utils/helpers/constants.d.ts +6 -0
  65. package/build/lib/utils/helpers/constants.d.ts.map +1 -0
  66. package/build/lib/utils/helpers/constants.js +9 -0
  67. package/build/lib/utils/helpers/constants.js.map +1 -0
  68. package/build/lib/utils/helpers/index.d.ts +6 -0
  69. package/build/lib/utils/helpers/index.d.ts.map +1 -0
  70. package/build/lib/utils/helpers/index.js +21 -0
  71. package/build/lib/utils/helpers/index.js.map +1 -0
  72. package/build/lib/utils/helpers/install.d.ts +16 -0
  73. package/build/lib/utils/helpers/install.d.ts.map +1 -0
  74. package/build/lib/utils/helpers/install.js +39 -0
  75. package/build/lib/utils/helpers/install.js.map +1 -0
  76. package/build/lib/utils/helpers/manifest.d.ts +10 -0
  77. package/build/lib/utils/helpers/manifest.d.ts.map +1 -0
  78. package/build/lib/utils/helpers/manifest.js +124 -0
  79. package/build/lib/utils/helpers/manifest.js.map +1 -0
  80. package/build/lib/utils/helpers/resource.d.ts +11 -0
  81. package/build/lib/utils/helpers/resource.d.ts.map +1 -0
  82. package/build/lib/utils/helpers/resource.js +59 -0
  83. package/build/lib/utils/helpers/resource.js.map +1 -0
  84. package/build/lib/utils/helpers/sdk.d.ts +20 -0
  85. package/build/lib/utils/helpers/sdk.d.ts.map +1 -0
  86. package/build/lib/utils/helpers/sdk.js +80 -0
  87. package/build/lib/utils/helpers/sdk.js.map +1 -0
  88. package/build/lib/utils/index.d.ts +40 -0
  89. package/build/lib/utils/index.d.ts.map +1 -0
  90. package/build/lib/utils/index.js +58 -0
  91. package/build/lib/utils/index.js.map +1 -0
  92. package/build/lib/utils/lodash/core.d.ts +9 -0
  93. package/build/lib/utils/lodash/core.d.ts.map +1 -0
  94. package/build/lib/utils/lodash/core.js +60 -0
  95. package/build/lib/utils/lodash/core.js.map +1 -0
  96. package/build/lib/utils/lodash/index.d.ts +2 -0
  97. package/build/lib/utils/lodash/index.d.ts.map +1 -0
  98. package/build/lib/utils/lodash/index.js +18 -0
  99. package/build/lib/utils/lodash/index.js.map +1 -0
  100. package/lib/adb.ts +14 -6
  101. package/lib/index.ts +1 -1
  102. package/lib/logcat.ts +6 -11
  103. package/lib/tools/aab-utils.ts +1 -1
  104. package/lib/tools/android-manifest.ts +4 -4
  105. package/lib/tools/apk-signing.ts +11 -7
  106. package/lib/tools/apk-utils.ts +41 -42
  107. package/lib/tools/apks-utils.ts +4 -5
  108. package/lib/tools/app-commands.ts +40 -34
  109. package/lib/tools/device-settings.ts +17 -15
  110. package/lib/tools/emulator-commands.ts +22 -23
  111. package/lib/tools/fs-commands.ts +5 -4
  112. package/lib/tools/general-commands.ts +32 -27
  113. package/lib/tools/keyboard-commands.ts +6 -4
  114. package/lib/tools/lockmgmt.ts +17 -13
  115. package/lib/tools/logcat-commands.ts +5 -6
  116. package/lib/tools/network-commands.ts +1 -2
  117. package/lib/tools/process-commands.ts +11 -9
  118. package/lib/tools/system-calls.ts +54 -46
  119. package/lib/tools/types.ts +1 -1
  120. package/lib/utils/helpers/constants.ts +6 -0
  121. package/lib/utils/helpers/index.ts +10 -0
  122. package/lib/utils/helpers/install.ts +50 -0
  123. package/lib/utils/helpers/manifest.ts +127 -0
  124. package/lib/utils/helpers/resource.ts +63 -0
  125. package/lib/utils/helpers/sdk.ts +81 -0
  126. package/lib/utils/index.ts +25 -0
  127. package/lib/utils/lodash/core.ts +65 -0
  128. package/lib/utils/lodash/index.ts +1 -0
  129. package/package.json +3 -5
  130. package/build/lib/helpers.d.ts +0 -47
  131. package/build/lib/helpers.d.ts.map +0 -1
  132. package/build/lib/helpers.js +0 -283
  133. package/build/lib/helpers.js.map +0 -1
  134. 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 (_.isEmpty(names)) {
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(_.trim)) {
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 = _.difference(requestedPermissions, grantedPermissions);
208
- if (_.isEmpty(permissionsToGrant)) {
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(`^\\s*Package\\s+\\[${_.escapeRegExp(pkg)}\\][^:]+:$`, 'm');
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 = _.trim(await this.shell(['pm', 'path', pkg]));
410
+ const stdout = (await this.shell(['pm', 'path', pkg])).trim();
408
411
  const packageMarker = 'package:';
409
- if (!_.startsWith(stdout, packageMarker)) {
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
- _.includes(error.stderr || error.stdout || error.message, 'access user') &&
550
- _.isEmpty(user)
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
- throw new Error(`Error attempting to start URI. Original error: ${e}`);
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 = _.clone(startAppOptions);
620
+ const options = {...startAppOptions};
617
621
  if (options.activity) {
618
622
  options.activity = options.activity.replace('$', '\\$');
619
623
  }
620
624
  // initializing defaults
621
- _.defaults(options, {
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
- _.isInteger(options.waitDuration) &&
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
- _.intersectionWith(focusedAppCandidates, currentFocusAppCandidates, (value, other) => {
754
- if (!_.isEqual(value.appPackage, other.appPackage)) {
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 && _.isEqual(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(_.trim);
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
- _.includes(allPackages, appPackage) &&
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*${_.escapeRegExp(MAIN_ACTION)}:$`);
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(_.trimEnd)) {
993
- const currentIndent = line.length - _.trimStart(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 (!_.isEmpty(block)) {
1002
+ if (!util.isEmpty(block)) {
997
1003
  blocks.push(block);
998
1004
  block = [];
999
1005
  }
1000
1006
  continue;
1001
1007
  }
1002
- if (_.isNil(blockStartIndent)) {
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 (!_.isEmpty(block)) {
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 (!_.isEmpty(block)) {
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(_.trim).filter(Boolean)) {
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*${_.escapeRegExp(groupName)} permissions:[\\s\\S]+)`, 'm');
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) => !_.isBoolean(grantedState) || item.granted === grantedState)
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+):${_.escapeRegExp(pkg)}\\/`);
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 _.uniq(pids);
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 !_.isEmpty(await this.listAppProcessIds(pkg));
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 (_.isUndefined(proxyHost)) {
157
+ if (typeof proxyHost === 'undefined') {
159
158
  throw new Error(`Call to setHttpProxy method with undefined proxy_host: ${proxy}`);
160
159
  }
161
- if (_.isUndefined(proxyPort)) {
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 _.includes(await this.shell(['cmd', 'location', 'is-location-enabled']), 'true')
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 = _.isString(language);
405
- const hasCountry = _.isString(country);
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 = `^${_.escapeRegExp(lcLanguage)}-${script ? _.escapeRegExp(script) + '-' : ''}`;
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 ? '-' + _.escapeRegExp(script) : ''}-${_.escapeRegExp(lcCountry)}$`;
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 _.trim(output)
513
+ return output
514
+ .trim()
513
515
  .split(/\n/)
514
- .map((line) => _.trim(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 (_.isEmpty(packages) || (await this.getApiLevel()) < 23) {
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 (_.includes(err.stderr, 'SecurityException')) {
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 (_.includes(e.message, 'requires:android.permission.WRITE_SECURE_SETTINGS')) {
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 !!_.find(emulators, (x) => x && x.udid === this.curDeviceId);
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 (_.values(this.POWER_AC_STATES).indexOf(state) === -1) {
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: ${_.values(this.POWER_AC_STATES)}]`,
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 (!_.includes(this.SENSORS, sensor)) {
90
+ if (!(Object.values(this.SENSORS) as string[]).includes(sensor)) {
92
91
  throw new TypeError(
93
- `Unsupported sensor sent '${sensor}'. ` + `Supported values: ${_.values(this.SENSORS)}]`,
92
+ `Unsupported sensor sent '${sensor}'. ` + `Supported values: ${Object.values(this.SENSORS)}]`,
94
93
  );
95
94
  }
96
- if (_.isNil(value)) {
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 (_.isEmpty(message)) {
138
+ if (!message) {
140
139
  throw new TypeError('SMS message must not be empty');
141
140
  }
142
- if (!_.isInteger(phoneNumber) && _.isEmpty(phoneNumber)) {
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 (!_.values(this.GSM_CALL_ACTIONS).includes(action)) {
160
+ if (!Object.values(this.GSM_CALL_ACTIONS).includes(action)) {
162
161
  throw new TypeError(
163
- `Invalid gsm action param ${action}. Supported values: ${_.values(this.GSM_CALL_ACTIONS)}`,
162
+ `Invalid gsm action param ${action}. Supported values: ${Object.values(this.GSM_CALL_ACTIONS)}`,
164
163
  );
165
164
  }
166
- if (!_.isInteger(phoneNumber) && _.isEmpty(phoneNumber)) {
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 (!_.includes(this.GSM_SIGNAL_STRENGTHS, strengthInt)) {
179
+ if (!(this.GSM_SIGNAL_STRENGTHS as readonly number[]).includes(strengthInt)) {
181
180
  throw new TypeError(
182
- `Invalid signal strength param ${strength}. Supported values: ${_.values(this.GSM_SIGNAL_STRENGTHS)}`,
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 (!_.values(this.GSM_VOICE_STATES).includes(state)) {
196
+ if (!Object.values(this.GSM_VOICE_STATES).includes(state)) {
198
197
  throw new TypeError(
199
- `Invalid gsm voice state param ${state}. Supported values: ${_.values(this.GSM_VOICE_STATES)}`,
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 (!_.values(this.NETWORK_SPEED).includes(speed)) {
213
+ if (!Object.values(this.NETWORK_SPEED).includes(speed)) {
215
214
  throw new Error(
216
- `Invalid network speed param ${speed}. Supported values: ${_.values(this.NETWORK_SPEED)}`,
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 = _.isArray(cmd) ? util.quote(cmd) : `${cmd}`;
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(_.trim(_.last(outputArr) || ''));
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 (_.isEmpty(avds)) {
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 (_.isEmpty(avds)) {
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}) => _.trim(name));
478
+ .filter(({name}) => name?.trim());
480
479
  }
481
480
 
482
481
  /**
@@ -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 _.includes(await this.shell([checkCmd]), passFlag);
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 || _.isNaN(parseInt(match[1], 10))) {
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 (!_.isInteger(this._apiLevel)) {
68
- try {
69
- const strOutput = await this.getDeviceProperty('ro.build.version.sdk');
70
- let apiLevel = parseInt(strOutput.trim(), 10);
67
+ if (Number.isInteger(this._apiLevel)) {
68
+ return this._apiLevel as number;
69
+ }
71
70
 
72
- // Workaround for preview/beta platform API level
73
- const charCodeQ = 'q'.charCodeAt(0);
74
- // 28 is the first API Level, where Android SDK started returning letters in response to getPlatformVersion
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
- this._apiLevel = apiLevel;
85
- log.debug(`Device API level: ${this._apiLevel}`);
86
- if (isNaN(this._apiLevel)) {
87
- throw new Error(`The actual output '${strOutput}' cannot be converted to an integer`);
88
- }
89
- } catch (e) {
90
- throw new Error(`Error getting device API level. Original error: ${(e as Error).message}`);
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
- _.memoize(
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 (_.includes(err.stderr, 'unknown command')) {
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) {