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.
Files changed (130) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/build/lib/commands/app-management.d.ts.map +1 -1
  3. package/build/lib/commands/app-management.js +13 -12
  4. package/build/lib/commands/app-management.js.map +1 -1
  5. package/build/lib/commands/bidi/models.d.ts.map +1 -1
  6. package/build/lib/commands/bidi/models.js +1 -5
  7. package/build/lib/commands/bidi/models.js.map +1 -1
  8. package/build/lib/commands/bluetooth.d.ts.map +1 -1
  9. package/build/lib/commands/bluetooth.js +1 -5
  10. package/build/lib/commands/bluetooth.js.map +1 -1
  11. package/build/lib/commands/context/exports.d.ts.map +1 -1
  12. package/build/lib/commands/context/exports.js +11 -15
  13. package/build/lib/commands/context/exports.js.map +1 -1
  14. package/build/lib/commands/context/helpers.d.ts.map +1 -1
  15. package/build/lib/commands/context/helpers.js +43 -22
  16. package/build/lib/commands/context/helpers.js.map +1 -1
  17. package/build/lib/commands/device/common.d.ts.map +1 -1
  18. package/build/lib/commands/device/common.js +9 -8
  19. package/build/lib/commands/device/common.js.map +1 -1
  20. package/build/lib/commands/device/utils.d.ts.map +1 -1
  21. package/build/lib/commands/device/utils.js +2 -6
  22. package/build/lib/commands/device/utils.js.map +1 -1
  23. package/build/lib/commands/deviceidle.d.ts.map +1 -1
  24. package/build/lib/commands/deviceidle.js +2 -6
  25. package/build/lib/commands/deviceidle.js.map +1 -1
  26. package/build/lib/commands/execute.d.ts.map +1 -1
  27. package/build/lib/commands/execute.js +2 -6
  28. package/build/lib/commands/execute.js.map +1 -1
  29. package/build/lib/commands/file-actions.d.ts.map +1 -1
  30. package/build/lib/commands/file-actions.js +2 -3
  31. package/build/lib/commands/file-actions.js.map +1 -1
  32. package/build/lib/commands/find.d.ts.map +1 -1
  33. package/build/lib/commands/find.js +4 -7
  34. package/build/lib/commands/find.js.map +1 -1
  35. package/build/lib/commands/geolocation.d.ts.map +1 -1
  36. package/build/lib/commands/geolocation.js +3 -4
  37. package/build/lib/commands/geolocation.js.map +1 -1
  38. package/build/lib/commands/image-injection.d.ts.map +1 -1
  39. package/build/lib/commands/image-injection.js +1 -2
  40. package/build/lib/commands/image-injection.js.map +1 -1
  41. package/build/lib/commands/intent.d.ts.map +1 -1
  42. package/build/lib/commands/intent.js +14 -18
  43. package/build/lib/commands/intent.js.map +1 -1
  44. package/build/lib/commands/keyboard.d.ts.map +1 -1
  45. package/build/lib/commands/keyboard.js +6 -6
  46. package/build/lib/commands/keyboard.js.map +1 -1
  47. package/build/lib/commands/lock/exports.d.ts.map +1 -1
  48. package/build/lib/commands/lock/exports.js +1 -5
  49. package/build/lib/commands/lock/exports.js.map +1 -1
  50. package/build/lib/commands/lock/helpers.d.ts.map +1 -1
  51. package/build/lib/commands/lock/helpers.js +5 -9
  52. package/build/lib/commands/lock/helpers.js.map +1 -1
  53. package/build/lib/commands/log.d.ts.map +1 -1
  54. package/build/lib/commands/log.js +9 -9
  55. package/build/lib/commands/log.js.map +1 -1
  56. package/build/lib/commands/media-projection.d.ts.map +1 -1
  57. package/build/lib/commands/media-projection.js +5 -6
  58. package/build/lib/commands/media-projection.js.map +1 -1
  59. package/build/lib/commands/network.d.ts.map +1 -1
  60. package/build/lib/commands/network.js +12 -16
  61. package/build/lib/commands/network.js.map +1 -1
  62. package/build/lib/commands/nfc.d.ts.map +1 -1
  63. package/build/lib/commands/nfc.js +1 -5
  64. package/build/lib/commands/nfc.js.map +1 -1
  65. package/build/lib/commands/performance.d.ts.map +1 -1
  66. package/build/lib/commands/performance.js +7 -12
  67. package/build/lib/commands/performance.js.map +1 -1
  68. package/build/lib/commands/permissions.d.ts.map +1 -1
  69. package/build/lib/commands/permissions.js +21 -23
  70. package/build/lib/commands/permissions.js.map +1 -1
  71. package/build/lib/commands/recordscreen.d.ts.map +1 -1
  72. package/build/lib/commands/recordscreen.js +10 -11
  73. package/build/lib/commands/recordscreen.js.map +1 -1
  74. package/build/lib/commands/resources.d.ts.map +1 -1
  75. package/build/lib/commands/resources.js +6 -10
  76. package/build/lib/commands/resources.js.map +1 -1
  77. package/build/lib/commands/shell.d.ts.map +1 -1
  78. package/build/lib/commands/shell.js +7 -6
  79. package/build/lib/commands/shell.js.map +1 -1
  80. package/build/lib/commands/streamscreen.d.ts.map +1 -1
  81. package/build/lib/commands/streamscreen.js +14 -15
  82. package/build/lib/commands/streamscreen.js.map +1 -1
  83. package/build/lib/commands/system-bars.d.ts.map +1 -1
  84. package/build/lib/commands/system-bars.js +19 -21
  85. package/build/lib/commands/system-bars.js.map +1 -1
  86. package/build/lib/commands/time.d.ts.map +1 -1
  87. package/build/lib/commands/time.js +23 -8
  88. package/build/lib/commands/time.js.map +1 -1
  89. package/build/lib/driver.d.ts.map +1 -1
  90. package/build/lib/driver.js +2 -6
  91. package/build/lib/driver.js.map +1 -1
  92. package/build/lib/logger.d.ts +2 -1
  93. package/build/lib/logger.d.ts.map +1 -1
  94. package/build/lib/logger.js.map +1 -1
  95. package/build/lib/utils.d.ts.map +1 -1
  96. package/build/lib/utils.js +9 -12
  97. package/build/lib/utils.js.map +1 -1
  98. package/lib/commands/app-management.ts +19 -9
  99. package/lib/commands/bidi/models.ts +1 -2
  100. package/lib/commands/bluetooth.ts +1 -2
  101. package/lib/commands/context/exports.ts +12 -14
  102. package/lib/commands/context/helpers.ts +47 -22
  103. package/lib/commands/device/common.ts +10 -8
  104. package/lib/commands/device/utils.ts +2 -3
  105. package/lib/commands/deviceidle.ts +2 -3
  106. package/lib/commands/execute.ts +2 -3
  107. package/lib/commands/file-actions.ts +2 -3
  108. package/lib/commands/find.ts +4 -5
  109. package/lib/commands/geolocation.ts +3 -4
  110. package/lib/commands/image-injection.ts +1 -2
  111. package/lib/commands/intent.ts +14 -15
  112. package/lib/commands/keyboard.ts +6 -3
  113. package/lib/commands/lock/exports.ts +1 -2
  114. package/lib/commands/lock/helpers.ts +5 -6
  115. package/lib/commands/log.ts +9 -9
  116. package/lib/commands/media-projection.ts +5 -6
  117. package/lib/commands/network.ts +12 -13
  118. package/lib/commands/nfc.ts +1 -2
  119. package/lib/commands/performance.ts +7 -11
  120. package/lib/commands/permissions.ts +23 -20
  121. package/lib/commands/recordscreen.ts +10 -11
  122. package/lib/commands/resources.ts +7 -8
  123. package/lib/commands/shell.ts +7 -3
  124. package/lib/commands/streamscreen.ts +14 -16
  125. package/lib/commands/system-bars.ts +20 -20
  126. package/lib/commands/time.ts +26 -8
  127. package/lib/driver.ts +2 -4
  128. package/lib/logger.ts +2 -1
  129. package/lib/utils.ts +9 -9
  130. 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 (!_.includes(contexts, newContext)) {
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 = _.cloneDeep(this.opts) as any;
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
- _.has(this.opts, 'enableWebviewDetailsCollection') &&
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 (_.includes(contexts, `${WEBVIEW_BASE}${knownPackage}`)) {
268
+ if (contexts?.includes(`${WEBVIEW_BASE}${knownPackage}`)) {
270
269
  continue;
271
270
  }
272
271
  const appState = await this.queryAppState(knownPackage);
273
272
  if (
274
- _.includes(
275
- [APP_STATE.RUNNING_IN_BACKGROUND, APP_STATE.RUNNING_IN_FOREGROUND],
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 && _.has(wm?.info, 'Android-Package')) {
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 _.keys(this.sessionChromedrivers)) {
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 _.includes(viewName, WEBVIEW_WIN) || viewName === CHROMIUM_WIN;
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(_.toLower(String(this.opts.automationName)), name),
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 = _.cloneDeep(this.opts) as any;
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 (_.includes(knownPackages, this.opts.appPackage)) {
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 (!_.isNumber(waitMs)) {
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 (!_.isEmpty(details)) {
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: _.isNil(opts.chromedriverPort) ? undefined : String(opts.chromedriverPort),
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 _.keys(opts)) {
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
- _.merge(opts.chromeOptions, (opts as any)[opt]);
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 _.keys(opts)) {
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
- _.merge(opts.chromeLoggingPrefs, (opts as any)[opt]);
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
- _.isArray(this.opts.chromeOptions.args) &&
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 (_.isBoolean(opts.chromeUseRunningApp)) {
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 (_.toLower(opts.browserName) === 'chromium-webview') {
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 = _.toLower(caps.chromeOptions.androidPackage) === 'chrome';
486
- if (_.includes(KNOWN_CHROME_PACKAGE_NAMES, caps.chromeOptions.androidPackage) || isChrome) {
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 (_.isPlainObject(opts.loggingPrefs) || _.isPlainObject(opts.chromeLoggingPrefs)) {
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 && _.isNil(caps.webSocketUrl) && this.opts.chromedriverForwardBiDi) {
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 _.toPairs(opts.chromeOptions)) {
533
- if (_.isUndefined(caps.chromeOptions[opt])) {
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 (!_.isEmpty(protectedCapNames)) {
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 (_.isEmpty(webviewsMapping)) {
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 (_.isEmpty(names)) {
731
+ if (util.isEmpty(names)) {
733
732
  this.log.debug('Found no active devtools sockets');
734
- if (!_.isEmpty(allMatches)) {
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 _.uniq(names);
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 (!_.includes(_.map(devices, 'udid'), udid)) {
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 = _.isString(deviceOS) && _.isString(platformVersion);
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 && _.toLower(deviceOS) === _.toLower(platformVersion))
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
- ((!_.includes(this.opts.platformVersion, '.') && pvMajor === dvMajor) ||
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 && semver.gt(deviceOS, _.values(partialMatchCandidate)[0])) ||
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 = _.keys(partialMatchCandidate)[0];
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 || _.isUndefined(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: ${_.values(this.adb.NETWORK_SPEED)}`,
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 (_.isArray(avdArgs)) {
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 (!(_.isString(packages) || _.isArray(packages))) {
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 = _.isArray(packages) ? packages : [packages];
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) {
@@ -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 (_.startsWith(script, EXECUTE_SCRIPT_PREFIX)) {
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 = (_.isArray(args) ? _.first(args) : args) ?? ({} as StringRecord);
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 (_.isArray(base64Data)) {
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 _.includes(await adb.shell([fullCmd]), passFlag);
329
+ return (await adb.shell([fullCmd])).includes(passFlag);
331
330
  } catch {
332
331
  return false;
333
332
  }
@@ -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 !_.isEmpty(element);
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 (_.isEmpty(element)) {
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 = _.intersection(pkgIds, thirdPartyPkgIds);
214
- if (_.size(resultPkgs) <= 1) {
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 (!_.isString(payload) || _.size(payload) <= PNG_MAGIC_LENGTH) {
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
  );
@@ -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 (!_.isNil(user)) {
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 (!_.isNil(windowingMode)) {
137
+ if (util.hasValue(windowingMode)) {
139
138
  cmd.push('--windowingMode', String(windowingMode));
140
139
  }
141
- if (!_.isNil(activityType)) {
140
+ if (util.hasValue(activityType)) {
142
141
  cmd.push('--activityType', String(activityType));
143
142
  }
144
- if (!_.isNil(display)) {
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 (!_.isNil(user)) {
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 (!_.isNil(user)) {
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 (!_.isNil(user)) {
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 (!_.isNil(identifier)) {
368
+ if (util.hasValue(identifier)) {
370
369
  resultArgs.push('-i', identifier);
371
370
  }
372
371
  if (categories) {
373
- if (_.isArray(categories)) {
374
- resultArgs.push(..._.flatMap(categories.map((cName) => ['-c', cName])));
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 (!_.isArray(extras)) {
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 (!_.isArray(item)) {
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 (_.isEmpty(key) || (_.isString(key) && _.trim(key) === '')) {
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 (_.isUndefined(value)) {
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`,
@@ -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 = _.isArray(keys) ? keys.join('') : keys;
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