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
@@ -15,7 +15,6 @@ import {
15
15
  toCredentialType,
16
16
  verifyUnlock,
17
17
  } from './helpers';
18
- import _ from 'lodash';
19
18
  import type {AndroidDriver, AndroidDriverCaps} from '../../driver';
20
19
  import type {UnlockType} from '../types';
21
20
 
@@ -119,7 +118,7 @@ export async function unlockWithOptions(
119
118
  if (
120
119
  unlockKey &&
121
120
  unlockType !== FINGERPRINT_UNLOCK &&
122
- (_.isNil(unlockStrategy) || _.toLower(unlockStrategy) === 'locksettings') &&
121
+ (unlockStrategy == null || String(unlockStrategy)?.toLowerCase() === 'locksettings') &&
123
122
  (await this.adb.isLockManagementSupported())
124
123
  ) {
125
124
  await fastUnlock.bind(this)({
@@ -1,6 +1,5 @@
1
1
  import {util} from '@appium/support';
2
2
  import {sleep, waitForCondition} from 'asyncbox';
3
- import _ from 'lodash';
4
3
  import type {ADB} from 'appium-adb';
5
4
  import type {Position, StringRecord} from '@appium/types';
6
5
  import type {AndroidDriver, AndroidDriverCaps} from '../../driver';
@@ -58,16 +57,16 @@ export function validateUnlockCapabilities<T extends AndroidDriverCaps>(caps: T)
58
57
  }
59
58
 
60
59
  if ([PIN_UNLOCK, PIN_UNLOCK_KEY_EVENT, FINGERPRINT_UNLOCK].includes(unlockType)) {
61
- if (!/^[0-9]+$/.test(_.trim(unlockKey))) {
60
+ if (!/^[0-9]+$/.test(String(unlockKey)?.trim())) {
62
61
  throw new Error(`Unlock key value '${unlockKey}' must only consist of digits`);
63
62
  }
64
63
  } else if (unlockType === PATTERN_UNLOCK) {
65
- if (!/^[1-9]{2,9}$/.test(_.trim(unlockKey))) {
64
+ if (!/^[1-9]{2,9}$/.test(String(unlockKey)?.trim())) {
66
65
  throw new Error(
67
66
  `Unlock key value '${unlockKey}' must only include from two to nine digits in range 1..9`,
68
67
  );
69
68
  }
70
- if (/([1-9]).*?\1/.test(_.trim(unlockKey))) {
69
+ if (/([1-9]).*?\1/.test(String(unlockKey)?.trim())) {
71
70
  throw new Error(
72
71
  `Unlock key value '${unlockKey}' must define a valid pattern where repeats are not allowed`,
73
72
  );
@@ -161,7 +160,7 @@ export async function pinUnlock(
161
160
  await this.adb.dismissKeyguard();
162
161
  const keys = stringKeyToArr(String(capabilities.unlockKey));
163
162
  const els = await this.findElOrEls('id', 'com.android.systemui:id/digit_text', true);
164
- if (_.isEmpty(els)) {
163
+ if (util.isEmpty(els)) {
165
164
  // fallback to pin with key event
166
165
  return await pinUnlockWithKeyEvent.bind(this)(capabilities);
167
166
  }
@@ -267,7 +266,7 @@ export function getPatternActions(
267
266
  ): StringRecord[] {
268
267
  // https://www.w3.org/TR/webdriver2/#actions
269
268
  const pointerActions: StringRecord[] = [];
270
- const intKeys = keys.map((key) => (_.isString(key) ? _.parseInt(key) : key));
269
+ const intKeys = keys.map((key) => (typeof key === 'string' ? parseInt(key, 10) : key));
271
270
  let lastPos: Position | undefined;
272
271
  for (const key of intKeys) {
273
272
  const keyPos = getPatternKeyPosition(key, initPos, piece);
@@ -1,5 +1,4 @@
1
1
  import {DEFAULT_WS_PATHNAME_PREFIX, BaseDriver} from 'appium/driver';
2
- import _ from 'lodash';
3
2
  import os from 'node:os';
4
3
  import WebSocket from 'ws';
5
4
  import type {AppiumServer, WSServer} from '@appium/types';
@@ -16,6 +15,7 @@ import {NATIVE_WIN} from './context/helpers';
16
15
  import {BIDI_EVENT_NAME} from './bidi/constants';
17
16
  import {makeLogEntryAddedEvent} from './bidi/models';
18
17
  import type {AndroidDriver} from '../driver';
18
+ import {util} from '@appium/support';
19
19
 
20
20
  export const supportedLogTypes = {
21
21
  logcat: {
@@ -52,7 +52,7 @@ export const supportedLogTypes = {
52
52
  export async function mobileStartLogsBroadcast(this: AndroidDriver): Promise<void> {
53
53
  const server = this.server as AppiumServer;
54
54
  const pathname = WEBSOCKET_ENDPOINT(this.sessionId as string);
55
- if (!_.isEmpty(await server.getWebSocketHandlers(pathname))) {
55
+ if (!util.isEmpty(await server.getWebSocketHandlers(pathname))) {
56
56
  this.log.debug(`The logcat broadcasting web socket server is already listening at ${pathname}`);
57
57
  return;
58
58
  }
@@ -67,7 +67,7 @@ export async function mobileStartLogsBroadcast(this: AndroidDriver): Promise<voi
67
67
  });
68
68
  wss.on('connection', (ws, req) => {
69
69
  if (req) {
70
- const remoteIp = _.isEmpty(req.headers['x-forwarded-for'])
70
+ const remoteIp = util.isEmpty(req.headers['x-forwarded-for'])
71
71
  ? req.socket.remoteAddress
72
72
  : req.headers['x-forwarded-for'];
73
73
  this.log.debug(`Established a new logcat listener web socket connection from ${remoteIp}`);
@@ -75,7 +75,7 @@ export async function mobileStartLogsBroadcast(this: AndroidDriver): Promise<voi
75
75
  this.log.debug('Established a new logcat listener web socket connection');
76
76
  }
77
77
 
78
- if (_.isEmpty(this._logcatWebsocketListener)) {
78
+ if (!this._logcatWebsocketListener) {
79
79
  this._logcatWebsocketListener = (logRecord: LogEntry) => {
80
80
  if (ws?.readyState === WebSocket.OPEN) {
81
81
  ws.send(logRecord.message);
@@ -85,7 +85,7 @@ export async function mobileStartLogsBroadcast(this: AndroidDriver): Promise<voi
85
85
  this.adb.setLogcatListener(this._logcatWebsocketListener);
86
86
 
87
87
  ws.on('close', (code, reason) => {
88
- if (!_.isEmpty(this._logcatWebsocketListener)) {
88
+ if (this._logcatWebsocketListener) {
89
89
  try {
90
90
  this.adb.removeLogcatListener(this._logcatWebsocketListener);
91
91
  } catch {}
@@ -93,10 +93,10 @@ export async function mobileStartLogsBroadcast(this: AndroidDriver): Promise<voi
93
93
  }
94
94
 
95
95
  let closeMsg = 'Logcat listener web socket is closed.';
96
- if (!_.isEmpty(code)) {
96
+ if (!util.isEmpty(code)) {
97
97
  closeMsg += ` Code: ${code}.`;
98
98
  }
99
- if (!_.isEmpty(reason)) {
99
+ if (!util.isEmpty(reason)) {
100
100
  closeMsg += ` Reason: ${reason.toString()}.`;
101
101
  }
102
102
  this.log.debug(closeMsg);
@@ -114,7 +114,7 @@ export async function mobileStartLogsBroadcast(this: AndroidDriver): Promise<voi
114
114
  export async function mobileStopLogsBroadcast(this: AndroidDriver): Promise<void> {
115
115
  const pathname = WEBSOCKET_ENDPOINT(this.sessionId as string);
116
116
  const server = this.server as AppiumServer;
117
- if (_.isEmpty(await server.getWebSocketHandlers(pathname))) {
117
+ if (util.isEmpty(await server.getWebSocketHandlers(pathname))) {
118
118
  return;
119
119
  }
120
120
 
@@ -174,7 +174,7 @@ export function assignBiDiLogListener<EE extends EventEmitter>(
174
174
  * @returns Promise that resolves to the logs for the specified type.
175
175
  */
176
176
  export async function getLog(this: AndroidDriver, logType: string): Promise<any> {
177
- if (this.isWebContext() && !_.keys(this.supportedLogTypes).includes(logType)) {
177
+ if (this.isWebContext() && !Object.keys(this.supportedLogTypes).includes(logType)) {
178
178
  return await (this.chromedriver as Chromedriver).jwproxy.command('/log', 'POST', {
179
179
  type: logType,
180
180
  });
@@ -1,7 +1,6 @@
1
1
  import {fs, net, util} from '@appium/support';
2
2
  import type {NetOptions, HttpUploadOptions} from '@appium/support';
3
- import _ from 'lodash';
4
- import moment from 'moment';
3
+ import dayjs from 'dayjs';
5
4
  import path from 'node:path';
6
5
  import type {HTTPMethod, StringRecord} from '@appium/types';
7
6
  import type {AndroidDriver} from '../driver';
@@ -51,7 +50,7 @@ export async function mobileStartMediaProjectionRecording(
51
50
  await verifyMediaProjectionRecordingIsSupported(this.adb);
52
51
 
53
52
  const recorder = this.settingsApp.makeMediaProjectionRecorder();
54
- const fname = adjustMediaExtension(filename || moment().format(DEFAULT_FILENAME_FORMAT));
53
+ const fname = adjustMediaExtension(filename || dayjs().format(DEFAULT_FILENAME_FORMAT));
55
54
  const didStart = await recorder.start({
56
55
  resolution,
57
56
  priority,
@@ -128,7 +127,7 @@ export async function mobileStopMediaProjectionRecording(
128
127
  throw new Error(`No recent media projection recording have been found. Did you start any?`);
129
128
  }
130
129
 
131
- if (_.isEmpty(remotePath)) {
130
+ if (util.isEmpty(remotePath)) {
132
131
  const {size} = await fs.stat(recentRecordingPath);
133
132
  this.log.debug(
134
133
  `The size of the resulting media projection recording is ${util.toReadableSizeString(size)}`,
@@ -154,7 +153,7 @@ async function uploadRecordedMedia(
154
153
  remotePath?: string,
155
154
  uploadOptions: UploadOptions = {},
156
155
  ): Promise<string> {
157
- if (_.isEmpty(remotePath)) {
156
+ if (util.isEmpty(remotePath)) {
158
157
  return (await util.toInMemoryBase64(localFile)).toString();
159
158
  }
160
159
 
@@ -182,7 +181,7 @@ async function uploadRecordedMedia(
182
181
  }
183
182
 
184
183
  function adjustMediaExtension(name: string): string {
185
- return _.toLower(name).endsWith(DEFAULT_EXT) ? name : `${name}${DEFAULT_EXT}`;
184
+ return name?.toLowerCase()?.endsWith(DEFAULT_EXT) ? name : `${name}${DEFAULT_EXT}`;
186
185
  }
187
186
 
188
187
  async function verifyMediaProjectionRecordingIsSupported(adb: ADB): Promise<void> {
@@ -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';
@@ -69,7 +68,7 @@ export async function mobileSetConnectivity(
69
68
  data?: boolean,
70
69
  airplaneMode?: boolean,
71
70
  ): Promise<void> {
72
- if (_.every([wifi, data, airplaneMode], _.isUndefined)) {
71
+ if (wifi === undefined && data === undefined && airplaneMode === undefined) {
73
72
  throw new errors.InvalidArgumentError(
74
73
  `Either one of ${JSON.stringify(SUPPORTED_SERVICE_NAMES)} options must be provided`,
75
74
  );
@@ -81,20 +80,20 @@ export async function mobileSetConnectivity(
81
80
  [airplaneMode, AIRPLANE_MODE_KEY_NAME],
82
81
  ];
83
82
  const services = connectivityPairs.reduce<ServiceType[]>((acc, [value, key]) => {
84
- if (!_.isUndefined(value)) {
83
+ if (value !== undefined) {
85
84
  acc.push(key);
86
85
  }
87
86
  return acc;
88
87
  }, []);
89
88
  const currentState = await this.mobileGetConnectivity(services);
90
89
  const setters: Promise<any>[] = [];
91
- if (!_.isUndefined(wifi) && currentState.wifi !== Boolean(wifi)) {
90
+ if (wifi !== undefined && currentState.wifi !== Boolean(wifi)) {
92
91
  setters.push(this.setWifiState(wifi));
93
92
  }
94
- if (!_.isUndefined(data) && currentState.data !== Boolean(data)) {
93
+ if (data !== undefined && currentState.data !== Boolean(data)) {
95
94
  setters.push(this.setDataState(data));
96
95
  }
97
- if (!_.isUndefined(airplaneMode) && currentState.airplaneMode !== Boolean(airplaneMode)) {
96
+ if (airplaneMode !== undefined && currentState.airplaneMode !== Boolean(airplaneMode)) {
98
97
  setters.push(
99
98
  (async () => {
100
99
  await this.adb.setAirplaneMode(airplaneMode);
@@ -104,7 +103,7 @@ export async function mobileSetConnectivity(
104
103
  })(),
105
104
  );
106
105
  }
107
- if (!_.isEmpty(setters)) {
106
+ if (setters.length > 0) {
108
107
  await Promise.all(setters);
109
108
  }
110
109
  }
@@ -120,9 +119,9 @@ export async function mobileGetConnectivity(
120
119
  this: AndroidDriver,
121
120
  services: ServiceType[] | ServiceType = SUPPORTED_SERVICE_NAMES,
122
121
  ): Promise<GetConnectivityResult> {
123
- const svcs = _.castArray(services);
124
- const unsupportedServices = _.difference(svcs, SUPPORTED_SERVICE_NAMES);
125
- if (!_.isEmpty(unsupportedServices)) {
122
+ const svcs = Array.isArray(services) ? services : [services];
123
+ const unsupportedServices = svcs.filter((svc) => !SUPPORTED_SERVICE_NAMES.includes(svc));
124
+ if (unsupportedServices.length > 0) {
126
125
  throw new errors.InvalidArgumentError(
127
126
  `${util.pluralize(
128
127
  'Service name',
@@ -144,13 +143,13 @@ export async function mobileGetConnectivity(
144
143
  : Promise.resolve(undefined),
145
144
  ]);
146
145
  const result: GetConnectivityResult = {};
147
- if (!_.isUndefined(wifi)) {
146
+ if (wifi !== undefined) {
148
147
  result.wifi = Boolean(wifi);
149
148
  }
150
- if (!_.isUndefined(data)) {
149
+ if (data !== undefined) {
151
150
  result.data = Boolean(data);
152
151
  }
153
- if (!_.isUndefined(airplaneMode)) {
152
+ if (airplaneMode !== undefined) {
154
153
  result.airplaneMode = Boolean(airplaneMode);
155
154
  }
156
155
  return result;
@@ -1,5 +1,4 @@
1
1
  import {errors} from 'appium/driver';
2
- import _ from 'lodash';
3
2
  import type {AndroidDriver} from '../driver';
4
3
  import type {NfcAction} from './types';
5
4
 
@@ -27,7 +26,7 @@ export async function mobileNfc(this: AndroidDriver, action: NfcAction): Promise
27
26
  break;
28
27
  default:
29
28
  throw new errors.InvalidArgumentError(
30
- `You must provide a valid 'action' argument. Supported actions are: ${_.values(SUPPORTED_ACTIONS)}`,
29
+ `You must provide a valid 'action' argument. Supported actions are: ${Object.values(SUPPORTED_ACTIONS)}`,
31
30
  );
32
31
  }
33
32
  }
@@ -1,8 +1,8 @@
1
1
  import {retryInterval} from 'asyncbox';
2
- import _ from 'lodash';
3
2
  import type {ExecError} from 'teen_process';
4
3
  import type {AndroidDriver} from '../driver';
5
4
  import type {PerformanceDataType} from './types';
5
+ import {util} from '@appium/support';
6
6
 
7
7
  export const NETWORK_KEYS = [
8
8
  [
@@ -68,7 +68,7 @@ const RETRY_PAUSE_MS = 1000;
68
68
  * The possible values are: 'cpuinfo', 'memoryinfo', 'batteryinfo', 'networkinfo'.
69
69
  */
70
70
  export async function getPerformanceDataTypes(this: AndroidDriver): Promise<PerformanceDataType[]> {
71
- return _.keys(SUPPORTED_PERFORMANCE_DATA_TYPES) as PerformanceDataType[];
71
+ return Object.keys(SUPPORTED_PERFORMANCE_DATA_TYPES) as PerformanceDataType[];
72
72
  }
73
73
 
74
74
  /**
@@ -89,7 +89,7 @@ export async function getPerformanceData(
89
89
  dataType: string,
90
90
  retries: number = 2,
91
91
  ): Promise<any[][]> {
92
- switch (_.toLower(dataType)) {
92
+ switch (dataType?.toLowerCase()) {
93
93
  case 'batteryinfo':
94
94
  return await getBatteryInfo.call(this, retries);
95
95
  case 'cpuinfo':
@@ -187,7 +187,7 @@ export async function getMemoryInfo(
187
187
  }
188
188
  }
189
189
  if (valDict.totalPrivateDirty && valDict.totalPrivateDirty !== 'nodex') {
190
- const headers = _.clone(MEMORY_KEYS);
190
+ const headers = structuredClone(MEMORY_KEYS);
191
191
  const values = headers.map((header) => valDict[header]);
192
192
  return [headers, values];
193
193
  }
@@ -371,11 +371,7 @@ export async function getNetworkTrafficInfo(
371
371
  }
372
372
  }
373
373
 
374
- if (
375
- !_.isEqual(pendingBytes, '') &&
376
- !_.isUndefined(pendingBytes) &&
377
- !_.isEqual(pendingBytes, 'nodex')
378
- ) {
374
+ if (pendingBytes !== '' && pendingBytes !== undefined && pendingBytes !== 'nodex') {
379
375
  return returnValue;
380
376
  } else {
381
377
  throw new Error(`Unable to parse network traffic data: '${data}'`);
@@ -427,7 +423,7 @@ export async function getCPUInfo(
427
423
  // `output` will be something like
428
424
  // +0% 2209/io.appium.android.apis: 0.1% user + 0.2% kernel / faults: 70 minor
429
425
  const usagesPattern = new RegExp(
430
- `^.+\\/${_.escapeRegExp(packageName)}:\\D+([\\d.]+)%\\s+user\\s+\\+\\s+([\\d.]+)%\\s+kernel`,
426
+ `^.+\\/${util.escapeRegExp(packageName)}:\\D+([\\d.]+)%\\s+user\\s+\\+\\s+([\\d.]+)%\\s+kernel`,
431
427
  'm',
432
428
  );
433
429
  const match = usagesPattern.exec(output);
@@ -462,7 +458,7 @@ export async function getBatteryInfo(this: AndroidDriver, retries: number = 2):
462
458
  const power = parseInt((data.split(':')[1] || '').trim(), 10);
463
459
 
464
460
  if (!Number.isNaN(power)) {
465
- return [_.clone(BATTERY_KEYS), [power.toString()]];
461
+ return [structuredClone(BATTERY_KEYS), [power.toString()]];
466
462
  } else {
467
463
  throw new Error(`Unable to parse battery data: '${data}'`);
468
464
  }
@@ -1,7 +1,7 @@
1
1
  import {errors} from 'appium/driver';
2
- import _ from 'lodash';
3
2
  import {ADB_SHELL_FEATURE} from '../utils';
4
3
  import type {AndroidDriver} from '../driver';
4
+ import {util} from '@appium/support';
5
5
 
6
6
  const ALL_PERMISSIONS_MAGIC = 'all';
7
7
  const PM_ACTION = Object.freeze({
@@ -67,31 +67,32 @@ export async function mobileChangePermissions(
67
67
  if (!appPackage) {
68
68
  throw new errors.InvalidArgumentError(`A valid appPackage must be provided`);
69
69
  }
70
- action ??= _.toLower(target) === PERMISSION_TARGET.APPOPS ? APPOPS_ACTION.ALLOW : PM_ACTION.GRANT;
71
- if (_.isNil(permissions)) {
70
+ action ??=
71
+ target?.toLowerCase() === PERMISSION_TARGET.APPOPS ? APPOPS_ACTION.ALLOW : PM_ACTION.GRANT;
72
+ if (permissions == null) {
72
73
  throw new errors.InvalidArgumentError(`'permissions' argument is required`);
73
74
  }
74
- if (_.isEmpty(permissions)) {
75
+ if (util.isEmpty(permissions)) {
75
76
  throw new errors.InvalidArgumentError(`'permissions' argument must not be empty`);
76
77
  }
77
78
 
78
- switch (_.toLower(target)) {
79
+ switch (target?.toLowerCase()) {
79
80
  case PERMISSION_TARGET.PM:
80
81
  return await changePermissionsViaPm.bind(this)(
81
82
  permissions,
82
83
  appPackage,
83
- _.toLower(action) as PMAction,
84
+ action?.toLowerCase() as PMAction,
84
85
  );
85
86
  case PERMISSION_TARGET.APPOPS:
86
87
  this.assertFeatureEnabled(ADB_SHELL_FEATURE);
87
88
  return await changePermissionsViaAppops.bind(this)(
88
89
  permissions,
89
90
  appPackage,
90
- _.toLower(action) as AppOpsAction,
91
+ action?.toLowerCase() as AppOpsAction,
91
92
  );
92
93
  default:
93
94
  throw new errors.InvalidArgumentError(
94
- `'target' argument must be one of: ${_.values(PERMISSION_TARGET)}`,
95
+ `'target' argument must be one of: ${Object.values(PERMISSION_TARGET)}`,
95
96
  );
96
97
  }
97
98
  }
@@ -111,7 +112,7 @@ export async function mobileGetPermissions(
111
112
  ): Promise<string[]> {
112
113
  appPackage ??= this.opts.appPackage;
113
114
  let actionFunc: (pkg: string) => Promise<string[]>;
114
- switch (_.toLower(type)) {
115
+ switch (type?.toLowerCase()) {
115
116
  case PERMISSIONS_TYPE.REQUESTED:
116
117
  actionFunc = (pkg) => this.adb.getReqPermissions(pkg);
117
118
  break;
@@ -124,7 +125,7 @@ export async function mobileGetPermissions(
124
125
  default:
125
126
  throw new errors.InvalidArgumentError(
126
127
  `Unknown permissions type '${type}'. ` +
127
- `Only ${JSON.stringify(_.values(PERMISSIONS_TYPE))} types are supported`,
128
+ `Only ${JSON.stringify(Object.values(PERMISSIONS_TYPE))} types are supported`,
128
129
  );
129
130
  }
130
131
  return await actionFunc(appPackage as string);
@@ -135,24 +136,26 @@ async function changePermissionsViaPm(
135
136
  appPackage: string,
136
137
  action: PMAction,
137
138
  ): Promise<void> {
138
- if (!_.values(PM_ACTION).includes(action)) {
139
+ if (!Object.values(PM_ACTION).includes(action)) {
139
140
  throw new errors.InvalidArgumentError(
140
141
  `Unknown action '${action}'. ` +
141
- `Only ${JSON.stringify(_.values(PM_ACTION))} actions are supported`,
142
+ `Only ${JSON.stringify(Object.values(PM_ACTION))} actions are supported`,
142
143
  );
143
144
  }
144
145
 
145
- let affectedPermissions = _.isArray(permissions) ? permissions : [permissions];
146
- if (_.isString(permissions) && _.toLower(permissions) === ALL_PERMISSIONS_MAGIC) {
146
+ let affectedPermissions = Array.isArray(permissions) ? permissions : [permissions];
147
+ if (typeof permissions === 'string' && permissions.toLowerCase() === ALL_PERMISSIONS_MAGIC) {
147
148
  const dumpsys = await this.adb.shell(['dumpsys', 'package', appPackage]);
148
149
  const grantedPermissions = await this.adb.getGrantedPermissions(appPackage, dumpsys);
149
150
  if (action === PM_ACTION.GRANT) {
150
151
  const reqPermissons = await this.adb.getReqPermissions(appPackage, dumpsys);
151
- affectedPermissions = _.difference(reqPermissons, grantedPermissions);
152
+ affectedPermissions = reqPermissons.filter(
153
+ (permission) => !grantedPermissions.includes(permission),
154
+ );
152
155
  } else {
153
156
  affectedPermissions = grantedPermissions;
154
157
  }
155
- if (_.isEmpty(affectedPermissions)) {
158
+ if (util.isEmpty(affectedPermissions)) {
156
159
  this.log.info(`'${appPackage}' contains no permissions to ${action}`);
157
160
  return;
158
161
  }
@@ -172,13 +175,13 @@ async function changePermissionsViaAppops(
172
175
  appPackage: string,
173
176
  action: AppOpsAction,
174
177
  ): Promise<void> {
175
- if (!_.values(APPOPS_ACTION).includes(action)) {
178
+ if (!Object.values(APPOPS_ACTION).includes(action)) {
176
179
  throw new errors.InvalidArgumentError(
177
180
  `Unknown action '${action}'. ` +
178
- `Only ${JSON.stringify(_.values(APPOPS_ACTION))} actions are supported`,
181
+ `Only ${JSON.stringify(Object.values(APPOPS_ACTION))} actions are supported`,
179
182
  );
180
183
  }
181
- if (_.isString(permissions) && _.toLower(permissions) === ALL_PERMISSIONS_MAGIC) {
184
+ if (typeof permissions === 'string' && permissions.toLowerCase() === ALL_PERMISSIONS_MAGIC) {
182
185
  throw new errors.InvalidArgumentError(
183
186
  `'${ALL_PERMISSIONS_MAGIC}' permission is only supported for ` +
184
187
  `'${PERMISSION_TARGET.PM}' target. ` +
@@ -186,7 +189,7 @@ async function changePermissionsViaAppops(
186
189
  );
187
190
  }
188
191
 
189
- const promises = (_.isArray(permissions) ? permissions : [permissions]).map((permission) =>
192
+ const promises = (Array.isArray(permissions) ? permissions : [permissions]).map((permission) =>
190
193
  this.adb.shell(['appops', 'set', appPackage, permission, action]),
191
194
  );
192
195
  await Promise.all(promises);
@@ -1,7 +1,6 @@
1
1
  import {fs, net, system, tempDir, timing, util} from '@appium/support';
2
2
  import type {NetOptions, HttpUploadOptions} from '@appium/support';
3
3
  import {waitForCondition} from 'asyncbox';
4
- import _ from 'lodash';
5
4
  import path from 'node:path';
6
5
  import {exec} from 'teen_process';
7
6
  import type {AndroidDriver} from '../driver';
@@ -63,7 +62,7 @@ export async function startRecordingScreen(
63
62
  );
64
63
  }
65
64
 
66
- if (!_.isEmpty(this._screenRecordingProperties)) {
65
+ if (this._screenRecordingProperties) {
67
66
  // XXX: this doesn't need to be done in serial, does it?
68
67
  const props = this._screenRecordingProperties;
69
68
  for (const record of props.records || []) {
@@ -117,7 +116,7 @@ export async function stopRecordingScreen(
117
116
  await verifyScreenRecordIsSupported(this.adb, this.isEmulator());
118
117
 
119
118
  const props = this._screenRecordingProperties;
120
- if (!_.isEmpty(props)) {
119
+ if (props) {
121
120
  props.stopped = true;
122
121
  }
123
122
 
@@ -125,12 +124,12 @@ export async function stopRecordingScreen(
125
124
  await terminateBackgroundScreenRecording(this.adb, false);
126
125
  } catch (err) {
127
126
  this.log.warn((err as Error).message);
128
- if (!_.isEmpty(props)) {
127
+ if (props) {
129
128
  this.log.warn('The resulting video might be corrupted');
130
129
  }
131
130
  }
132
131
 
133
- if (_.isEmpty(props)) {
132
+ if (!props) {
134
133
  this.log.info(
135
134
  `Screen recording has not been previously started by Appium. There is nothing to stop`,
136
135
  );
@@ -148,7 +147,7 @@ export async function stopRecordingScreen(
148
147
  props.recordingProcess = null;
149
148
  }
150
149
 
151
- if (_.isEmpty(props.records)) {
150
+ if (util.isEmpty(props.records)) {
152
151
  throw this.log.errorWithException(
153
152
  `No screen recordings have been stored on the device so far. ` +
154
153
  `Are you sure the ${SCREENRECORD_BINARY} utility works as expected?`,
@@ -164,7 +163,7 @@ export async function stopRecordingScreen(
164
163
  await this.adb.pull(pathOnDevice, relativePath, {timeout: ADB_PULL_TIMEOUT});
165
164
  await this.adb.rimraf(pathOnDevice);
166
165
  }
167
- let resultFilePath = _.last(localRecords) as string;
166
+ let resultFilePath = localRecords.at(-1) as string;
168
167
  if (localRecords.length > 1) {
169
168
  this.log.info(`Got ${localRecords.length} screen recordings. Trying to merge them`);
170
169
  try {
@@ -176,7 +175,7 @@ export async function stopRecordingScreen(
176
175
  );
177
176
  }
178
177
  }
179
- if (_.isEmpty(options.remotePath)) {
178
+ if (util.isEmpty(options.remotePath)) {
180
179
  const {size} = await fs.stat(resultFilePath);
181
180
  this.log.debug(
182
181
  `The size of the resulting screen recording is ${util.toReadableSizeString(size)}`,
@@ -196,7 +195,7 @@ async function uploadRecordedMedia(
196
195
  remotePath?: string,
197
196
  uploadOptions: StopScreenRecordingOpts = {},
198
197
  ): Promise<string> {
199
- if (_.isEmpty(remotePath)) {
198
+ if (util.isEmpty(remotePath)) {
200
199
  return (await util.toInMemoryBase64(localFile)).toString();
201
200
  }
202
201
 
@@ -322,14 +321,14 @@ async function mergeScreenRecords(this: AndroidDriver, mediaFiles: string[]): Pr
322
321
 
323
322
  async function terminateBackgroundScreenRecording(adb: ADB, force = true): Promise<boolean> {
324
323
  const screenrecordPids = await adb.getProcessIdsByName(SCREENRECORD_BINARY);
325
- if (_.isEmpty(screenrecordPids)) {
324
+ if (util.isEmpty(screenrecordPids)) {
326
325
  return false;
327
326
  }
328
327
 
329
328
  try {
330
329
  await adb.shell(['kill', force ? '-15' : '-2', ...screenrecordPids.map(String)]);
331
330
  await waitForCondition(
332
- async () => _.isEmpty(await adb.getProcessIdsByName(SCREENRECORD_BINARY)),
331
+ async () => util.isEmpty(await adb.getProcessIdsByName(SCREENRECORD_BINARY)),
333
332
  {
334
333
  waitMs: PROCESS_SHUTDOWN_TIMEOUT,
335
334
  intervalMs: 500,
@@ -1,5 +1,4 @@
1
- import _ from 'lodash';
2
- import {fs, tempDir} from '@appium/support';
1
+ import {fs, tempDir, util} from '@appium/support';
3
2
  import type {StringRecord} from '@appium/types';
4
3
  import type {AndroidDriver, AndroidDriverOpts} from '../driver';
5
4
  import type {Locale} from './types';
@@ -24,8 +23,8 @@ export async function getStrings(
24
23
  // and values of type string
25
24
  const preprocessStringsMap = (mapping: StringRecord): StringRecord => {
26
25
  const result: StringRecord = {};
27
- for (const [key, value] of _.toPairs(mapping)) {
28
- result[key] = _.isString(value) ? value : JSON.stringify(value);
26
+ for (const [key, value] of Object.entries(mapping)) {
27
+ result[key] = typeof value === 'string' ? value : JSON.stringify(value);
29
28
  }
30
29
  return result;
31
30
  };
@@ -63,7 +62,7 @@ export async function ensureDeviceLocale(
63
62
  } catch (e1) {
64
63
  this.log.debug((e1 as Error).stack);
65
64
  }
66
- if (!_.isEmpty(suggestions)) {
65
+ if (suggestions.length > 0) {
67
66
  errMsg += ` You may want to apply one of the following locales instead: ${suggestions}`;
68
67
  }
69
68
  throw new Error(errMsg, {cause: e});
@@ -114,10 +113,10 @@ async function fetchLocaleSuggestions(
114
113
  const supportedLocales = await this.settingsApp.listSupportedLocales();
115
114
  const suggestedLocales = supportedLocales.filter(
116
115
  (locale) =>
117
- _.toLower(language) === _.toLower(locale.language) ||
118
- _.toLower(country) === _.toLower(locale.country),
116
+ language?.toLowerCase() === locale.language?.toLowerCase() ||
117
+ country?.toLowerCase() === locale.country?.toLowerCase(),
119
118
  );
120
- return _.isEmpty(suggestedLocales) ? supportedLocales : suggestedLocales;
119
+ return util.isEmpty(suggestedLocales) ? supportedLocales : suggestedLocales;
121
120
  }
122
121
 
123
122
  function toLocaleAbbr({language, country, script}: Locale): string {
@@ -1,6 +1,5 @@
1
1
  import {util} from '@appium/support';
2
2
  import {errors} from 'appium/driver';
3
- import _ from 'lodash';
4
3
  import {exec} from 'teen_process';
5
4
  import type {ExecError} from 'teen_process';
6
5
  import {ADB_SHELL_FEATURE} from '../utils';
@@ -36,11 +35,16 @@ export async function mobileShell<T extends boolean>(
36
35
  ): Promise<T extends true ? {stdout: string; stderr: string} : string> {
37
36
  this.assertFeatureEnabled(ADB_SHELL_FEATURE);
38
37
 
39
- if (!_.isString(command)) {
38
+ if (typeof command !== 'string') {
40
39
  throw new errors.InvalidArgumentError(`The 'command' argument is mandatory`);
41
40
  }
42
41
 
43
- const adbArgs = [...this.adb.executable.defaultArgs, 'shell', command, ..._.castArray(args)];
42
+ const adbArgs = [
43
+ ...this.adb.executable.defaultArgs,
44
+ 'shell',
45
+ command,
46
+ ...(Array.isArray(args) ? args : [args]),
47
+ ];
44
48
  this.log.debug(`Running '${this.adb.executable.path} ${util.quote(adbArgs)}'`);
45
49
  try {
46
50
  const {stdout, stderr} = await exec(this.adb.executable.path, adbArgs, {timeout});