appium-xcuitest-driver 10.2.2 → 10.4.0
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/active-app-info.d.ts +9 -0
- package/build/lib/commands/active-app-info.d.ts.map +1 -0
- package/build/lib/commands/active-app-info.js +14 -0
- package/build/lib/commands/active-app-info.js.map +1 -0
- package/build/lib/commands/alert.d.ts +42 -45
- package/build/lib/commands/alert.d.ts.map +1 -1
- package/build/lib/commands/alert.js +66 -62
- package/build/lib/commands/alert.js.map +1 -1
- package/build/lib/commands/app-management.d.ts +150 -153
- package/build/lib/commands/app-management.d.ts.map +1 -1
- package/build/lib/commands/app-management.js +300 -286
- package/build/lib/commands/app-management.js.map +1 -1
- package/build/lib/commands/app-strings.d.ts +14 -17
- package/build/lib/commands/app-strings.d.ts.map +1 -1
- package/build/lib/commands/app-strings.js +23 -24
- package/build/lib/commands/app-strings.js.map +1 -1
- package/build/lib/commands/appearance.d.ts +19 -22
- package/build/lib/commands/appearance.d.ts.map +1 -1
- package/build/lib/commands/appearance.js +56 -56
- package/build/lib/commands/appearance.js.map +1 -1
- package/build/lib/commands/audit.d.ts +22 -17
- package/build/lib/commands/audit.d.ts.map +1 -1
- package/build/lib/commands/audit.js +17 -18
- package/build/lib/commands/audit.js.map +1 -1
- package/build/lib/commands/battery.d.ts +11 -14
- package/build/lib/commands/battery.d.ts.map +1 -1
- package/build/lib/commands/battery.js +36 -37
- package/build/lib/commands/battery.js.map +1 -1
- package/build/lib/commands/biometric.d.ts +30 -33
- package/build/lib/commands/biometric.d.ts.map +1 -1
- package/build/lib/commands/biometric.js +42 -41
- package/build/lib/commands/biometric.js.map +1 -1
- package/build/lib/commands/certificate.d.ts +48 -45
- package/build/lib/commands/certificate.d.ts.map +1 -1
- package/build/lib/commands/certificate.js +218 -205
- package/build/lib/commands/certificate.js.map +1 -1
- package/build/lib/commands/clipboard.d.ts +19 -22
- package/build/lib/commands/clipboard.d.ts.map +1 -1
- package/build/lib/commands/clipboard.js +30 -30
- package/build/lib/commands/clipboard.js.map +1 -1
- package/build/lib/commands/condition.d.ts +49 -26
- package/build/lib/commands/condition.d.ts.map +1 -1
- package/build/lib/commands/condition.js +87 -86
- package/build/lib/commands/condition.js.map +1 -1
- package/build/lib/commands/content-size.d.ts +26 -29
- package/build/lib/commands/content-size.d.ts.map +1 -1
- package/build/lib/commands/content-size.js +36 -36
- package/build/lib/commands/content-size.js.map +1 -1
- package/build/lib/commands/context.d.ts +161 -108
- package/build/lib/commands/context.d.ts.map +1 -1
- package/build/lib/commands/context.js +530 -517
- package/build/lib/commands/context.js.map +1 -1
- package/build/lib/commands/deviceInfo.d.ts +9 -12
- package/build/lib/commands/deviceInfo.d.ts.map +1 -1
- package/build/lib/commands/deviceInfo.js +17 -18
- package/build/lib/commands/deviceInfo.js.map +1 -1
- package/build/lib/commands/element.d.ts +102 -105
- package/build/lib/commands/element.d.ts.map +1 -1
- package/build/lib/commands/element.js +337 -323
- package/build/lib/commands/element.js.map +1 -1
- package/build/lib/commands/execute.d.ts +24 -19
- package/build/lib/commands/execute.d.ts.map +1 -1
- package/build/lib/commands/execute.js +63 -62
- package/build/lib/commands/execute.js.map +1 -1
- package/build/lib/commands/file-movement.d.ts +77 -80
- package/build/lib/commands/file-movement.d.ts.map +1 -1
- package/build/lib/commands/file-movement.js +130 -124
- package/build/lib/commands/file-movement.js.map +1 -1
- package/build/lib/commands/find.d.ts +18 -21
- package/build/lib/commands/find.d.ts.map +1 -1
- package/build/lib/commands/find.js +158 -156
- package/build/lib/commands/find.js.map +1 -1
- package/build/lib/commands/general.d.ts +124 -116
- package/build/lib/commands/general.d.ts.map +1 -1
- package/build/lib/commands/general.js +248 -232
- package/build/lib/commands/general.js.map +1 -1
- package/build/lib/commands/geolocation.d.ts +43 -46
- package/build/lib/commands/geolocation.d.ts.map +1 -1
- package/build/lib/commands/geolocation.js +10 -11
- package/build/lib/commands/geolocation.js.map +1 -1
- package/build/lib/commands/gesture.d.ts +273 -276
- package/build/lib/commands/gesture.d.ts.map +1 -1
- package/build/lib/commands/gesture.js +506 -492
- package/build/lib/commands/gesture.js.map +1 -1
- package/build/lib/commands/increase-contrast.d.ts +20 -23
- package/build/lib/commands/increase-contrast.d.ts.map +1 -1
- package/build/lib/commands/increase-contrast.js +30 -30
- package/build/lib/commands/increase-contrast.js.map +1 -1
- package/build/lib/commands/iohid.d.ts +1370 -1373
- package/build/lib/commands/iohid.d.ts.map +1 -1
- package/build/lib/commands/iohid.js +30 -31
- package/build/lib/commands/iohid.js.map +1 -1
- package/build/lib/commands/keyboard.d.ts +29 -32
- package/build/lib/commands/keyboard.d.ts.map +1 -1
- package/build/lib/commands/keyboard.js +53 -51
- package/build/lib/commands/keyboard.js.map +1 -1
- package/build/lib/commands/keychains.d.ts +9 -12
- package/build/lib/commands/keychains.d.ts.map +1 -1
- package/build/lib/commands/keychains.js +13 -14
- package/build/lib/commands/keychains.js.map +1 -1
- package/build/lib/commands/localization.d.ts +16 -19
- package/build/lib/commands/localization.d.ts.map +1 -1
- package/build/lib/commands/localization.js +25 -26
- package/build/lib/commands/localization.js.map +1 -1
- package/build/lib/commands/location.d.ts +36 -39
- package/build/lib/commands/location.d.ts.map +1 -1
- package/build/lib/commands/location.js +99 -98
- package/build/lib/commands/location.js.map +1 -1
- package/build/lib/commands/lock.d.ts +21 -24
- package/build/lib/commands/lock.d.ts.map +1 -1
- package/build/lib/commands/lock.js +39 -38
- package/build/lib/commands/lock.js.map +1 -1
- package/build/lib/commands/log.d.ts +43 -37
- package/build/lib/commands/log.d.ts.map +1 -1
- package/build/lib/commands/log.js +174 -171
- package/build/lib/commands/log.js.map +1 -1
- package/build/lib/commands/memory.d.ts +9 -12
- package/build/lib/commands/memory.d.ts.map +1 -1
- package/build/lib/commands/memory.js +37 -39
- package/build/lib/commands/memory.js.map +1 -1
- package/build/lib/commands/navigation.d.ts +30 -33
- package/build/lib/commands/navigation.d.ts.map +1 -1
- package/build/lib/commands/navigation.js +92 -92
- package/build/lib/commands/navigation.js.map +1 -1
- package/build/lib/commands/notifications.d.ts +26 -29
- package/build/lib/commands/notifications.d.ts.map +1 -1
- package/build/lib/commands/notifications.js +53 -53
- package/build/lib/commands/notifications.js.map +1 -1
- package/build/lib/commands/pasteboard.d.ts +21 -24
- package/build/lib/commands/pasteboard.d.ts.map +1 -1
- package/build/lib/commands/pasteboard.js +37 -37
- package/build/lib/commands/pasteboard.js.map +1 -1
- package/build/lib/commands/pcap.d.ts +39 -26
- package/build/lib/commands/pcap.d.ts.map +1 -1
- package/build/lib/commands/pcap.js +81 -81
- package/build/lib/commands/pcap.js.map +1 -1
- package/build/lib/commands/performance.d.ts +63 -44
- package/build/lib/commands/performance.d.ts.map +1 -1
- package/build/lib/commands/performance.js +105 -105
- package/build/lib/commands/performance.js.map +1 -1
- package/build/lib/commands/permissions.d.ts +33 -36
- package/build/lib/commands/permissions.d.ts.map +1 -1
- package/build/lib/commands/permissions.js +66 -65
- package/build/lib/commands/permissions.js.map +1 -1
- package/build/lib/commands/proxy-helper.d.ts +12 -15
- package/build/lib/commands/proxy-helper.d.ts.map +1 -1
- package/build/lib/commands/proxy-helper.js +53 -54
- package/build/lib/commands/proxy-helper.js.map +1 -1
- package/build/lib/commands/record-audio.d.ts +49 -29
- package/build/lib/commands/record-audio.d.ts.map +1 -1
- package/build/lib/commands/record-audio.js +100 -104
- package/build/lib/commands/record-audio.js.map +1 -1
- package/build/lib/commands/recordscreen.d.ts +54 -18
- package/build/lib/commands/recordscreen.d.ts.map +1 -1
- package/build/lib/commands/recordscreen.js +127 -129
- package/build/lib/commands/recordscreen.js.map +1 -1
- package/build/lib/commands/screenshots.d.ts +14 -17
- package/build/lib/commands/screenshots.d.ts.map +1 -1
- package/build/lib/commands/screenshots.js +108 -107
- package/build/lib/commands/screenshots.js.map +1 -1
- package/build/lib/commands/simctl.d.ts +11 -14
- package/build/lib/commands/simctl.d.ts.map +1 -1
- package/build/lib/commands/simctl.js +23 -26
- package/build/lib/commands/simctl.js.map +1 -1
- package/build/lib/commands/source.d.ts +14 -17
- package/build/lib/commands/source.d.ts.map +1 -1
- package/build/lib/commands/source.js +40 -43
- package/build/lib/commands/source.js.map +1 -1
- package/build/lib/commands/timeouts.d.ts +44 -33
- package/build/lib/commands/timeouts.d.ts.map +1 -1
- package/build/lib/commands/timeouts.js +65 -63
- package/build/lib/commands/timeouts.js.map +1 -1
- package/build/lib/commands/web.d.ts +247 -197
- package/build/lib/commands/web.d.ts.map +1 -1
- package/build/lib/commands/web.js +815 -786
- package/build/lib/commands/web.js.map +1 -1
- package/build/lib/commands/xctest-record-screen.d.ts +63 -66
- package/build/lib/commands/xctest-record-screen.d.ts.map +1 -1
- package/build/lib/commands/xctest-record-screen.js +103 -102
- package/build/lib/commands/xctest-record-screen.js.map +1 -1
- package/build/lib/commands/xctest.d.ts +55 -51
- package/build/lib/commands/xctest.d.ts.map +1 -1
- package/build/lib/commands/xctest.js +116 -117
- package/build/lib/commands/xctest.js.map +1 -1
- package/build/lib/driver.d.ts +278 -1597
- package/build/lib/driver.d.ts.map +1 -1
- package/build/lib/driver.js +320 -236
- package/build/lib/driver.js.map +1 -1
- package/build/lib/execute-method-map.d.ts.map +1 -1
- package/build/lib/execute-method-map.js +9 -0
- package/build/lib/execute-method-map.js.map +1 -1
- package/build/lib/real-device.d.ts +1 -1
- package/build/lib/real-device.d.ts.map +1 -1
- package/build/lib/real-device.js +2 -2
- package/build/lib/real-device.js.map +1 -1
- package/lib/commands/active-app-info.js +12 -0
- package/lib/commands/alert.js +68 -65
- package/lib/commands/app-management.js +308 -301
- package/lib/commands/app-strings.js +24 -26
- package/lib/commands/appearance.js +54 -56
- package/lib/commands/audit.js +18 -20
- package/lib/commands/battery.js +35 -37
- package/lib/commands/biometric.js +44 -46
- package/lib/commands/certificate.js +226 -215
- package/lib/commands/clipboard.js +30 -32
- package/lib/commands/condition.js +98 -100
- package/lib/commands/content-size.js +36 -38
- package/lib/commands/context.js +495 -490
- package/lib/commands/deviceInfo.js +19 -20
- package/lib/commands/element.js +367 -357
- package/lib/commands/execute.js +72 -72
- package/lib/commands/file-movement.js +132 -134
- package/lib/commands/find.js +160 -159
- package/lib/commands/general.js +238 -231
- package/lib/commands/geolocation.js +6 -14
- package/lib/commands/gesture.js +525 -515
- package/lib/commands/increase-contrast.js +30 -32
- package/lib/commands/iohid.js +32 -34
- package/lib/commands/keyboard.js +49 -51
- package/lib/commands/keychains.js +12 -14
- package/lib/commands/localization.js +24 -26
- package/lib/commands/location.js +102 -104
- package/lib/commands/lock.js +38 -38
- package/lib/commands/log.js +197 -198
- package/lib/commands/memory.js +40 -43
- package/lib/commands/navigation.js +96 -100
- package/lib/commands/notifications.js +57 -59
- package/lib/commands/pasteboard.js +37 -39
- package/lib/commands/pcap.js +84 -86
- package/lib/commands/performance.js +132 -133
- package/lib/commands/permissions.js +67 -69
- package/lib/commands/proxy-helper.js +60 -61
- package/lib/commands/record-audio.js +115 -120
- package/lib/commands/recordscreen.js +145 -149
- package/lib/commands/screenshots.js +116 -116
- package/lib/commands/simctl.js +25 -29
- package/lib/commands/source.js +42 -46
- package/lib/commands/timeouts.js +59 -63
- package/lib/commands/web.js +878 -858
- package/lib/commands/xctest-record-screen.js +103 -105
- package/lib/commands/xctest.js +134 -139
- package/lib/driver.js +288 -236
- package/lib/execute-method-map.ts +9 -0
- package/lib/real-device.js +2 -2
- package/npm-shrinkwrap.json +440 -76
- package/package.json +2 -1
- package/build/lib/commands/activeAppInfo.d.ts +0 -12
- package/build/lib/commands/activeAppInfo.d.ts.map +0 -1
- package/build/lib/commands/activeAppInfo.js +0 -15
- package/build/lib/commands/activeAppInfo.js.map +0 -1
- package/build/lib/commands/index.d.ts +0 -96
- package/build/lib/commands/index.d.ts.map +0 -1
- package/build/lib/commands/index.js +0 -100
- package/build/lib/commands/index.js.map +0 -1
- package/build/lib/real-device-clients/devicectl.d.ts +0 -204
- package/build/lib/real-device-clients/devicectl.d.ts.map +0 -1
- package/build/lib/real-device-clients/devicectl.js +0 -264
- package/build/lib/real-device-clients/devicectl.js.map +0 -1
- package/lib/commands/activeAppInfo.js +0 -14
- package/lib/commands/index.js +0 -95
- package/lib/real-device-clients/devicectl.js +0 -291
|
@@ -11,6 +11,7 @@ const PROCESS_STARTUP_TIMEOUT_MS = 5000;
|
|
|
11
11
|
const DEFAULT_EXT = '.mp4';
|
|
12
12
|
const FFMPEG_BINARY = 'ffmpeg';
|
|
13
13
|
const ffmpegLogger = logger.getLogger(FFMPEG_BINARY);
|
|
14
|
+
|
|
14
15
|
export class AudioRecorder {
|
|
15
16
|
constructor(input, log, audioPath, opts = {}) {
|
|
16
17
|
this.input = input;
|
|
@@ -134,135 +135,129 @@ export class AudioRecorder {
|
|
|
134
135
|
}
|
|
135
136
|
}
|
|
136
137
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
`The mandatory audioInput option is not provided. Please set it ` +
|
|
178
|
-
`to a correct value (e. g. ':1'). Use 'ffmpeg -f avfoundation -list_devices true -i ""' ` +
|
|
179
|
-
`command to list available input sources`,
|
|
180
|
-
);
|
|
181
|
-
}
|
|
138
|
+
/**
|
|
139
|
+
* Records the given hardware audio input and saves it into an `.mp4` file.
|
|
140
|
+
*
|
|
141
|
+
* **To use this command, the `audio_record` security feature must be enabled _and_ [FFMpeg](https://ffmpeg.org/) must be installed on the Appium server.**
|
|
142
|
+
*
|
|
143
|
+
* @param {string|number} audioInput - The name of the corresponding audio input device to use for the capture. The full list of capture devices could be shown by executing `ffmpeg -f avfoundation -list_devices true -i ""`
|
|
144
|
+
* @param {string|number} timeLimit - The maximum recording time, in seconds.
|
|
145
|
+
* @param {string} audioCodec - The name of the audio codec.
|
|
146
|
+
* @param {string} audioBitrate - The bitrate of the resulting audio stream.
|
|
147
|
+
* @param {string|number} audioChannels - The count of audio channels in the resulting stream. Setting it to `1` will create a single channel (mono) audio stream.
|
|
148
|
+
* @param {string|number} audioRate - The sampling rate of the resulting audio stream (in Hz).
|
|
149
|
+
* @param {boolean} forceRestart - Whether to restart audio capture process forcefully when `mobile: startRecordingAudio` is called (`true`) or ignore the call until the current audio recording is completed (`false`).
|
|
150
|
+
* @group Real Device Only
|
|
151
|
+
* @this {XCUITestDriver}
|
|
152
|
+
* @returns {Promise<void>}
|
|
153
|
+
* @privateRemarks Using string literals for the default parameters makes better documentation.
|
|
154
|
+
*/
|
|
155
|
+
export async function startAudioRecording(
|
|
156
|
+
audioInput,
|
|
157
|
+
timeLimit = 180,
|
|
158
|
+
audioCodec = 'aac',
|
|
159
|
+
audioBitrate = '128k',
|
|
160
|
+
audioChannels = 2,
|
|
161
|
+
audioRate = 44100,
|
|
162
|
+
forceRestart = false,
|
|
163
|
+
) {
|
|
164
|
+
if (!this.isFeatureEnabled(AUDIO_RECORD_FEAT_NAME)) {
|
|
165
|
+
throw this.log.errorWithException(
|
|
166
|
+
`Audio capture feature must be enabled on the server side. ` +
|
|
167
|
+
`Please set '--relaxed-security' or '--allow-insecure' with '${AUDIO_RECORD_FEAT_NAME}' option. ` +
|
|
168
|
+
`Read https://github.com/appium/appium/blob/master/docs/en/writing-running-appium/security.md for more details.`,
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
if (!audioInput) {
|
|
172
|
+
throw this.log.errorWithException(
|
|
173
|
+
`The mandatory audioInput option is not provided. Please set it ` +
|
|
174
|
+
`to a correct value (e. g. ':1'). Use 'ffmpeg -f avfoundation -list_devices true -i ""' ` +
|
|
175
|
+
`command to list available input sources`,
|
|
176
|
+
);
|
|
177
|
+
}
|
|
182
178
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
if (this._audioRecorder) {
|
|
197
|
-
await this._audioRecorder.cleanup();
|
|
198
|
-
this._audioRecorder = null;
|
|
179
|
+
if (this._audioRecorder?.isRecording()) {
|
|
180
|
+
this.log.info(`There is an active audio recording process`);
|
|
181
|
+
if (forceRestart) {
|
|
182
|
+
this.log.info(`Stopping it because 'forceRestart' option is set to true`);
|
|
183
|
+
await this._audioRecorder.interrupt(true);
|
|
184
|
+
} else {
|
|
185
|
+
this.log.info(
|
|
186
|
+
`Doing nothing. ` +
|
|
187
|
+
`Set 'forceRestart' option to true if you'd like to start a new audio recording session`,
|
|
188
|
+
);
|
|
189
|
+
return;
|
|
199
190
|
}
|
|
191
|
+
}
|
|
192
|
+
if (this._audioRecorder) {
|
|
193
|
+
await this._audioRecorder.cleanup();
|
|
194
|
+
this._audioRecorder = null;
|
|
195
|
+
}
|
|
200
196
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
197
|
+
const audioPath = await tempDir.path({
|
|
198
|
+
prefix: `appium_${util.uuidV4().substring(0, 8)}`,
|
|
199
|
+
suffix: DEFAULT_EXT,
|
|
200
|
+
});
|
|
205
201
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
202
|
+
const audioRecorder = new AudioRecorder(audioInput, this.log, audioPath, {
|
|
203
|
+
audioSource: DEFAULT_SOURCE,
|
|
204
|
+
audioCodec,
|
|
205
|
+
audioBitrate,
|
|
206
|
+
audioChannels,
|
|
207
|
+
audioRate,
|
|
208
|
+
});
|
|
213
209
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
210
|
+
const timeoutSeconds = parseInt(String(timeLimit), 10);
|
|
211
|
+
if (isNaN(timeoutSeconds) || timeoutSeconds > MAX_RECORDING_TIME_SEC || timeoutSeconds <= 0) {
|
|
212
|
+
throw this.log.errorWithException(
|
|
213
|
+
`The timeLimit value must be in range [1, ${MAX_RECORDING_TIME_SEC}] seconds. ` +
|
|
214
|
+
`The value of '${timeLimit}' has been passed instead.`,
|
|
215
|
+
);
|
|
216
|
+
}
|
|
221
217
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
218
|
+
try {
|
|
219
|
+
await audioRecorder.start(timeoutSeconds);
|
|
220
|
+
} catch (e) {
|
|
221
|
+
await audioRecorder.interrupt(true);
|
|
222
|
+
await audioRecorder.cleanup();
|
|
223
|
+
throw e;
|
|
224
|
+
}
|
|
225
|
+
this._audioRecorder = audioRecorder;
|
|
226
|
+
}
|
|
231
227
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
228
|
+
/**
|
|
229
|
+
* Stop recording of the audio input. If no audio recording process is running then
|
|
230
|
+
* the endpoint will try to get the recently recorded file.
|
|
231
|
+
* If no previously recorded file is found and no active audio recording
|
|
232
|
+
* processes are running then the method returns an empty string.
|
|
233
|
+
*
|
|
234
|
+
* @returns {Promise<string>} Base64-encoded content of the recorded media file or an
|
|
235
|
+
* empty string if no audio recording has been started before.
|
|
236
|
+
* @throws {Error} If there was an error while getting the recorded file.
|
|
237
|
+
* @this {XCUITestDriver}
|
|
238
|
+
*/
|
|
239
|
+
export async function stopAudioRecording() {
|
|
240
|
+
if (!this._audioRecorder) {
|
|
241
|
+
this.log.info('Audio recording has not been started. There is nothing to stop');
|
|
242
|
+
return '';
|
|
243
|
+
}
|
|
248
244
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
}
|
|
257
|
-
} catch (e) {
|
|
258
|
-
await this._audioRecorder.interrupt(true);
|
|
259
|
-
await this._audioRecorder.cleanup();
|
|
260
|
-
this._audioRecorder = null;
|
|
261
|
-
throw e;
|
|
245
|
+
let resultPath;
|
|
246
|
+
try {
|
|
247
|
+
resultPath = await this._audioRecorder.finish();
|
|
248
|
+
if (!(await fs.exists(resultPath))) {
|
|
249
|
+
throw this.log.errorWithException(
|
|
250
|
+
`${FFMPEG_BINARY} has failed ` + `to store the actual audio recording at '${resultPath}'`,
|
|
251
|
+
);
|
|
262
252
|
}
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
253
|
+
} catch (e) {
|
|
254
|
+
await this._audioRecorder.interrupt(true);
|
|
255
|
+
await this._audioRecorder.cleanup();
|
|
256
|
+
this._audioRecorder = null;
|
|
257
|
+
throw e;
|
|
258
|
+
}
|
|
259
|
+
return await encodeBase64OrUpload(resultPath);
|
|
260
|
+
}
|
|
266
261
|
|
|
267
262
|
/**
|
|
268
263
|
* @typedef {import('../driver').XCUITestDriver} XCUITestDriver
|
|
@@ -226,169 +226,165 @@ export class ScreenRecorder {
|
|
|
226
226
|
}
|
|
227
227
|
}
|
|
228
228
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
pixelFormat,
|
|
257
|
-
hardwareAcceleration
|
|
258
|
-
} = options;
|
|
229
|
+
/**
|
|
230
|
+
* Direct Appium to start recording the device screen
|
|
231
|
+
*
|
|
232
|
+
* Record the display of devices running iOS Simulator since Xcode 9 or real devices since iOS 11
|
|
233
|
+
* (ffmpeg utility is required: 'brew install ffmpeg').
|
|
234
|
+
* It records screen activity to a MPEG-4 file. Audio is not recorded with the video file.
|
|
235
|
+
* If screen recording has been already started then the command will stop it forcefully and start a new one.
|
|
236
|
+
* The previously recorded video file will be deleted.
|
|
237
|
+
*
|
|
238
|
+
* @param {import('./types').StartRecordingScreenOptions} [options] - The available options.
|
|
239
|
+
* @returns {Promise<string>} Base64-encoded content of the recorded media file if
|
|
240
|
+
* any screen recording is currently running or an empty string.
|
|
241
|
+
* @throws {Error} If screen recording has failed to start.
|
|
242
|
+
* @this {XCUITestDriver}
|
|
243
|
+
*/
|
|
244
|
+
export async function startRecordingScreen(options = {}) {
|
|
245
|
+
const {
|
|
246
|
+
videoType = DEFAULT_VCODEC,
|
|
247
|
+
timeLimit = DEFAULT_RECORDING_TIME_SEC,
|
|
248
|
+
videoQuality = DEFAULT_QUALITY,
|
|
249
|
+
videoFps = DEFAULT_FPS,
|
|
250
|
+
videoFilters,
|
|
251
|
+
videoScale,
|
|
252
|
+
forceRestart,
|
|
253
|
+
pixelFormat,
|
|
254
|
+
hardwareAcceleration
|
|
255
|
+
} = options;
|
|
259
256
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
257
|
+
let result = '';
|
|
258
|
+
if (!forceRestart) {
|
|
259
|
+
this.log.info(
|
|
260
|
+
`Checking if there is/was a previous screen recording. ` +
|
|
261
|
+
`Set 'forceRestart' option to 'true' if you'd like to skip this step.`
|
|
262
|
+
);
|
|
263
|
+
result = (await this.stopRecordingScreen(options)) ?? result;
|
|
264
|
+
}
|
|
268
265
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
266
|
+
const videoPath = await tempDir.path({
|
|
267
|
+
prefix: `appium_${Math.random().toString(16).substring(2, 8)}`,
|
|
268
|
+
suffix: MP4_EXT
|
|
269
|
+
});
|
|
273
270
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
271
|
+
const wdaBaseUrl = this.opts.wdaBaseUrl || WDA_BASE_URL;
|
|
272
|
+
const screenRecorder = new ScreenRecorder(this.device.udid, this.log, videoPath, {
|
|
273
|
+
remotePort: this.opts.mjpegServerPort || DEFAULT_MJPEG_SERVER_PORT,
|
|
274
|
+
remoteUrl: wdaBaseUrl,
|
|
275
|
+
videoType,
|
|
276
|
+
videoFilters,
|
|
277
|
+
videoScale,
|
|
278
|
+
videoFps,
|
|
279
|
+
pixelFormat,
|
|
280
|
+
hardwareAcceleration
|
|
281
|
+
});
|
|
282
|
+
if (!(await screenRecorder.interrupt(true))) {
|
|
283
|
+
throw this.log.errorWithException('Unable to stop screen recording process');
|
|
284
|
+
}
|
|
285
|
+
if (this._recentScreenRecorder) {
|
|
286
|
+
await this._recentScreenRecorder.cleanup();
|
|
287
|
+
this._recentScreenRecorder = null;
|
|
288
|
+
}
|
|
292
289
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
290
|
+
const timeoutSeconds = parseFloat(String(timeLimit));
|
|
291
|
+
if (isNaN(timeoutSeconds) || timeoutSeconds > MAX_RECORDING_TIME_SEC || timeoutSeconds <= 0) {
|
|
292
|
+
throw this.log.errorWithException(
|
|
293
|
+
`The timeLimit value must be in range [1, ${MAX_RECORDING_TIME_SEC}] seconds. ` +
|
|
294
|
+
`The value of '${timeLimit}' has been passed instead.`,
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
let {mjpegServerScreenshotQuality, mjpegServerFramerate} =
|
|
299
|
+
/** @type {import('appium-webdriveragent').WDASettings} */ (
|
|
300
|
+
await this.proxyCommand('/appium/settings', 'GET')
|
|
301
|
+
);
|
|
302
|
+
if (videoQuality) {
|
|
303
|
+
const quality = _.isInteger(videoQuality)
|
|
304
|
+
? videoQuality
|
|
305
|
+
: QUALITY_MAPPING[_.toLower(String(videoQuality))];
|
|
306
|
+
if (!quality) {
|
|
307
|
+
throw new Error(
|
|
308
|
+
`videoQuality value should be one of ${JSON.stringify(
|
|
309
|
+
_.keys(QUALITY_MAPPING)
|
|
310
|
+
)} or a number in range 1..100. ` + `'${videoQuality}' is given instead`
|
|
298
311
|
);
|
|
299
312
|
}
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
`videoQuality value should be one of ${JSON.stringify(
|
|
312
|
-
_.keys(QUALITY_MAPPING)
|
|
313
|
-
)} or a number in range 1..100. ` + `'${videoQuality}' is given instead`
|
|
314
|
-
);
|
|
315
|
-
}
|
|
316
|
-
mjpegServerScreenshotQuality = mjpegServerScreenshotQuality !== quality ? quality : undefined;
|
|
317
|
-
} else {
|
|
318
|
-
mjpegServerScreenshotQuality = undefined;
|
|
313
|
+
mjpegServerScreenshotQuality = mjpegServerScreenshotQuality !== quality ? quality : undefined;
|
|
314
|
+
} else {
|
|
315
|
+
mjpegServerScreenshotQuality = undefined;
|
|
316
|
+
}
|
|
317
|
+
if (videoFps) {
|
|
318
|
+
const fps = parseInt(String(videoFps), 10);
|
|
319
|
+
if (isNaN(fps)) {
|
|
320
|
+
throw new Error(
|
|
321
|
+
`videoFps value should be a valid number in range 1..60. ` +
|
|
322
|
+
`'${videoFps}' is given instead`
|
|
323
|
+
);
|
|
319
324
|
}
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
325
|
+
mjpegServerFramerate = mjpegServerFramerate !== fps ? fps : undefined;
|
|
326
|
+
} else {
|
|
327
|
+
mjpegServerFramerate = undefined;
|
|
328
|
+
}
|
|
329
|
+
if (util.hasValue(mjpegServerScreenshotQuality) || util.hasValue(mjpegServerFramerate)) {
|
|
330
|
+
await this.proxyCommand('/appium/settings', 'POST', {
|
|
331
|
+
settings: {
|
|
332
|
+
mjpegServerScreenshotQuality,
|
|
333
|
+
mjpegServerFramerate
|
|
327
334
|
}
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
mjpegServerFramerate = undefined;
|
|
331
|
-
}
|
|
332
|
-
if (util.hasValue(mjpegServerScreenshotQuality) || util.hasValue(mjpegServerFramerate)) {
|
|
333
|
-
await this.proxyCommand('/appium/settings', 'POST', {
|
|
334
|
-
settings: {
|
|
335
|
-
mjpegServerScreenshotQuality,
|
|
336
|
-
mjpegServerFramerate
|
|
337
|
-
}
|
|
338
|
-
});
|
|
339
|
-
}
|
|
335
|
+
});
|
|
336
|
+
}
|
|
340
337
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
338
|
+
try {
|
|
339
|
+
await screenRecorder.start(timeoutSeconds * 1000);
|
|
340
|
+
} catch (e) {
|
|
341
|
+
await screenRecorder.interrupt(true);
|
|
342
|
+
await screenRecorder.cleanup();
|
|
343
|
+
throw e;
|
|
344
|
+
}
|
|
345
|
+
this._recentScreenRecorder = screenRecorder;
|
|
349
346
|
|
|
350
|
-
|
|
351
|
-
|
|
347
|
+
return result;
|
|
348
|
+
}
|
|
352
349
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
350
|
+
/**
|
|
351
|
+
* Direct Appium to stop screen recording and return the video
|
|
352
|
+
*
|
|
353
|
+
* If no screen recording process is running then the endpoint will try to get
|
|
354
|
+
* the recently recorded file. If no previously recorded file is found and no
|
|
355
|
+
* active screen recording processes are running then the method returns an
|
|
356
|
+
* empty string.
|
|
357
|
+
*
|
|
358
|
+
* @param {import('./types').StopRecordingScreenOptions} options - The available
|
|
359
|
+
* options.
|
|
360
|
+
* @returns {Promise<string?>} Base64-encoded content of the recorded media
|
|
361
|
+
* file if `remotePath` parameter is empty or null or an empty string.
|
|
362
|
+
* @throws {Error} If there was an error while getting the name of a media
|
|
363
|
+
* file or the file content cannot be uploaded to the remote
|
|
364
|
+
* location.
|
|
365
|
+
* @this {XCUITestDriver}
|
|
366
|
+
*/
|
|
367
|
+
export async function stopRecordingScreen(options = {}) {
|
|
368
|
+
if (!this._recentScreenRecorder) {
|
|
369
|
+
this.log.info('Screen recording is not running. There is nothing to stop.');
|
|
370
|
+
return '';
|
|
371
|
+
}
|
|
375
372
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
}
|
|
384
|
-
return await encodeBase64OrUpload(videoPath, options.remotePath, options);
|
|
385
|
-
} finally {
|
|
386
|
-
await this._recentScreenRecorder.interrupt(true);
|
|
387
|
-
await this._recentScreenRecorder.cleanup();
|
|
388
|
-
this._recentScreenRecorder = null;
|
|
373
|
+
try {
|
|
374
|
+
const videoPath = await this._recentScreenRecorder.finish();
|
|
375
|
+
if (!(await fs.exists(videoPath))) {
|
|
376
|
+
throw this.log.errorWithException(
|
|
377
|
+
`The screen recorder utility has failed ` +
|
|
378
|
+
`to store the actual screen recording at '${videoPath}'`
|
|
379
|
+
);
|
|
389
380
|
}
|
|
381
|
+
return await encodeBase64OrUpload(videoPath, options.remotePath, options);
|
|
382
|
+
} finally {
|
|
383
|
+
await this._recentScreenRecorder.interrupt(true);
|
|
384
|
+
await this._recentScreenRecorder.cleanup();
|
|
385
|
+
this._recentScreenRecorder = null;
|
|
390
386
|
}
|
|
391
|
-
}
|
|
387
|
+
}
|
|
392
388
|
|
|
393
389
|
/**
|
|
394
390
|
* @typedef {import('../driver').XCUITestDriver} XCUITestDriver
|