appium-android-driver 12.4.5 → 12.4.7

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.
@@ -1,6 +1,7 @@
1
- // @ts-check
2
1
  import {retryInterval} from 'asyncbox';
3
2
  import _ from 'lodash';
3
+ import type {AndroidDriver} from '../driver';
4
+ import type {PerformanceDataType} from './types';
4
5
 
5
6
  export const NETWORK_KEYS = [
6
7
  [
@@ -14,9 +15,10 @@ export const NETWORK_KEYS = [
14
15
  'bucketDuration',
15
16
  ],
16
17
  ['st', 'activeTime', 'rb', 'rp', 'tb', 'tp', 'op', 'bucketDuration'],
17
- ];
18
- export const CPU_KEYS = /** @type {const} */ (['user', 'kernel']);
19
- export const BATTERY_KEYS = ['power'];
18
+ ] as const;
19
+
20
+ export const CPU_KEYS = ['user', 'kernel'] as const;
21
+ export const BATTERY_KEYS = ['power'] as const;
20
22
  export const MEMORY_KEYS = [
21
23
  'totalPrivateDirty',
22
24
  'nativePrivateDirty',
@@ -33,7 +35,8 @@ export const MEMORY_KEYS = [
33
35
  'nativeRss',
34
36
  'dalvikRss',
35
37
  'totalRss',
36
- ];
38
+ ] as const;
39
+
37
40
  export const SUPPORTED_PERFORMANCE_DATA_TYPES = Object.freeze({
38
41
  cpuinfo:
39
42
  'the amount of cpu by user and kernel process - cpu information for applications on real devices and simulators',
@@ -43,7 +46,8 @@ export const SUPPORTED_PERFORMANCE_DATA_TYPES = Object.freeze({
43
46
  'the remaining battery power - battery power information for applications on real devices and simulators',
44
47
  networkinfo:
45
48
  'the network statistics - network rx/tx information for applications on real devices and simulators',
46
- });
49
+ } as const);
50
+
47
51
  export const MEMINFO_TITLES = Object.freeze({
48
52
  NATIVE: 'Native',
49
53
  DALVIK: 'Dalvik',
@@ -52,41 +56,49 @@ export const MEMINFO_TITLES = Object.freeze({
52
56
  MTRACK: 'mtrack',
53
57
  TOTAL: 'TOTAL',
54
58
  HEAP: 'Heap',
55
- });
59
+ } as const);
60
+
56
61
  const RETRY_PAUSE_MS = 1000;
57
62
 
58
63
  /**
59
- * @this {AndroidDriver}
60
- * @returns {Promise<import('./types').PerformanceDataType[]>}
64
+ * Retrieves the list of available performance data types.
65
+ *
66
+ * @returns An array of supported performance data type names.
67
+ * The possible values are: 'cpuinfo', 'memoryinfo', 'batteryinfo', 'networkinfo'.
61
68
  */
