appium-android-driver 5.14.7 → 6.0.1
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 +7 -0
- package/build/lib/commands/actions.d.ts +6 -224
- package/build/lib/commands/actions.d.ts.map +1 -1
- package/build/lib/commands/actions.js +306 -405
- package/build/lib/commands/actions.js.map +1 -1
- package/build/lib/commands/alert.d.ts +7 -9
- package/build/lib/commands/alert.d.ts.map +1 -1
- package/build/lib/commands/alert.js +24 -18
- package/build/lib/commands/alert.js.map +1 -1
- package/build/lib/commands/app-management.d.ts +7 -313
- package/build/lib/commands/app-management.d.ts.map +1 -1
- package/build/lib/commands/app-management.js +135 -293
- package/build/lib/commands/app-management.js.map +1 -1
- package/build/lib/commands/context.d.ts +8 -92
- package/build/lib/commands/context.d.ts.map +1 -1
- package/build/lib/commands/context.js +381 -439
- package/build/lib/commands/context.js.map +1 -1
- package/build/lib/commands/element.d.ts +8 -35
- package/build/lib/commands/element.d.ts.map +1 -1
- package/build/lib/commands/element.js +153 -136
- package/build/lib/commands/element.js.map +1 -1
- package/build/lib/commands/emu-console.d.ts +6 -48
- package/build/lib/commands/emu-console.d.ts.map +1 -1
- package/build/lib/commands/emu-console.js +19 -34
- package/build/lib/commands/emu-console.js.map +1 -1
- package/build/lib/commands/execute.d.ts +6 -5
- package/build/lib/commands/execute.d.ts.map +1 -1
- package/build/lib/commands/execute.js +77 -66
- package/build/lib/commands/execute.js.map +1 -1
- package/build/lib/commands/file-actions.d.ts +7 -128
- package/build/lib/commands/file-actions.d.ts.map +1 -1
- package/build/lib/commands/file-actions.js +183 -219
- package/build/lib/commands/file-actions.js.map +1 -1
- package/build/lib/commands/find.d.ts +8 -12
- package/build/lib/commands/find.d.ts.map +1 -1
- package/build/lib/commands/find.js +19 -23
- package/build/lib/commands/find.js.map +1 -1
- package/build/lib/commands/general.d.ts +9 -132
- package/build/lib/commands/general.d.ts.map +1 -1
- package/build/lib/commands/general.js +281 -312
- package/build/lib/commands/general.js.map +1 -1
- package/build/lib/commands/ime.d.ts +7 -10
- package/build/lib/commands/ime.d.ts.map +1 -1
- package/build/lib/commands/ime.js +47 -35
- package/build/lib/commands/ime.js.map +1 -1
- package/build/lib/commands/index.d.ts +27 -2
- package/build/lib/commands/index.d.ts.map +1 -1
- package/build/lib/commands/index.js +41 -19
- package/build/lib/commands/index.js.map +1 -1
- package/build/lib/commands/intent.d.ts +7 -417
- package/build/lib/commands/intent.d.ts.map +1 -1
- package/build/lib/commands/intent.js +104 -216
- package/build/lib/commands/intent.js.map +1 -1
- package/build/lib/commands/keyboard.d.ts +6 -5
- package/build/lib/commands/keyboard.d.ts.map +1 -1
- package/build/lib/commands/keyboard.js +16 -8
- package/build/lib/commands/keyboard.js.map +1 -1
- package/build/lib/commands/log.d.ts +7 -44
- package/build/lib/commands/log.d.ts.map +1 -1
- package/build/lib/commands/log.js +146 -108
- package/build/lib/commands/log.js.map +1 -1
- package/build/lib/commands/media-projection.d.ts +7 -143
- package/build/lib/commands/media-projection.d.ts.map +1 -1
- package/build/lib/commands/media-projection.js +113 -140
- package/build/lib/commands/media-projection.js.map +1 -1
- package/build/lib/commands/mixins.d.ts +740 -0
- package/build/lib/commands/mixins.d.ts.map +1 -0
- package/build/lib/commands/mixins.js +19 -0
- package/build/lib/commands/mixins.js.map +1 -0
- package/build/lib/commands/network.d.ts +7 -138
- package/build/lib/commands/network.d.ts.map +1 -1
- package/build/lib/commands/network.js +212 -254
- package/build/lib/commands/network.js.map +1 -1
- package/build/lib/commands/performance.d.ts +24 -70
- package/build/lib/commands/performance.d.ts.map +1 -1
- package/build/lib/commands/performance.js +144 -100
- package/build/lib/commands/performance.js.map +1 -1
- package/build/lib/commands/permissions.d.ts +8 -92
- package/build/lib/commands/permissions.d.ts.map +1 -1
- package/build/lib/commands/permissions.js +75 -87
- package/build/lib/commands/permissions.js.map +1 -1
- package/build/lib/commands/recordscreen.d.ts +7 -193
- package/build/lib/commands/recordscreen.d.ts.map +1 -1
- package/build/lib/commands/recordscreen.js +151 -182
- package/build/lib/commands/recordscreen.js.map +1 -1
- package/build/lib/commands/shell.d.ts +7 -7
- package/build/lib/commands/shell.d.ts.map +1 -1
- package/build/lib/commands/shell.js +40 -33
- package/build/lib/commands/shell.js.map +1 -1
- package/build/lib/commands/streamscreen.d.ts +9 -103
- package/build/lib/commands/streamscreen.d.ts.map +1 -1
- package/build/lib/commands/streamscreen.js +261 -218
- package/build/lib/commands/streamscreen.js.map +1 -1
- package/build/lib/commands/system-bars.d.ts +22 -90
- package/build/lib/commands/system-bars.d.ts.map +1 -1
- package/build/lib/commands/system-bars.js +76 -74
- package/build/lib/commands/system-bars.js.map +1 -1
- package/build/lib/commands/touch.d.ts +10 -29
- package/build/lib/commands/touch.d.ts.map +1 -1
- package/build/lib/commands/touch.js +301 -285
- package/build/lib/commands/touch.js.map +1 -1
- package/build/lib/commands/types.d.ts +978 -0
- package/build/lib/commands/types.d.ts.map +1 -0
- package/build/lib/commands/types.js +3 -0
- package/build/lib/commands/types.js.map +1 -0
- package/build/lib/constraints.d.ts +291 -0
- package/build/lib/constraints.d.ts.map +1 -0
- package/build/lib/{desired-caps.js → constraints.js} +103 -102
- package/build/lib/constraints.js.map +1 -0
- package/build/lib/driver.d.ts +68 -37
- package/build/lib/driver.d.ts.map +1 -1
- package/build/lib/driver.js +123 -80
- package/build/lib/driver.js.map +1 -1
- package/build/lib/helpers/android.d.ts +164 -0
- package/build/lib/helpers/android.d.ts.map +1 -0
- package/build/lib/helpers/android.js +819 -0
- package/build/lib/helpers/android.js.map +1 -0
- package/build/lib/helpers/index.d.ts +7 -0
- package/build/lib/helpers/index.d.ts.map +1 -0
- package/build/lib/helpers/index.js +29 -0
- package/build/lib/helpers/index.js.map +1 -0
- package/build/lib/helpers/types.d.ts +121 -0
- package/build/lib/helpers/types.d.ts.map +1 -0
- package/build/lib/helpers/types.js +3 -0
- package/build/lib/helpers/types.js.map +1 -0
- package/build/lib/helpers/unlock.d.ts +32 -0
- package/build/lib/helpers/unlock.d.ts.map +1 -0
- package/build/lib/helpers/unlock.js +273 -0
- package/build/lib/helpers/unlock.js.map +1 -0
- package/build/lib/helpers/webview.d.ts +74 -0
- package/build/lib/helpers/webview.d.ts.map +1 -0
- package/build/lib/helpers/webview.js +421 -0
- package/build/lib/helpers/webview.js.map +1 -0
- package/build/lib/index.d.ts +9 -0
- package/build/lib/index.d.ts.map +1 -0
- package/build/lib/index.js +37 -0
- package/build/lib/index.js.map +1 -0
- package/build/lib/method-map.d.ts +0 -8
- package/build/lib/method-map.d.ts.map +1 -1
- package/build/lib/method-map.js +63 -74
- package/build/lib/method-map.js.map +1 -1
- package/build/lib/stubs.d.ts +0 -1
- package/build/lib/stubs.d.ts.map +1 -1
- package/build/lib/stubs.js +1 -0
- package/build/lib/stubs.js.map +1 -1
- package/build/lib/utils.d.ts +1 -1
- package/build/lib/utils.d.ts.map +1 -1
- package/lib/commands/actions.js +351 -464
- package/lib/commands/alert.js +27 -17
- package/lib/commands/app-management.js +156 -314
- package/lib/commands/context.js +457 -441
- package/lib/commands/element.js +201 -157
- package/lib/commands/emu-console.js +25 -45
- package/lib/commands/execute.js +106 -90
- package/lib/commands/file-actions.js +222 -240
- package/lib/commands/find.ts +103 -0
- package/lib/commands/general.js +327 -339
- package/lib/commands/ime.js +50 -34
- package/lib/commands/{index.js → index.ts} +20 -24
- package/lib/commands/intent.js +108 -249
- package/lib/commands/keyboard.js +20 -8
- package/lib/commands/log.js +172 -116
- package/lib/commands/media-projection.js +134 -161
- package/lib/commands/mixins.ts +966 -0
- package/lib/commands/network.js +252 -281
- package/lib/commands/performance.js +203 -132
- package/lib/commands/permissions.js +108 -109
- package/lib/commands/recordscreen.js +212 -209
- package/lib/commands/shell.js +51 -40
- package/lib/commands/streamscreen.js +355 -289
- package/lib/commands/system-bars.js +92 -83
- package/lib/commands/touch.js +357 -294
- package/lib/commands/types.ts +1097 -0
- package/lib/{desired-caps.js → constraints.ts} +106 -103
- package/lib/{driver.js → driver.ts} +278 -132
- package/lib/helpers/android.ts +1143 -0
- package/lib/helpers/index.ts +6 -0
- package/lib/helpers/types.ts +134 -0
- package/lib/helpers/unlock.ts +329 -0
- package/lib/helpers/webview.ts +582 -0
- package/lib/index.ts +18 -0
- package/lib/method-map.js +87 -98
- package/lib/stubs.ts +0 -1
- package/package.json +26 -19
- package/build/index.js +0 -51
- package/build/lib/android-helpers.d.ts +0 -136
- package/build/lib/android-helpers.d.ts.map +0 -1
- package/build/lib/android-helpers.js +0 -855
- package/build/lib/android-helpers.js.map +0 -1
- package/build/lib/commands/coverage.d.ts +0 -5
- package/build/lib/commands/coverage.d.ts.map +0 -1
- package/build/lib/commands/coverage.js +0 -19
- package/build/lib/commands/coverage.js.map +0 -1
- package/build/lib/desired-caps.d.ts +0 -353
- package/build/lib/desired-caps.d.ts.map +0 -1
- package/build/lib/desired-caps.js.map +0 -1
- package/build/lib/unlock-helpers.d.ts +0 -38
- package/build/lib/unlock-helpers.d.ts.map +0 -1
- package/build/lib/unlock-helpers.js +0 -266
- package/build/lib/unlock-helpers.js.map +0 -1
- package/build/lib/webview-helpers.d.ts +0 -224
- package/build/lib/webview-helpers.d.ts.map +0 -1
- package/build/lib/webview-helpers.js +0 -528
- package/build/lib/webview-helpers.js.map +0 -1
- package/index.js +0 -24
- package/lib/android-helpers.js +0 -983
- package/lib/commands/coverage.js +0 -18
- package/lib/commands/find.js +0 -82
- package/lib/unlock-helpers.js +0 -278
- package/lib/webview-helpers.js +0 -602
|
@@ -1,11 +1,11 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import {fs, net, system, tempDir, timing, util} from '@appium/support';
|
|
4
|
+
import {waitForCondition} from 'asyncbox';
|
|
1
5
|
import _ from 'lodash';
|
|
2
|
-
import { waitForCondition } from 'asyncbox';
|
|
3
|
-
import { util, fs, net, tempDir, system, timing } from '@appium/support';
|
|
4
|
-
import { exec } from 'teen_process';
|
|
5
6
|
import path from 'path';
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const commands = {};
|
|
7
|
+
import {exec} from 'teen_process';
|
|
8
|
+
import {mixin} from './mixins';
|
|
9
9
|
|
|
10
10
|
const RETRY_PAUSE = 300;
|
|
11
11
|
const RETRY_TIMEOUT = 5000;
|
|
@@ -18,12 +18,22 @@ const DEFAULT_EXT = '.mp4';
|
|
|
18
18
|
const MIN_EMULATOR_API_LEVEL = 27;
|
|
19
19
|
const FFMPEG_BINARY = `ffmpeg${system.isWindows() ? '.exe' : ''}`;
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
/**
|
|
22
|
+
*
|
|
23
|
+
* @param {string} localFile
|
|
24
|
+
* @param {string} [remotePath]
|
|
25
|
+
* @param {import('./types').StopScreenRecordingOpts} uploadOptions
|
|
26
|
+
* @returns {Promise<string>}
|
|
27
|
+
*/
|
|
28
|
+
async function uploadRecordedMedia(localFile, remotePath, uploadOptions = {}) {
|
|
22
29
|
if (_.isEmpty(remotePath)) {
|
|
23
30
|
return (await util.toInMemoryBase64(localFile)).toString();
|
|
24
31
|
}
|
|
25
32
|
|
|
26
33
|
const {user, pass, method, headers, fileFieldName, formFields} = uploadOptions;
|
|
34
|
+
/**
|
|
35
|
+
* @type {import('@appium/support').NetOptions & import('@appium/support').HttpUploadOptions}
|
|
36
|
+
*/
|
|
27
37
|
const options = {
|
|
28
38
|
method: method || 'PUT',
|
|
29
39
|
headers,
|
|
@@ -33,32 +43,42 @@ async function uploadRecordedMedia (localFile, remotePath = null, uploadOptions
|
|
|
33
43
|
if (user && pass) {
|
|
34
44
|
options.auth = {user, pass};
|
|
35
45
|
}
|
|
36
|
-
await net.uploadFile(localFile, remotePath, options);
|
|
46
|
+
await net.uploadFile(localFile, /** @type {string} */ (remotePath), options);
|
|
37
47
|
return '';
|
|
38
48
|
}
|
|
39
49
|
|
|
40
|
-
|
|
50
|
+
/**
|
|
51
|
+
*
|
|
52
|
+
* @param {ADB} adb
|
|
53
|
+
* @param {boolean} isEmulator
|
|
54
|
+
*/
|
|
55
|
+
async function verifyScreenRecordIsSupported(adb, isEmulator) {
|
|
41
56
|
const apiLevel = await adb.getApiLevel();
|
|
42
57
|
if (isEmulator && apiLevel < MIN_EMULATOR_API_LEVEL) {
|
|
43
|
-
throw new Error(
|
|
58
|
+
throw new Error(
|
|
59
|
+
`Screen recording does not work on emulators running Android API level less than ${MIN_EMULATOR_API_LEVEL}`
|
|
60
|
+
);
|
|
44
61
|
}
|
|
45
62
|
if (apiLevel < 19) {
|
|
46
|
-
throw new Error(
|
|
63
|
+
throw new Error(
|
|
64
|
+
`Screen recording not available on API Level ${apiLevel}. Minimum API Level is 19.`
|
|
65
|
+
);
|
|
47
66
|
}
|
|
48
67
|
}
|
|
49
68
|
|
|
50
|
-
|
|
69
|
+
/**
|
|
70
|
+
*
|
|
71
|
+
* @param {ADB} adb
|
|
72
|
+
* @param {import('@appium/types').StringRecord} recordingProperties
|
|
73
|
+
* @param {import('@appium/types').AppiumLogger} [log]
|
|
74
|
+
* @returns {Promise<void>}
|
|
75
|
+
*/
|
|
76
|
+
async function scheduleScreenRecord(adb, recordingProperties, log) {
|
|
51
77
|
if (recordingProperties.stopped) {
|
|
52
78
|
return;
|
|
53
79
|
}
|
|
54
80
|
|
|
55
|
-
const {
|
|
56
|
-
timer,
|
|
57
|
-
videoSize,
|
|
58
|
-
bitRate,
|
|
59
|
-
timeLimit,
|
|
60
|
-
bugReport,
|
|
61
|
-
} = recordingProperties;
|
|
81
|
+
const {timer, videoSize, bitRate, timeLimit, bugReport} = recordingProperties;
|
|
62
82
|
|
|
63
83
|
let currentTimeLimit = MAX_RECORDING_TIME_SEC;
|
|
64
84
|
if (util.hasValue(recordingProperties.currentTimeLimit)) {
|
|
@@ -88,16 +108,19 @@ async function scheduleScreenRecord (adb, recordingProperties, log = null) {
|
|
|
88
108
|
}
|
|
89
109
|
|
|
90
110
|
recordingProperties.currentTimeLimit = timeLimitInt - currentDuration;
|
|
91
|
-
const chunkDuration =
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
111
|
+
const chunkDuration =
|
|
112
|
+
recordingProperties.currentTimeLimit < MAX_RECORDING_TIME_SEC
|
|
113
|
+
? recordingProperties.currentTimeLimit
|
|
114
|
+
: MAX_RECORDING_TIME_SEC;
|
|
115
|
+
log?.debug(
|
|
116
|
+
`Starting the next ${chunkDuration}s-chunk ` +
|
|
117
|
+
`of screen recording in order to achieve ${timeLimitInt}s total duration`
|
|
118
|
+
);
|
|
96
119
|
(async () => {
|
|
97
120
|
try {
|
|
98
121
|
await scheduleScreenRecord(adb, recordingProperties, log);
|
|
99
122
|
} catch (e) {
|
|
100
|
-
log?.error(e.stack);
|
|
123
|
+
log?.error(/** @type {Error} */ (e).stack);
|
|
101
124
|
recordingProperties.stopped = true;
|
|
102
125
|
}
|
|
103
126
|
})();
|
|
@@ -105,39 +128,59 @@ async function scheduleScreenRecord (adb, recordingProperties, log = null) {
|
|
|
105
128
|
|
|
106
129
|
await recordingProc.start(0);
|
|
107
130
|
try {
|
|
108
|
-
await waitForCondition(async () => await adb.fileExists(pathOnDevice),
|
|
109
|
-
|
|
131
|
+
await waitForCondition(async () => await adb.fileExists(pathOnDevice), {
|
|
132
|
+
waitMs: RETRY_TIMEOUT,
|
|
133
|
+
intervalMs: RETRY_PAUSE,
|
|
134
|
+
});
|
|
110
135
|
} catch (e) {
|
|
111
|
-
throw new Error(
|
|
112
|
-
`
|
|
136
|
+
throw new Error(
|
|
137
|
+
`The expected screen record file '${pathOnDevice}' does not exist after ${RETRY_TIMEOUT}ms. ` +
|
|
138
|
+
`Is ${SCREENRECORD_BINARY} utility available and operational on the device under test?`
|
|
139
|
+
);
|
|
113
140
|
}
|
|
114
141
|
|
|
115
142
|
recordingProperties.records.push(pathOnDevice);
|
|
116
143
|
recordingProperties.recordingProcess = recordingProc;
|
|
117
144
|
}
|
|
118
145
|
|
|
119
|
-
|
|
146
|
+
/**
|
|
147
|
+
*
|
|
148
|
+
* @param {string[]} mediaFiles
|
|
149
|
+
* @param {import('@appium/types').AppiumLogger} [log]
|
|
150
|
+
* @returns {Promise<string>}
|
|
151
|
+
*/
|
|
152
|
+
async function mergeScreenRecords(mediaFiles, log) {
|
|
120
153
|
try {
|
|
121
154
|
await fs.which(FFMPEG_BINARY);
|
|
122
155
|
} catch (e) {
|
|
123
|
-
throw new Error(
|
|
156
|
+
throw new Error(
|
|
157
|
+
`${FFMPEG_BINARY} utility is not available in PATH. Please install it from https://www.ffmpeg.org/`
|
|
158
|
+
);
|
|
124
159
|
}
|
|
125
|
-
const configContent = mediaFiles
|
|
126
|
-
.map((x) => `file '${x}'`)
|
|
127
|
-
.join('\n');
|
|
160
|
+
const configContent = mediaFiles.map((x) => `file '${x}'`).join('\n');
|
|
128
161
|
const configFile = path.resolve(path.dirname(mediaFiles[0]), 'config.txt');
|
|
129
162
|
await fs.writeFile(configFile, configContent, 'utf8');
|
|
130
163
|
log?.debug(`Generated ffmpeg merging config '${configFile}' with items:\n${configContent}`);
|
|
131
|
-
const result = path.resolve(
|
|
164
|
+
const result = path.resolve(
|
|
165
|
+
path.dirname(mediaFiles[0]),
|
|
166
|
+
`merge_${Math.floor(+new Date())}${DEFAULT_EXT}`
|
|
167
|
+
);
|
|
132
168
|
const args = ['-safe', '0', '-f', 'concat', '-i', configFile, '-c', 'copy', result];
|
|
133
|
-
log?.info(
|
|
169
|
+
log?.info(
|
|
170
|
+
`Initiating screen records merging using the command '${FFMPEG_BINARY} ${args.join(' ')}'`
|
|
171
|
+
);
|
|
134
172
|
await exec(FFMPEG_BINARY, args);
|
|
135
173
|
return result;
|
|
136
174
|
}
|
|
137
175
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
176
|
+
/**
|
|
177
|
+
*
|
|
178
|
+
* @param {ADB} adb
|
|
179
|
+
* @param {boolean} force
|
|
180
|
+
* @returns {Promise<boolean>}
|
|
181
|
+
*/
|
|
182
|
+
async function terminateBackgroundScreenRecording(adb, force = true) {
|
|
183
|
+
const pids = (await adb.getPIDsByName(SCREENRECORD_BINARY)).map((p) => `${p}`);
|
|
141
184
|
if (_.isEmpty(pids)) {
|
|
142
185
|
return false;
|
|
143
186
|
}
|
|
@@ -150,199 +193,159 @@ async function terminateBackgroundScreenRecording (adb, force = true) {
|
|
|
150
193
|
});
|
|
151
194
|
return true;
|
|
152
195
|
} catch (err) {
|
|
153
|
-
throw new Error(
|
|
196
|
+
throw new Error(
|
|
197
|
+
`Unable to stop the background screen recording: ${/** @type {Error} */ (err).message}`
|
|
198
|
+
);
|
|
154
199
|
}
|
|
155
200
|
}
|
|
156
201
|
|
|
157
|
-
|
|
158
|
-
/**
|
|
159
|
-
* @typedef {Object} StartRecordingOptions
|
|
160
|
-
*
|
|
161
|
-
* @property {?string} remotePath - The path to the remote location, where the captured video should be uploaded.
|
|
162
|
-
* The following protocols are supported: http/https, ftp.
|
|
163
|
-
* Null or empty string value (the default setting) means the content of resulting
|
|
164
|
-
* file should be encoded as Base64 and passed as the endpount response value.
|
|
165
|
-
* An exception will be thrown if the generated media file is too big to
|
|
166
|
-
* fit into the available process memory.
|
|
167
|
-
* This option only has an effect if there is screen recording process in progreess
|
|
168
|
-
* and `forceRestart` parameter is not set to `true`.
|
|
169
|
-
* @property {?string} user - The name of the user for the remote authentication. Only works if `remotePath` is provided.
|
|
170
|
-
* @property {?string} pass - The password for the remote authentication. Only works if `remotePath` is provided.
|
|
171
|
-
* @property {?string} method [PUT] - The http multipart upload method name. Only works if `remotePath` is provided.
|
|
172
|
-
* @property {?Object} headers - Additional headers mapping for multipart http(s) uploads
|
|
173
|
-
* @property {?string} fileFieldName [file] - The name of the form field, where the file content BLOB should be stored for
|
|
174
|
-
* http(s) uploads
|
|
175
|
-
* @property {?Object|Array<Pair>} formFields - Additional form fields for multipart http(s) uploads
|
|
176
|
-
* @property {?string} videoSize - The format is widthxheight.
|
|
177
|
-
* The default value is the device's native display resolution (if supported),
|
|
178
|
-
* 1280x720 if not. For best results,
|
|
179
|
-
* use a size supported by your device's Advanced Video Coding (AVC) encoder.
|
|
180
|
-
* For example, "1280x720"
|
|
181
|
-
* @property {?boolean} bugReport - Set it to `true` in order to display additional information on the video overlay,
|
|
182
|
-
* such as a timestamp, that is helpful in videos captured to illustrate bugs.
|
|
183
|
-
* This option is only supported since API level 27 (Android P).
|
|
184
|
-
* @property {?string|number} timeLimit - The maximum recording time, in seconds. The default value is 180 (3 minutes).
|
|
185
|
-
* The maximum value is 1800 (30 minutes). If the passed value is greater than 180 then
|
|
186
|
-
* the algorithm will try to schedule multiple screen recording chunks and merge the
|
|
187
|
-
* resulting videos into a single media file using `ffmpeg` utility.
|
|
188
|
-
* If the utility is not available in PATH then the most recent screen recording chunk is
|
|
189
|
-
* going to be returned.
|
|
190
|
-
* @property {?string|number} bitRate - The video bit rate for the video, in bits per second.
|
|
191
|
-
* The default value is 4000000 (4 Mbit/s). You can increase the bit rate to improve video quality,
|
|
192
|
-
* but doing so results in larger movie files.
|
|
193
|
-
* @property {?boolean} forceRestart - Whether to try to catch and upload/return the currently running screen recording
|
|
194
|
-
* (`false`, the default setting) or ignore the result of it and start a new recording
|
|
195
|
-
* immediately (`true`).
|
|
196
|
-
*/
|
|
197
|
-
|
|
198
202
|
/**
|
|
199
|
-
*
|
|
200
|
-
*
|
|
201
|
-
* It records screen activity to an MPEG-4 file. Audio is not recorded with the video file.
|
|
202
|
-
* If screen recording has been already started then the command will stop it forcefully and start a new one.
|
|
203
|
-
* The previously recorded video file will be deleted.
|
|
204
|
-
*
|
|
205
|
-
* @param {?StartRecordingOptions} options - The available options.
|
|
206
|
-
* @returns {string} Base64-encoded content of the recorded media file if
|
|
207
|
-
* any screen recording is currently running or an empty string.
|
|
208
|
-
* @throws {Error} If screen recording has failed to start or is not supported on the device under test.
|
|
203
|
+
* @type {import('./mixins').RecordScreenMixin & ThisType<import('../driver').AndroidDriver>}
|
|
204
|
+
* @satisfies {import('@appium/types').ExternalDriver}
|
|
209
205
|
*/
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
if (!_.isEmpty(this._screenRecordingProperties)) {
|
|
226
|
-
for (const record of (this._screenRecordingProperties.records || [])) {
|
|
227
|
-
await this.adb.rimraf(record);
|
|
206
|
+
const RecordScreenMixin = {
|
|
207
|
+
async startRecordingScreen(options = {}) {
|
|
208
|
+
const adb = /** @type {ADB} */ (this.adb);
|
|
209
|
+
await verifyScreenRecordIsSupported(adb, this.isEmulator());
|
|
210
|
+
|
|
211
|
+
let result = '';
|
|
212
|
+
const {
|
|
213
|
+
videoSize,
|
|
214
|
+
timeLimit = DEFAULT_RECORDING_TIME_SEC,
|
|
215
|
+
bugReport,
|
|
216
|
+
bitRate,
|
|
217
|
+
forceRestart,
|
|
218
|
+
} = options;
|
|
219
|
+
if (!forceRestart) {
|
|
220
|
+
result = await this.stopRecordingScreen(options);
|
|
228
221
|
}
|
|
229
|
-
this._screenRecordingProperties = null;
|
|
230
|
-
}
|
|
231
222
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
timer: new timing.Timer().start(),
|
|
240
|
-
videoSize,
|
|
241
|
-
timeLimit,
|
|
242
|
-
currentTimeLimit: timeLimit,
|
|
243
|
-
bitRate,
|
|
244
|
-
bugReport,
|
|
245
|
-
records: [],
|
|
246
|
-
recordingProcess: null,
|
|
247
|
-
stopped: false,
|
|
248
|
-
};
|
|
249
|
-
await scheduleScreenRecord(this.adb, this._screenRecordingProperties, this.log);
|
|
250
|
-
return result;
|
|
251
|
-
};
|
|
223
|
+
if (await terminateBackgroundScreenRecording(adb, true)) {
|
|
224
|
+
this.log.warn(
|
|
225
|
+
`There were some ${SCREENRECORD_BINARY} process leftovers running ` +
|
|
226
|
+
`in the background. Make sure you stop screen recording each time after it is started, ` +
|
|
227
|
+
`otherwise the recorded media might quickly exceed all the free space on the device under test.`
|
|
228
|
+
);
|
|
229
|
+
}
|
|
252
230
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
* An exception will be thrown if the generated media file is too big to
|
|
261
|
-
* fit into the available process memory.
|
|
262
|
-
* @property {?string} user - The name of the user for the remote authentication.
|
|
263
|
-
* @property {?string} pass - The password for the remote authentication.
|
|
264
|
-
* @property {?string} method - The http multipart upload method name. The 'PUT' one is used by default.
|
|
265
|
-
* @property {?Object} headers - Additional headers mapping for multipart http(s) uploads
|
|
266
|
-
* @property {?string} fileFieldName [file] - The name of the form field, where the file content BLOB should be stored for
|
|
267
|
-
* http(s) uploads
|
|
268
|
-
* @property {?Object|Array<Pair>} formFields - Additional form fields for multipart http(s) uploads
|
|
269
|
-
*/
|
|
231
|
+
if (!_.isEmpty(this._screenRecordingProperties)) {
|
|
232
|
+
// XXX: this doesn't need to be done in serial, does it?
|
|
233
|
+
for (const record of this._screenRecordingProperties.records || []) {
|
|
234
|
+
await /** @type {ADB} */ (this.adb).rimraf(record);
|
|
235
|
+
}
|
|
236
|
+
this._screenRecordingProperties = undefined;
|
|
237
|
+
}
|
|
270
238
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
* @throws {Error} If there was an error while getting the name of a media file
|
|
279
|
-
* or the file content cannot be uploaded to the remote location
|
|
280
|
-
* or screen recording is not supported on the device under test.
|
|
281
|
-
*/
|
|
282
|
-
commands.stopRecordingScreen = async function stopRecordingScreen (options = {}) {
|
|
283
|
-
await verifyScreenRecordIsSupported(this.adb, this.isEmulator());
|
|
239
|
+
const timeout = parseFloat(String(timeLimit));
|
|
240
|
+
if (isNaN(timeout) || timeout > MAX_TIME_SEC || timeout <= 0) {
|
|
241
|
+
throw new Error(
|
|
242
|
+
`The timeLimit value must be in range [1, ${MAX_TIME_SEC}] seconds. ` +
|
|
243
|
+
`The value of '${timeLimit}' has been passed instead.`
|
|
244
|
+
);
|
|
245
|
+
}
|
|
284
246
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
247
|
+
this._screenRecordingProperties = {
|
|
248
|
+
timer: new timing.Timer().start(),
|
|
249
|
+
videoSize,
|
|
250
|
+
timeLimit,
|
|
251
|
+
currentTimeLimit: timeLimit,
|
|
252
|
+
bitRate,
|
|
253
|
+
bugReport,
|
|
254
|
+
records: [],
|
|
255
|
+
recordingProcess: null,
|
|
256
|
+
stopped: false,
|
|
257
|
+
};
|
|
258
|
+
await scheduleScreenRecord(adb, this._screenRecordingProperties, this.log);
|
|
259
|
+
return result;
|
|
260
|
+
},
|
|
261
|
+
|
|
262
|
+
async stopRecordingScreen(options = {}) {
|
|
263
|
+
const adb = /** @type {ADB} */ (this.adb);
|
|
264
|
+
await verifyScreenRecordIsSupported(adb, this.isEmulator());
|
|
288
265
|
|
|
289
|
-
try {
|
|
290
|
-
await terminateBackgroundScreenRecording(this.adb, false);
|
|
291
|
-
} catch (err) {
|
|
292
|
-
this.log.warn(err.message);
|
|
293
266
|
if (!_.isEmpty(this._screenRecordingProperties)) {
|
|
294
|
-
this.
|
|
267
|
+
this._screenRecordingProperties.stopped = true;
|
|
295
268
|
}
|
|
296
|
-
}
|
|
297
269
|
|
|
298
|
-
if (_.isEmpty(this._screenRecordingProperties)) {
|
|
299
|
-
this.log.info(`Screen recording has not been previously started by Appium. There is nothing to stop`);
|
|
300
|
-
return '';
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
if (this._screenRecordingProperties.recordingProcess && this._screenRecordingProperties.recordingProcess.isRunning) {
|
|
304
270
|
try {
|
|
305
|
-
await
|
|
306
|
-
} catch (
|
|
307
|
-
this.log.
|
|
271
|
+
await terminateBackgroundScreenRecording(adb, false);
|
|
272
|
+
} catch (err) {
|
|
273
|
+
this.log.warn(/** @type {Error} */ (err).message);
|
|
274
|
+
if (!_.isEmpty(this._screenRecordingProperties)) {
|
|
275
|
+
this.log.warn('The resulting video might be corrupted');
|
|
276
|
+
}
|
|
308
277
|
}
|
|
309
|
-
this._screenRecordingProperties.recordingProcess = null;
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
if (_.isEmpty(this._screenRecordingProperties.records)) {
|
|
313
|
-
this.log.errorAndThrow(`No screen recordings have been stored on the device so far. ` +
|
|
314
|
-
`Are you sure the ${SCREENRECORD_BINARY} utility works as expected?`);
|
|
315
|
-
}
|
|
316
278
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
await this.adb.pull(pathOnDevice, _.last(localRecords));
|
|
323
|
-
await this.adb.rimraf(pathOnDevice);
|
|
279
|
+
if (_.isEmpty(this._screenRecordingProperties)) {
|
|
280
|
+
this.log.info(
|
|
281
|
+
`Screen recording has not been previously started by Appium. There is nothing to stop`
|
|
282
|
+
);
|
|
283
|
+
return '';
|
|
324
284
|
}
|
|
325
|
-
|
|
326
|
-
if (
|
|
327
|
-
this.
|
|
285
|
+
|
|
286
|
+
if (
|
|
287
|
+
this._screenRecordingProperties.recordingProcess &&
|
|
288
|
+
this._screenRecordingProperties.recordingProcess.isRunning
|
|
289
|
+
) {
|
|
328
290
|
try {
|
|
329
|
-
|
|
291
|
+
await this._screenRecordingProperties.recordingProcess.stop(
|
|
292
|
+
'SIGINT',
|
|
293
|
+
PROCESS_SHUTDOWN_TIMEOUT
|
|
294
|
+
);
|
|
330
295
|
} catch (e) {
|
|
331
|
-
this.log.
|
|
332
|
-
`
|
|
296
|
+
this.log.errorAndThrow(
|
|
297
|
+
`Unable to stop screen recording within ${PROCESS_SHUTDOWN_TIMEOUT}ms`
|
|
298
|
+
);
|
|
333
299
|
}
|
|
300
|
+
this._screenRecordingProperties.recordingProcess = null;
|
|
334
301
|
}
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
this.log.
|
|
302
|
+
|
|
303
|
+
if (_.isEmpty(this._screenRecordingProperties.records)) {
|
|
304
|
+
this.log.errorAndThrow(
|
|
305
|
+
`No screen recordings have been stored on the device so far. ` +
|
|
306
|
+
`Are you sure the ${SCREENRECORD_BINARY} utility works as expected?`
|
|
307
|
+
);
|
|
338
308
|
}
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
309
|
+
|
|
310
|
+
const tmpRoot = await tempDir.openDir();
|
|
311
|
+
try {
|
|
312
|
+
const localRecords = [];
|
|
313
|
+
for (const pathOnDevice of this._screenRecordingProperties.records) {
|
|
314
|
+
const relativePath = path.resolve(tmpRoot, path.posix.basename(pathOnDevice));
|
|
315
|
+
localRecords.push(relativePath);
|
|
316
|
+
await adb.pull(pathOnDevice, relativePath);
|
|
317
|
+
await adb.rimraf(pathOnDevice);
|
|
318
|
+
}
|
|
319
|
+
let resultFilePath = /** @type {string} */ (_.last(localRecords));
|
|
320
|
+
if (localRecords.length > 1) {
|
|
321
|
+
this.log.info(`Got ${localRecords.length} screen recordings. Trying to merge them`);
|
|
322
|
+
try {
|
|
323
|
+
resultFilePath = await mergeScreenRecords(localRecords, this.log);
|
|
324
|
+
} catch (e) {
|
|
325
|
+
this.log.warn(
|
|
326
|
+
`Cannot merge the recorded files. The most recent screen recording is going to be returned as the result. ` +
|
|
327
|
+
`Original error: ${/** @type {Error} */ (e).message}`
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
if (_.isEmpty(options.remotePath)) {
|
|
332
|
+
const {size} = await fs.stat(resultFilePath);
|
|
333
|
+
this.log.debug(
|
|
334
|
+
`The size of the resulting screen recording is ${util.toReadableSizeString(size)}`
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
return await uploadRecordedMedia(resultFilePath, options.remotePath, options);
|
|
338
|
+
} finally {
|
|
339
|
+
await fs.rimraf(tmpRoot);
|
|
340
|
+
this._screenRecordingProperties = undefined;
|
|
341
|
+
}
|
|
342
|
+
},
|
|
344
343
|
};
|
|
345
344
|
|
|
345
|
+
mixin(RecordScreenMixin);
|
|
346
|
+
|
|
347
|
+
export default RecordScreenMixin;
|
|
346
348
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
+
/**
|
|
350
|
+
* @typedef {import('appium-adb').ADB} ADB
|
|
351
|
+
*/
|
package/lib/commands/shell.js
CHANGED
|
@@ -1,47 +1,58 @@
|
|
|
1
|
-
|
|
2
|
-
import { exec } from 'teen_process';
|
|
3
|
-
import { util } from '@appium/support';
|
|
4
|
-
import { errors } from 'appium/driver';
|
|
5
|
-
import { ADB_SHELL_FEATURE } from '../utils';
|
|
6
|
-
|
|
7
|
-
const commands = {};
|
|
1
|
+
// @ts-check
|
|
8
2
|
|
|
9
|
-
|
|
10
|
-
|
|
3
|
+
import {util} from '@appium/support';
|
|
4
|
+
import {errors} from 'appium/driver';
|
|
5
|
+
import _ from 'lodash';
|
|
6
|
+
import {exec} from 'teen_process';
|
|
7
|
+
import {ADB_SHELL_FEATURE} from '../utils';
|
|
8
|
+
import {mixin} from './mixins';
|
|
11
9
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
10
|
+
/**
|
|
11
|
+
* @type {import('./mixins').ShellMixin & ThisType<import('../driver').AndroidDriver>}
|
|
12
|
+
* @satisfies {import('@appium/types').ExternalDriver}
|
|
13
|
+
*/
|
|
14
|
+
const ShellMixin = {
|
|
15
|
+
async mobileShell(opts) {
|
|
16
|
+
this.ensureFeatureEnabled(ADB_SHELL_FEATURE);
|
|
17
|
+
const adb = /** @type {ADB} */ (this.adb);
|
|
18
|
+
const {
|
|
19
|
+
command,
|
|
20
|
+
args = /** @type {string[]} */ ([]),
|
|
21
|
+
timeout = 20000,
|
|
22
|
+
includeStderr,
|
|
23
|
+
} = opts ?? {};
|
|
18
24
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
25
|
+
if (!_.isString(command)) {
|
|
26
|
+
throw new errors.InvalidArgumentError(`The 'command' argument is mandatory`);
|
|
27
|
+
}
|
|
22
28
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
return
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
29
|
+
const adbArgs = [...adb.executable.defaultArgs, 'shell', command, ..._.castArray(args)];
|
|
30
|
+
this.log.debug(`Running '${adb.executable.path} ${util.quote(adbArgs)}'`);
|
|
31
|
+
try {
|
|
32
|
+
const {stdout, stderr} = await exec(adb.executable.path, adbArgs, {timeout});
|
|
33
|
+
if (includeStderr) {
|
|
34
|
+
return {
|
|
35
|
+
stdout,
|
|
36
|
+
stderr,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
return stdout;
|
|
40
|
+
} catch (e) {
|
|
41
|
+
const err = /** @type {import('teen_process').ExecError} */ (e);
|
|
42
|
+
this.log.errorAndThrow(
|
|
43
|
+
`Cannot execute the '${command}' shell command. ` +
|
|
44
|
+
`Original error: ${err.message}. ` +
|
|
45
|
+
`StdOut: ${err.stdout}. StdErr: ${err.stderr}`
|
|
46
|
+
);
|
|
47
|
+
throw new Error(); // unreachable; for TS
|
|
37
48
|
}
|
|
38
|
-
|
|
39
|
-
} catch (err) {
|
|
40
|
-
this.log.errorAndThrow(`Cannot execute the '${command}' shell command. ` +
|
|
41
|
-
`Original error: ${err.message}. ` +
|
|
42
|
-
`StdOut: ${err.stdout}. StdErr: ${err.stderr}`);
|
|
43
|
-
}
|
|
49
|
+
},
|
|
44
50
|
};
|
|
45
51
|
|
|
46
|
-
|
|
47
|
-
|
|
52
|
+
mixin(ShellMixin);
|
|
53
|
+
|
|
54
|
+
export default ShellMixin;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* @typedef {import('appium-adb').ADB} ADB
|
|
58
|
+
*/
|