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.
- package/CHANGELOG.md +12 -0
- package/build/lib/commands/app-management.d.ts +167 -113
- package/build/lib/commands/app-management.d.ts.map +1 -1
- package/build/lib/commands/app-management.js +147 -103
- package/build/lib/commands/app-management.js.map +1 -1
- package/build/lib/commands/find.d.ts +20 -3
- package/build/lib/commands/find.d.ts.map +1 -1
- package/build/lib/commands/find.js +10 -3
- package/build/lib/commands/find.js.map +1 -1
- package/build/lib/commands/performance.d.ts +80 -51
- package/build/lib/commands/performance.d.ts.map +1 -1
- package/build/lib/commands/performance.js +113 -89
- package/build/lib/commands/performance.js.map +1 -1
- package/build/lib/commands/shell.d.ts +21 -0
- package/build/lib/commands/shell.d.ts.map +1 -1
- package/build/lib/commands/shell.js +21 -0
- package/build/lib/commands/shell.js.map +1 -1
- package/build/lib/commands/streamscreen.d.ts +34 -64
- package/build/lib/commands/streamscreen.d.ts.map +1 -1
- package/build/lib/commands/streamscreen.js +41 -80
- package/build/lib/commands/streamscreen.js.map +1 -1
- package/lib/commands/app-management.ts +639 -0
- package/lib/commands/find.ts +20 -3
- package/lib/commands/{performance.js → performance.ts} +167 -108
- package/lib/commands/shell.ts +21 -0
- package/lib/commands/{streamscreen.js → streamscreen.ts} +86 -109
- package/package.json +2 -2
- package/lib/commands/app-management.js +0 -533
|
@@ -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
|
-
*
|
|
38
|
-
*
|
|
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
|
|
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
|
|
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
|
|
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
|
|
51
|
-
* @param
|
|
52
|
-
* @param
|
|
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
|
|
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
|
|
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
|
|
74
|
+
* @param logPipelineDetails Whether to log GStreamer pipeline events into the standard log output.
|
|
63
75
|
* Might be useful for debugging purposes.
|
|
64
|
-
* @
|
|
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
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
|
|
128
|
-
let
|
|
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
|
-
|
|
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
|
-
*
|
|
214
|
-
*
|
|
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 =
|
|
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
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
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
|
-
|
|
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
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
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
|
-
|
|
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
|
|
532
|
-
|
|
533
|
-
|
|
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.
|
|
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": "^
|
|
55
|
+
"asyncbox": "^4.0.1",
|
|
56
56
|
"axios": "^1.x",
|
|
57
57
|
"bluebird": "^3.4.7",
|
|
58
58
|
"io.appium.settings": "^7.0.4",
|