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,5 +1,3 @@
1
- // @ts-check
2
-
3
1
  import {fs, logger, system, util} from '@appium/support';
4
2
  import {waitForCondition} from 'asyncbox';
5
3
  import B from 'bluebird';
@@ -10,6 +8,10 @@ import net from 'node:net';
10
8
  import url from 'node:url';
11
9
  import {checkPortStatus} from 'portscanner';
12
10
  import {SubProcess, exec} from 'teen_process';
11
+ import type {AndroidDriver} from '../driver';
12
+ import type {ADB} from 'appium-adb';
13
+ import type {AppiumLogger} from '@appium/types';
14
+ import type {DeviceInfo, InitGStreamerPipelineOpts} from './types';
13
15
 
14
16
  const RECORDING_INTERVAL_SEC = 5;
15
17
  const STREAMING_STARTUP_TIMEOUT_MS = 5000;
@@ -21,7 +23,7 @@ const REQUIRED_GST_PLUGINS = {
21
23
  jpegenc: 'gst-plugins-good',
22
24
  tcpserversink: 'gst-plugins-base',
23
25
  multipartmux: 'gst-plugins-good',
24
- };
26
+ } as const;
25
27
  const SCREENRECORD_BINARY = 'screenrecord';
26
28
  const GST_TUTORIAL_URL = 'https://gstreamer.freedesktop.org/documentation/installing/index.html';
27
29
  const DEFAULT_HOST = '127.0.0.1';
@@ -34,47 +36,58 @@ const BOUNDARY_STRING = '--2ae9746887f170b8cf7c271047ce314c';
34
36
  const ADB_SCREEN_STREAMING_FEATURE = 'adb_screen_streaming';
35
37
 