62
- export async function getPerformanceDataTypes() {
63
- return /** @type {import('./types').PerformanceDataType[]} */ (
64
- _.keys(SUPPORTED_PERFORMANCE_DATA_TYPES)
65
- );
69
+ export async function getPerformanceDataTypes(
70
+ this: AndroidDriver,
71
+ ): Promise<PerformanceDataType[]> {
72
+ return _.keys(SUPPORTED_PERFORMANCE_DATA_TYPES) as PerformanceDataType[];
66
73
  }
67
74
 
68
75
  /**
69
- * @this {AndroidDriver}
70
- * @param {string} packageName
71
- * @param {string} dataType
72
- * @param {number} [retries=2]
73
- * @returns {Promise<any[][]>}
76
+ * Retrieves performance data for the specified data type.
77
+ *
78
+ * @param packageName The package name of the application to get performance data for.
79
+ * Required for 'cpuinfo' and 'memoryinfo' data types.
80
+ * @param dataType The type of performance data to retrieve.
81
+ * Must be one of values returned by {@link getPerformanceDataTypes}.
82
+ * @param retries The number of retry attempts if data retrieval fails.
83
+ * @returns A two-dimensional array where the first row contains column names
84
+ * and subsequent rows contain the sampled data values.
85
+ * @throws {Error} If the data type is not supported or data retrieval fails.
74
86
  */
75
- export async function getPerformanceData(packageName, dataType, retries = 2) {
76
- let result;
87
+ export async function getPerformanceData(
88
+ this: AndroidDriver,
89
+ packageName: string,
90
+ dataType: string,
91
+ retries: number = 2,
92
+ ): Promise<any[][]> {
77
93
  switch (_.toLower(dataType)) {
78
94
  case 'batteryinfo':
79
- result = await getBatteryInfo.call(this, retries);
80
- break;
95
+ return await getBatteryInfo.call(this, retries);
81
96
  case 'cpuinfo':
82
- result = await getCPUInfo.call(this, packageName, retries);
83
- break;
97
+ return await getCPUInfo.call(this, packageName, retries);
84
98
  case 'memoryinfo':
85
- result = await getMemoryInfo.call(this, packageName, retries);
86
- break;
99
+ return await getMemoryInfo.call(this, packageName, retries);
87
100
  case 'networkinfo':
88
- result = await getNetworkTrafficInfo.call(this, retries);
89
- break;
101
+ return await getNetworkTrafficInfo.call(this, retries);
90
102
  default:
91
103
  throw new Error(
92
104
  `No performance data of type '${dataType}' found. ` +
@@ -97,7 +109,6 @@ export async function getPerformanceData(packageName, dataType, retries = 2) {
97
109
  )}`,
98
110
  );
99
111
  }
100
- return /** @type {any[][]} */ (result);
101
112
  }
102
113
 
103
114
  /**
@@ -118,13 +129,12 @@ export async function getPerformanceData(packageName, dataType, retries = 2) {
118
129
  * [1478091600, null, null, 2714683, 11821, 1420564, 12650, 0, 3600], [1478095200, null, null, 10079213, 19962, 2487705, 20015, 0, 3600],
119
130
  * [1478098800, null, null, 4444433, 10227, 1430356, 10493, 0, 3600]]
120
131
  * - cpuinfo: [[user, kernel], [0.9, 1.3]]
121
- *
122
- * @this {AndroidDriver}
123
- * @param {string} packageName The name of the package identifier to fetch the data for
124
- * @param {import('./types').PerformanceDataType} dataType One of supported subsystem to fetch the data for.
125
- * @returns {Promise<any[][]>}
126
132
  */
127
- export async function mobileGetPerformanceData(packageName, dataType) {
133
+ export async function mobileGetPerformanceData(
134
+ this: AndroidDriver,
135
+ packageName: string,
136
+ dataType: PerformanceDataType,
137
+ ): Promise<any[][]> {
128
138
  return await this.getPerformanceData(packageName, dataType);
129
139
  }
130
140
 
@@ -136,7 +146,10 @@ export async function mobileGetPerformanceData(packageName, dataType) {
136
146
  * except 'TOTAL', which skips the second type name
137
147
  * !!! valDict gets mutated
138
148
  */
139
- function parseMeminfoForApi19To29(entries, valDict) {
149
+ function parseMeminfoForApi19To29(
150
+ entries: string[],
151
+ valDict: Record<string, string | number>,
152
+ ): void {
140
153
  const [type, subType] = entries;
141
154
  if (type === MEMINFO_TITLES.NATIVE && subType === MEMINFO_TITLES.HEAP) {
142
155
  [
@@ -166,7 +179,10 @@ function parseMeminfoForApi19To29(entries, valDict) {
166
179
  * ['<System Type>', '<Memory Type>', <pss total>, <private dirty>, <private clean>, <swapPss dirty>, <rss total>, <heap size>, <heap alloc>, <heap free>]
167
180
  * !!! valDict gets mutated
168
181
  */
169
- function parseMeminfoForApiAbove29(entries, valDict) {
182
+ function parseMeminfoForApiAbove29(
183
+ entries: string[],
184
+ valDict: Record<string, string | number>,
185
+ ): void {
170
186
  const [type, subType] = entries;
171
187
  if (type === MEMINFO_TITLES.NATIVE && subType === MEMINFO_TITLES.HEAP) {
172
188
  [
@@ -193,13 +209,28 @@ function parseMeminfoForApiAbove29(entries, valDict) {
193
209
  }
194
210
 
195
211
  /**
212
+ * Retrieves memory information for the specified application package.
213
+ *
214
+ * The data is parsed from the output of `dumpsys meminfo` command.
215
+ * The output format varies depending on the Android API level:
216
+ * - API 18-29: Contains PSS, private dirty, and heap information
217
+ * - API 30+: Additionally includes RSS information
196
218
  *
197
- * @this {AndroidDriver}
198
- * @param {string} packageName
199
- * @param {number} retries
219
+ * @param packageName The package name of the application to get memory information for.
220
+ * @param retries The number of retry attempts if data retrieval fails.
221
+ * @returns A two-dimensional array where the first row contains memory metric names
222
+ * (totalPrivateDirty, nativePrivateDirty, dalvikPrivateDirty, eglPrivateDirty,
223
+ * glPrivateDirty, totalPss, nativePss, dalvikPss, eglPss, glPss,
224
+ * nativeHeapAllocatedSize, nativeHeapSize, nativeRss, dalvikRss, totalRss)
225
+ * and the second row contains the corresponding values.
226
+ * @throws {Error} If memory data cannot be retrieved or parsed.
200
227
  */
201
- export async function getMemoryInfo(packageName, retries = 2) {
202
- return await retryInterval(retries, RETRY_PAUSE_MS, async () => {
228
+ export async function getMemoryInfo(
229
+ this: AndroidDriver,
230
+ packageName: string,
231
+ retries: number = 2,
232
+ ): Promise<any[][]> {
233
+ return (await retryInterval(retries, RETRY_PAUSE_MS, async () => {
203
234
  const cmd = [
204
235
  'dumpsys',
205
236
  'meminfo',
@@ -214,7 +245,7 @@ export async function getMemoryInfo(packageName, retries = 2) {
214
245
  if (!data) {
215
246
  throw new Error('No data from dumpsys');
216
247
  }
217
- const valDict = {totalPrivateDirty: ''};
248
+ const valDict: Record<string, string | number> = {totalPrivateDirty: ''};
218
249
  const apiLevel = await this.adb.getApiLevel();
219
250
  for (const line of data.split('\n')) {
220
251
  const entries = line.trim().split(/\s+/).filter(Boolean);
@@ -231,19 +262,40 @@ export async function getMemoryInfo(packageName, retries = 2) {
231
262
  }
232
263
 
233
264
  throw new Error(`Unable to parse memory data: '${data}'`);
234
- });
265
+ })) as any[][];
235
266
  }
236
267
 
237
268
  /**
238
- * @this {AndroidDriver}
239
- * @param {number} retries
269
+ * Retrieves network traffic statistics from the device.
270
+ *
271
+ * The data is parsed from the output of `dumpsys netstats` command.
272
+ * The output format differs between emulators and real devices:
273
+ * - Emulators: Uses full key names (bucketStart, activeTime, rxBytes, etc.)
274
+ * - Real devices (Android 7.1+): Uses abbreviated keys (st, rb, rp, tb, tp, op)
275
+ *
276
+ * @param retries The number of retry attempts if data retrieval fails.
277
+ * @returns A two-dimensional array where the first row contains network metric names
278
+ * (bucketStart/st, activeTime, rxBytes/rb, rxPackets/rp, txBytes/tb, txPackets/tp,
279
+ * operations/op, bucketDuration) and subsequent rows contain the sampled data
280
+ * for each time bucket.
281
+ * @throws {Error} If network traffic data cannot be retrieved or parsed.
240
282
  */
241
- export async function getNetworkTrafficInfo(retries = 2) {
242
- return await retryInterval(retries, RETRY_PAUSE_MS, async () => {
243
- let returnValue = [];
244
- let bucketDuration, bucketStart, activeTime, rxBytes, rxPackets, txBytes, txPackets, operations;
245
-
246
- let cmd = ['dumpsys', 'netstats'];
283
+ export async function getNetworkTrafficInfo(
284
+ this: AndroidDriver,
285
+ retries: number = 2,
286
+ ): Promise<any[][]> {
287
+ return (await retryInterval(retries, RETRY_PAUSE_MS, async () => {
288
+ const returnValue: any[][] = [];
289
+ let bucketDuration: string | undefined;
290
+ let bucketStart: string | undefined;
291
+ let activeTime: string | undefined;
292
+ let rxBytes: string | undefined;
293
+ let rxPackets: string | undefined;
294
+ let txBytes: string | undefined;
295
+ let txPackets: string | undefined;
296
+ let operations: string | undefined;
297
+
298
+ const cmd = ['dumpsys', 'netstats'];
247
299
  let data = await this.adb.shell(cmd);
248
300
  if (!data) throw new Error('No data from dumpsys'); //eslint-disable-line curly
249
301
 
@@ -278,12 +330,12 @@ export async function getNetworkTrafficInfo(retries = 2) {
278
330
  // st=1478095200 rb=10079213 rp=19962 tb=2487705 tp=20015 op=0
279
331
  // st=1478098800 rb=4444433 rp=10227 tb=1430356 tp=10493 op=0
280
332
  let index = 0;
281
- let fromXtstats = data.indexOf('Xt stats:');
333
+ const fromXtstats = data.indexOf('Xt stats:');
282
334
 
283
335
  let start = data.indexOf('Pending bytes:', fromXtstats);
284
336
  let delimiter = data.indexOf(':', start + 1);
285
337
  let end = data.indexOf('\n', delimiter + 1);
286
- let pendingBytes = data.substring(delimiter + 1, end).trim();
338
+ const pendingBytes = data.substring(delimiter + 1, end).trim();
287
339
 
288
340
  if (end > delimiter) {
289
341
  start = data.indexOf('bucketDuration', end + 1);
@@ -294,7 +346,7 @@ export async function getNetworkTrafficInfo(retries = 2) {
294
346
 
295
347
  if (start >= 0) {
296
348
  data = data.substring(end + 1, data.length);
297
- let arrayList = data.split('\n');
349
+ const arrayList = data.split('\n');
298
350
 
299
351
  if (arrayList.length > 0) {
300
352
  start = -1;
@@ -304,7 +356,7 @@ export async function getNetworkTrafficInfo(retries = 2) {
304
356
 
305
357
  if (start >= 0) {
306
358
  index = j;
307
- returnValue[0] = /** @type {string[]} */ ([]);
359
+ returnValue[0] = [];
308
360
 
309
361
  for (let k = 0; k < NETWORK_KEYS[j].length; ++k) {
310
362
  returnValue[0][k] = NETWORK_KEYS[j][k];
@@ -314,65 +366,65 @@ export async function getNetworkTrafficInfo(retries = 2) {
314
366
  }
315
367
 
316
368
  let returnIndex = 1;
317
- for (const data of arrayList) {
318
- start = data.indexOf(NETWORK_KEYS[index][0]);
369
+ for (const dataLine of arrayList) {
370
+ start = dataLine.indexOf(NETWORK_KEYS[index][0]);
319
371
 
320
372
  if (start >= 0) {
321
- delimiter = data.indexOf('=', start + 1);
322
- end = data.indexOf(' ', delimiter + 1);
323
- bucketStart = data.substring(delimiter + 1, end).trim();
373
+ delimiter = dataLine.indexOf('=', start + 1);
374
+ end = dataLine.indexOf(' ', delimiter + 1);
375
+ bucketStart = dataLine.substring(delimiter + 1, end).trim();
324
376
 
325
377
  if (end > delimiter) {
326
- start = data.indexOf(NETWORK_KEYS[index][1], end + 1);
378
+ start = dataLine.indexOf(NETWORK_KEYS[index][1], end + 1);
327
379
  if (start >= 0) {
328
- delimiter = data.indexOf('=', start + 1);
329
- end = data.indexOf(' ', delimiter + 1);
330
- activeTime = data.substring(delimiter + 1, end).trim();
380
+ delimiter = dataLine.indexOf('=', start + 1);
381
+ end = dataLine.indexOf(' ', delimiter + 1);
382
+ activeTime = dataLine.substring(delimiter + 1, end).trim();
331
383
  }
332
384
  }
333
385
 
334
386
  if (end > delimiter) {
335
- start = data.indexOf(NETWORK_KEYS[index][2], end + 1);
387
+ start = dataLine.indexOf(NETWORK_KEYS[index][2], end + 1);
336
388
  if (start >= 0) {
337
- delimiter = data.indexOf('=', start + 1);
338
- end = data.indexOf(' ', delimiter + 1);
339
- rxBytes = data.substring(delimiter + 1, end).trim();
389
+ delimiter = dataLine.indexOf('=', start + 1);
390
+ end = dataLine.indexOf(' ', delimiter + 1);
391
+ rxBytes = dataLine.substring(delimiter + 1, end).trim();
340
392
  }
341
393
  }
342
394
 
343
395
  if (end > delimiter) {
344
- start = data.indexOf(NETWORK_KEYS[index][3], end + 1);
396
+ start = dataLine.indexOf(NETWORK_KEYS[index][3], end + 1);
345
397
  if (start >= 0) {
346
- delimiter = data.indexOf('=', start + 1);
347
- end = data.indexOf(' ', delimiter + 1);
348
- rxPackets = data.substring(delimiter + 1, end).trim();
398
+ delimiter = dataLine.indexOf('=', start + 1);
399
+ end = dataLine.indexOf(' ', delimiter + 1);
400
+ rxPackets = dataLine.substring(delimiter + 1, end).trim();
349
401
  }
350
402
  }
351
403
 
352
404
  if (end > delimiter) {
353
- start = data.indexOf(NETWORK_KEYS[index][4], end + 1);
405
+ start = dataLine.indexOf(NETWORK_KEYS[index][4], end + 1);
354
406
  if (start >= 0) {
355
- delimiter = data.indexOf('=', start + 1);
356
- end = data.indexOf(' ', delimiter + 1);
357
- txBytes = data.substring(delimiter + 1, end).trim();
407
+ delimiter = dataLine.indexOf('=', start + 1);
408
+ end = dataLine.indexOf(' ', delimiter + 1);
409
+ txBytes = dataLine.substring(delimiter + 1, end).trim();
358
410
  }
359
411
  }
360
412
 
361
413
  if (end > delimiter) {
362
- start = data.indexOf(NETWORK_KEYS[index][5], end + 1);
414
+ start = dataLine.indexOf(NETWORK_KEYS[index][5], end + 1);
363
415
  if (start >= 0) {
364
- delimiter = data.indexOf('=', start + 1);
365
- end = data.indexOf(' ', delimiter + 1);
366
- txPackets = data.substring(delimiter + 1, end).trim();
416
+ delimiter = dataLine.indexOf('=', start + 1);
417
+ end = dataLine.indexOf(' ', delimiter + 1);
418
+ txPackets = dataLine.substring(delimiter + 1, end).trim();
367
419
  }
368
420
  }
369
421
 
370
422
  if (end > delimiter) {
371
- start = data.indexOf(NETWORK_KEYS[index][6], end + 1);
423
+ start = dataLine.indexOf(NETWORK_KEYS[index][6], end + 1);
372
424
  if (start >= 0) {
373
- delimiter = data.indexOf('=', start + 1);
374
- end = data.length;
375
- operations = data.substring(delimiter + 1, end).trim();
425
+ delimiter = dataLine.indexOf('=', start + 1);
426
+ end = dataLine.length;
427
+ operations = dataLine.substring(delimiter + 1, end).trim();
376
428
  }
377
429
  }
378
430
  returnValue[returnIndex++] = [
@@ -399,7 +451,7 @@ export async function getNetworkTrafficInfo(retries = 2) {
399
451
  } else {
400
452
  throw new Error(`Unable to parse network traffic data: '${data}'`);
401
453
  }
402
- });
454
+ })) as any[][];
403
455
  }
404
456
 
405
457
  /**
@@ -412,15 +464,18 @@ export async function getNetworkTrafficInfo(retries = 2) {
412
464
  * from 2023-02-07 12:04:40.556 to 2023-02-07 12:09:40.668. No process information
413
465
  * exists in the result if the process was not running during the period.
414
466
  *
415
- * @this {AndroidDriver}
416
- * @param {string} packageName The package name to get the CPU information.
417
- * @param {number} retries The number of retry count.
418
- * @returns {Promise<[typeof CPU_KEYS, [user: string, kernel: string]]>} The array of the parsed CPU upsage percentages.
467
+ * @param packageName The package name to get the CPU information.
468
+ * @param retries The number of retry count.
469
+ * @returns The array of the parsed CPU upsage percentages.
419
470
  * e.g. ['cpuinfo', ['14.3', '28.2']]
420
471
  * '14.3' is usage by the user (%), '28.2' is usage by the kernel (%)
421
472
  * @throws {Error} If it failed to parse the result of dumpsys, or no package name exists.
422
473
  */
423
- export async function getCPUInfo(packageName, retries = 2) {
474
+ export async function getCPUInfo(
475
+ this: AndroidDriver,
476
+ packageName: string,
477
+ retries: number = 2,
478
+ ): Promise<[typeof CPU_KEYS, [user: string, kernel: string]]> {
424
479
  // TODO: figure out why this is
425
480
  // sometimes, the function of 'adb.shell' fails. when I tested this function on the target of 'Galaxy Note5',
426
481
  // adb.shell(dumpsys cpuinfo) returns cpu datas for other application packages, but I can't find the data for packageName.
@@ -430,12 +485,11 @@ export async function getCPUInfo(packageName, retries = 2) {
430
485
  // @ts-expect-error retryInterval says it can return `null`, but it doesn't look like it actually can.
431
486
  // FIXME: fix this in asyncbox
432
487
  return await retryInterval(retries, RETRY_PAUSE_MS, async () => {
433
- /** @type {string} */
434
- let output;
488
+ let output: string;
435
489
  try {
436
490
  output = await this.adb.shell(['dumpsys', 'cpuinfo']);
437
491
  } catch (e) {
438
- const err = /** @type {import('teen_process').ExecError} */ (e);
492
+ const err = e as import('teen_process').ExecError;
439
493
  if (err.stderr) {
440
494
  this.log.info(err.stderr);
441
495
  }
@@ -454,35 +508,40 @@ export async function getCPUInfo(packageName, retries = 2) {
454
508
  `Unable to parse cpu usage data for '${packageName}'. Check the server log for more details`,
455
509
  );
456
510
  }
457
- const user = /** @type {string} */ (match[1]);
458
- const kernel = /** @type {string} */ (match[2]);
511
+ const user = match[1];
512
+ const kernel = match[2];
459
513
  return [CPU_KEYS, [user, kernel]];
460
514
  });
461
515
  }
462
516
 
463
517
  /**
464
- * @this {AndroidDriver}
465
- * @param {number} retries
518
+ * Retrieves battery level information from the device.
519
+ *
520
+ * The data is parsed from the output of `dumpsys battery` command.
521
+ *
522
+ * @param retries The number of retry attempts if data retrieval fails.
523
+ * @returns A two-dimensional array where the first row contains the metric name ['power']
524
+ * and the second row contains the battery level as a string (0-100).
525
+ * @throws {Error} If battery data cannot be retrieved or parsed.
466
526
  */
467
- export async function getBatteryInfo(retries = 2) {
468
- return await retryInterval(retries, RETRY_PAUSE_MS, async () => {
469
- let cmd = ['dumpsys', 'battery', '|', 'grep', 'level'];
470
- let data = await this.adb.shell(cmd);
527
+ export async function getBatteryInfo(
528
+ this: AndroidDriver,
529
+ retries: number = 2,
530
+ ): Promise<any[][]> {
531
+ return (await retryInterval(retries, RETRY_PAUSE_MS, async () => {
532
+ const cmd = ['dumpsys', 'battery', '|', 'grep', 'level'];
533
+ const data = await this.adb.shell(cmd);
471
534
  if (!data) throw new Error('No data from dumpsys'); //eslint-disable-line curly
472
535
 
473
- let power = parseInt((data.split(':')[1] || '').trim(), 10);
536
+ const power = parseInt((data.split(':')[1] || '').trim(), 10);
474
537
 
475
538
  if (!Number.isNaN(power)) {
476
539
  return [_.clone(BATTERY_KEYS), [power.toString()]];
477
540
  } else {
478
541
  throw new Error(`Unable to parse battery data: '${data}'`);
479
542
  }
480
- });
543
+ })) as any[][];
481
544
  }
482
545
 
483
546
  // #endregion
484
547
 
485
- /**
486
- * @typedef {import('../driver').AndroidDriver} AndroidDriver
487
- * @typedef {import('appium-adb').ADB} ADB
488
- */
@@ -4,6 +4,27 @@ import _ from 'lodash';
4
4
  import {exec} from 'teen_process';
5
5
  import {ADB_SHELL_FEATURE} from '../utils';
6
6
 
7
+ /**
8
+ * Executes a shell command on the device via ADB.
9
+ *
10
+ * This method runs an arbitrary shell command on the Android device and returns
11
+ * the output. The command is executed using `adb shell` with the specified
12
+ * command and arguments.
13
+ *
14
+ * Requirements:
15
+ * - The adb_shell feature must be enabled
16
+ *
17
+ * @param command The shell command to execute (e.g., 'pm', 'dumpsys', 'getprop').
18
+ * @param args Optional array of command arguments.
19
+ * @param timeout The maximum time in milliseconds to wait for command execution.
20
+ * Defaults to 20000ms (20 seconds).
21
+ * @param includeStderr If `true`, returns both stdout and stderr in an object.
22
+ * If `false` or undefined, returns only stdout as a string.
23
+ * @returns If `includeStderr` is `true`, returns `{ stdout: string, stderr: string }`.
24
+ * Otherwise, returns the command output as a string.
25
+ * @throws {errors.InvalidArgumentError} If `command` is not a string.
26
+ * @throws {Error} If the command execution fails or times out.
27
+ */
7
28
  export async function mobileShell<T extends boolean>(
8
29
  command: string,
9
30
  args: string[] = [],