36
38
  /**
37
- * @this {import('../driver').AndroidDriver}
38
- * @param {number} [width] The scaled width of the device's screen.
39
+ * Starts a screen streaming session that broadcasts the device screen as an MJPEG stream.
40
+ *
41
+ * This method uses Android's `screenrecord` command to capture the screen and GStreamer
42
+ * to encode it as an MJPEG stream accessible via HTTP. The stream can be viewed in any
43
+ * web browser or MJPEG-compatible client.
44
+ *
45
+ * Requirements:
46
+ * - The device must have the `screenrecord` binary available
47
+ * - The host system must have GStreamer installed with required plugins
48
+ * - The ADB screen streaming feature must be enabled
49
+ *
50
+ * @param width The scaled width of the device's screen.
39
51
  * If unset then the script will assign it to the actual screen width measured
40
52
  * in pixels.
41
- * @param {number} [height] The scaled height of the device's screen.
53
+ * @param height The scaled height of the device's screen.
42
54
  * If unset then the script will assign it to the actual screen height
43
55
  * measured in pixels.
44
- * @param {number} [bitRate=4000000] The video bit rate for the video, in bits per second.
56
+ * @param bitRate The video bit rate for the video, in bits per second.
45
57
  * The default value is 4 Mb/s. You can increase the bit rate to improve video
46
58
  * quality, but doing so results in larger movie files.
47
- * @param {string} [host='127.0.0.1'] The IP address/host name to start the MJPEG server on.
59
+ * @param host The IP address/host name to start the MJPEG server on.
48
60
  * You can set it to `0.0.0.0` to trigger the broadcast on all available
49
61
  * network interfaces.
50
- * @param {number} [port=8093] The port number to start the MJPEG server on.
51
- * @param {number} [tcpPort=8094] The port number to start the internal TCP MJPEG broadcast on.
52
- * @param {string} [pathname] The HTTP request path the MJPEG server should be available on.
62
+ * @param port The port number to start the MJPEG server on.
63
+ * @param tcpPort The port number to start the internal TCP MJPEG broadcast on.
64
+ * @param pathname The HTTP request path the MJPEG server should be available on.
53
65
  * If unset, then any pathname on the given `host`/`port` combination will
54
66
  * work. Note that the value should always start with a single slash: `/`
55
- * @param {number} [quality=70] The quality value for the streamed JPEG images.
67
+ * @param quality The quality value for the streamed JPEG images.
56
68
  * This number should be in range `[1,100]`, where `100` is the best quality.
57
- * @param {boolean} [considerRotation=false] If set to `true` then GStreamer pipeline will increase the dimensions of
69
+ * @param considerRotation If set to `true` then GStreamer pipeline will increase the dimensions of
58
70
  * the resulting images to properly fit images in both landscape and portrait
59
71
  * orientations.
60
72
  * Set it to `true` if the device rotation is not going to be the same during
61
73
  * the broadcasting session.
62
- * @param {boolean} [logPipelineDetails=false] Whether to log GStreamer pipeline events into the standard log output.
74
+ * @param logPipelineDetails Whether to log GStreamer pipeline events into the standard log output.
63
75
  * Might be useful for debugging purposes.
64
- * @returns {Promise<void>}
65
- */
76
+ * @throws {Error} If streaming requirements are not met, ports are busy, or streaming fails to start.
77
+ */
66
78
  export async function mobileStartScreenStreaming(
67
- width,
68
- height,
69
- bitRate,
70
- host = DEFAULT_HOST,
71
- port = DEFAULT_PORT,
72
- pathname,
73
- tcpPort = DEFAULT_PORT + 1,
74
- quality = DEFAULT_QUALITY,
75
- considerRotation = false,
76
- logPipelineDetails = false,
77
- ) {
79
+ this: AndroidDriver,
80
+ width?: number,
81
+ height?: number,
82
+ bitRate?: number,
83
+ host: string = DEFAULT_HOST,
84
+ port: number = DEFAULT_PORT,
85
+ pathname?: string,
86
+ tcpPort: number = DEFAULT_PORT + 1,
87
+ quality: number = DEFAULT_QUALITY,
88
+ considerRotation: boolean = false,
89
+ logPipelineDetails: boolean = false,
90
+ ): Promise<void> {
78
91
  this.assertFeatureEnabled(ADB_SCREEN_STREAMING_FEATURE);
79
92
 
80
93
  if (_.isUndefined(this._screenStreamingProps)) {
@@ -124,12 +137,10 @@ export async function mobileStartScreenStreaming(
124
137
  throw e;
125
138
  }
126
139
 
127
- /** @type {import('node:net').Socket|undefined} */
128
- let mjpegSocket;
129
- /** @type {import('node:http').Server|undefined} */
130
- let mjpegServer;
140
+ let mjpegSocket: net.Socket | undefined;
141
+ let mjpegServer: http.Server | undefined;
131
142
  try {
132
- await new B((resolve, reject) => {
143
+ await new B<void>((resolve, reject) => {
133
144
  mjpegSocket = net.createConnection(tcpPort, TCP_HOST, () => {
134
145
  this.log.info(`Successfully connected to MJPEG stream at tcp://${TCP_HOST}:${tcpPort}`);
135
146
  mjpegServer = http.createServer((req, res) => {
@@ -162,7 +173,9 @@ export async function mobileStartScreenStreaming(
162
173
  'Content-Type': `multipart/x-mixed-replace; boundary=${BOUNDARY_STRING}`,
163
174
  });
164
175
 
165
- /** @type {import('node:net').Socket} */ (mjpegSocket).pipe(res);
176
+ if (mjpegSocket) {
177
+ mjpegSocket.pipe(res);
178
+ }
166
179
  });
167
180
  mjpegServer.on('error', (e) => {
168
181
  this.log.warn(e);
@@ -210,10 +223,16 @@ export async function mobileStartScreenStreaming(
210
223
  }
211
224
 
212
225
  /**
213
- * @this {import('../driver').AndroidDriver}
214
- * @returns {Promise<void>}
226
+ * Stops the currently running screen streaming session.
227
+ *
228
+ * This method gracefully terminates all processes involved in screen streaming:
229
+ * - The MJPEG HTTP server
230
+ * - The GStreamer pipeline
231
+ * - The device screen recording process
232
+ *
233
+ * If no streaming session is active, this method returns without error.
215
234
  */
216
- export async function mobileStopScreenStreaming() {
235
+ export async function mobileStopScreenStreaming(this: AndroidDriver): Promise<void> {
217
236
  if (_.isEmpty(this._screenStreamingProps)) {
218
237
  if (!_.isUndefined(this._screenStreamingProps)) {
219
238
  this.log.debug(`Screen streaming is not running. There is nothing to stop`);
@@ -252,13 +271,7 @@ export async function mobileStopScreenStreaming() {
252
271
 
253
272
  // #region Internal helpers
254
273
 
255
- /**
256
- *
257
- * @param {string} streamName
258
- * @param {string} udid
259
- * @returns {AppiumLogger}
260
- */
261
- function createStreamingLogger(streamName, udid) {
274
+ function createStreamingLogger(streamName: string, udid: string): AppiumLogger {
262
275
  return logger.getLogger(
263
276
  `${streamName}@` +
264
277
  _.truncate(udid, {
@@ -268,18 +281,14 @@ function createStreamingLogger(streamName, udid) {
268
281
  );
269
282
  }
270
283
 
271
- /**
272
- *
273
- * @param {ADB} adb
274
- */
275
- async function verifyStreamingRequirements(adb) {
284
+ async function verifyStreamingRequirements(adb: ADB): Promise<void> {
276
285
  if (!_.trim(await adb.shell(['which', SCREENRECORD_BINARY]))) {
277
286
  throw new Error(
278
287
  `The required '${SCREENRECORD_BINARY}' binary is not available on the device under test`,
279
288
  );
280
289
  }
281
290
 
282
- const gstreamerCheckPromises = [];
291
+ const gstreamerCheckPromises: Promise<void>[] = [];
283
292
  for (const binaryName of [GSTREAMER_BINARY, GST_INSPECT_BINARY]) {
284
293
  gstreamerCheckPromises.push(
285
294
  (async () => {
@@ -296,7 +305,7 @@ async function verifyStreamingRequirements(adb) {
296
305
  }
297
306
  await B.all(gstreamerCheckPromises);
298
307
 
299
- const moduleCheckPromises = [];
308
+ const moduleCheckPromises: Promise<void>[] = [];
300
309
  for (const [name, modName] of _.toPairs(REQUIRED_GST_PLUGINS)) {
301
310
  moduleCheckPromises.push(
302
311
  (async () => {
@@ -313,23 +322,15 @@ async function verifyStreamingRequirements(adb) {
313
322
  await B.all(moduleCheckPromises);
314
323
  }
315
324
 
316
- const deviceInfoRegexes = /** @type {const} */ ([
325
+ const deviceInfoRegexes = [
317
326
  ['width', /\bdeviceWidth=(\d+)/],
318
327
  ['height', /\bdeviceHeight=(\d+)/],
319
328
  ['fps', /\bfps=(\d+)/],
320
- ]);
329
+ ] as const;
321
330
 
322
- /**
323
- *
324
- * @param {ADB} adb
325
- * @param {AppiumLogger} [log]
326
- */
327
- async function getDeviceInfo(adb, log) {
331
+ async function getDeviceInfo(adb: ADB, log?: AppiumLogger): Promise<DeviceInfo> {
328
332
  const output = await adb.shell(['dumpsys', 'display']);
329
- /**
330
- * @type {DeviceInfo}
331
- */
332
- const result = {};
333
+ const result: Partial<DeviceInfo> = {};
333
334
  for (const [key, pattern] of deviceInfoRegexes) {
334
335
  const match = pattern.exec(output);
335
336
  if (!match) {
@@ -342,18 +343,15 @@ async function getDeviceInfo(adb, log) {
342
343
  result[key] = parseInt(match[1], 10);
343
344
  }
344
345
  result.udid = String(adb.curDeviceId);
345
- return result;
346
+ return result as DeviceInfo;
346
347
  }
347
348
 
348
- /**
349
- *
350
- * @param {ADB} adb
351
- * @param {AppiumLogger} log
352
- * @param {DeviceInfo} deviceInfo
353
- * @param {{width?: string|number, height?: string|number, bitRate?: string|number}} opts
354
- * @returns
355
- */
356
- async function initDeviceStreamingProc(adb, log, deviceInfo, opts = {}) {
349
+ async function initDeviceStreamingProc(
350
+ adb: ADB,
351
+ log: AppiumLogger,
352
+ deviceInfo: DeviceInfo,
353
+ opts: {width?: string | number; height?: string | number; bitRate?: string | number} = {},
354
+ ): Promise<ReturnType<typeof spawn>> {
357
355
  const {width, height, bitRate} = opts;
358
356
  const adjustedWidth = _.isUndefined(width) ? deviceInfo.width : parseInt(String(width), 10);
359
357
  const adjustedHeight = _.isUndefined(height) ? deviceInfo.height : parseInt(String(height), 10);
@@ -383,11 +381,7 @@ async function initDeviceStreamingProc(adb, log, deviceInfo, opts = {}) {
383
381
 
384
382
  let isStarted = false;
385
383
  const deviceStreamingLogger = createStreamingLogger(SCREENRECORD_BINARY, deviceInfo.udid);
386
- /**
387
- *
388
- * @param {Buffer|string} chunk
389
- */
390
- const errorsListener = (chunk) => {
384
+ const errorsListener = (chunk: Buffer | string) => {
391
385
  const stderr = chunk.toString();
392
386
  if (_.trim(stderr)) {
393
387
  deviceStreamingLogger.debug(stderr);
@@ -395,11 +389,7 @@ async function initDeviceStreamingProc(adb, log, deviceInfo, opts = {}) {
395
389
  };
396
390
  deviceStreaming.stderr.on('data', errorsListener);
397
391
 
398
- /**
399
- *
400
- * @param {Buffer|string} chunk
401
- */
402
- const startupListener = (chunk) => {
392
+ const startupListener = (chunk: Buffer | string) => {
403
393
  if (!isStarted) {
404
394
  isStarted = !_.isEmpty(chunk);
405
395
  }
@@ -415,7 +405,7 @@ async function initDeviceStreamingProc(adb, log, deviceInfo, opts = {}) {
415
405
  } catch (e) {
416
406
  throw log.errorWithException(
417
407
  `Cannot start the screen streaming process. Original error: ${
418
- /** @type {Error} */ (e).message
408
+ (e as Error).message
419
409
  }`,
420
410
  );
421
411
  } finally {
@@ -425,14 +415,12 @@ async function initDeviceStreamingProc(adb, log, deviceInfo, opts = {}) {
425
415
  return deviceStreaming;
426
416
  }
427
417
 
428
- /**
429
- *
430
- * @param {import('node:child_process').ChildProcess} deviceStreamingProc
431
- * @param {DeviceInfo} deviceInfo
432
- * @param {AppiumLogger} log
433
- * @param {import('./types').InitGStreamerPipelineOpts} opts
434
- */
435
- async function initGstreamerPipeline(deviceStreamingProc, deviceInfo, log, opts) {
418
+ async function initGstreamerPipeline(
419
+ deviceStreamingProc: ReturnType<typeof spawn>,
420
+ deviceInfo: DeviceInfo,
421
+ log: AppiumLogger,
422
+ opts: InitGStreamerPipelineOpts,
423
+ ): Promise<SubProcess> {
436
424
  const {width, height, quality, tcpPort, considerRotation, logPipelineDetails} = opts;
437
425
  const adjustedWidth = parseInt(String(width), 10) || deviceInfo.width;
438
426
  const adjustedHeight = parseInt(String(height), 10) || deviceInfo.height;
@@ -477,12 +465,7 @@ async function initGstreamerPipeline(deviceStreamingProc, deviceInfo, log, opts)
477
465
  log.debug(`Pipeline streaming process exited with code ${code}, signal ${signal}`);
478
466
  });
479
467
  const gstreamerLogger = createStreamingLogger('gst', deviceInfo.udid);
480
- /**
481
- *
482
- * @param {string} stdout
483
- * @param {string} stderr
484
- */
485
- const gstOutputListener = (stdout, stderr) => {
468
+ const gstOutputListener = (stdout: string, stderr: string) => {
486
469
  if (_.trim(stderr || stdout)) {
487
470
  gstreamerLogger.debug(stderr || stdout);
488
471
  }
@@ -509,7 +492,7 @@ async function initGstreamerPipeline(deviceStreamingProc, deviceInfo, log, opts)
509
492
  didFail = true;
510
493
  throw log.errorWithException(
511
494
  `Cannot start the screen streaming pipeline. Original error: ${
512
- /** @type {Error} */ (e).message
495
+ (e as Error).message
513
496
  }`,
514
497
  );
515
498
  } finally {
@@ -521,23 +504,17 @@ async function initGstreamerPipeline(deviceStreamingProc, deviceInfo, log, opts)
521
504
  }
522
505
 
523
506
  /**
524
- * @param {import('node:http').IncomingMessage} req
525
507
  * @privateRemarks This may need to be future-proofed, as `IncomingMessage.connection` is deprecated and its `socket` prop is likely private
526
508
  */
527
- function extractRemoteAddress(req) {
509
+ function extractRemoteAddress(req: http.IncomingMessage): string {
528
510
  return (
529
- req.headers['x-forwarded-for'] ||
511
+ (req.headers['x-forwarded-for'] as string) ||
530
512
  req.socket.remoteAddress ||
531
- req.connection.remoteAddress ||
532
- // @ts-expect-error socket may be a private API??
533
- req.connection.socket.remoteAddress
513
+ (req as any).connection?.remoteAddress ||
514
+ (req as any).connection?.socket?.remoteAddress ||
515
+ 'unknown'
534
516
  );
535
517
  }
536
518
 
537
519
  // #endregion
538
520
 
539
- /**
540
- * @typedef {import('appium-adb').ADB} ADB
541
- * @typedef {import('@appium/types').AppiumLogger} AppiumLogger
542
- * @typedef {import('./types').DeviceInfo} DeviceInfo
543
- */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "appium-android-driver",
3
- "version": "12.4.5",
3
+ "version": "12.4.7",
4
4
  "description": "Android UiAutomator and Chrome support for Appium",
5
5
  "keywords": [
6
6
  "appium",
@@ -52,7 +52,7 @@
52
52
  "@colors/colors": "^1.6.0",
53
53
  "appium-adb": "^14.1.0",
54
54
  "appium-chromedriver": "^8.0.18",
55
- "asyncbox": "^3.0.0",
55
+ "asyncbox": "^4.0.1",
56
56
  "axios": "^1.x",
57
57
  "bluebird": "^3.4.7",
58
58
  "io.appium.settings": "^7.0.4",