@limrun/appium-xcuitest-driver 10.11.0-lim.4 → 10.14.6-lim.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 +94 -0
- package/build/lib/commands/active-app-info.d.ts +4 -3
- package/build/lib/commands/active-app-info.d.ts.map +1 -1
- package/build/lib/commands/active-app-info.js +2 -3
- package/build/lib/commands/active-app-info.js.map +1 -1
- package/build/lib/commands/alert.d.ts +26 -31
- package/build/lib/commands/alert.d.ts.map +1 -1
- package/build/lib/commands/alert.js +20 -29
- package/build/lib/commands/alert.js.map +1 -1
- package/build/lib/commands/app-management.d.ts +99 -76
- package/build/lib/commands/app-management.d.ts.map +1 -1
- package/build/lib/commands/app-management.js +83 -73
- package/build/lib/commands/app-management.js.map +1 -1
- package/build/lib/commands/app-strings.d.ts +6 -7
- package/build/lib/commands/app-strings.d.ts.map +1 -1
- package/build/lib/commands/app-strings.js +3 -8
- package/build/lib/commands/app-strings.js.map +1 -1
- package/build/lib/commands/appearance.d.ts +7 -9
- package/build/lib/commands/appearance.d.ts.map +1 -1
- package/build/lib/commands/appearance.js +13 -19
- package/build/lib/commands/appearance.js.map +1 -1
- package/build/lib/commands/audit.d.ts +5 -33
- package/build/lib/commands/audit.d.ts.map +1 -1
- package/build/lib/commands/audit.js +3 -16
- package/build/lib/commands/audit.js.map +1 -1
- package/build/lib/commands/battery.d.ts +4 -4
- package/build/lib/commands/battery.d.ts.map +1 -1
- package/build/lib/commands/battery.js +3 -7
- package/build/lib/commands/battery.js.map +1 -1
- package/build/lib/commands/biometric.d.ts +12 -14
- package/build/lib/commands/biometric.d.ts.map +1 -1
- package/build/lib/commands/biometric.js +10 -19
- package/build/lib/commands/biometric.js.map +1 -1
- package/build/lib/commands/certificate.d.ts +14 -19
- package/build/lib/commands/certificate.d.ts.map +1 -1
- package/build/lib/commands/certificate.js +24 -31
- package/build/lib/commands/certificate.js.map +1 -1
- package/build/lib/commands/clipboard.d.ts +9 -11
- package/build/lib/commands/clipboard.d.ts.map +1 -1
- package/build/lib/commands/clipboard.js +8 -13
- package/build/lib/commands/clipboard.js.map +1 -1
- package/build/lib/commands/condition.d.ts +9 -72
- package/build/lib/commands/condition.d.ts.map +1 -1
- package/build/lib/commands/condition.js +5 -66
- package/build/lib/commands/condition.js.map +1 -1
- package/build/lib/commands/content-size.d.ts +16 -19
- package/build/lib/commands/content-size.d.ts.map +1 -1
- package/build/lib/commands/content-size.js +14 -22
- package/build/lib/commands/content-size.js.map +1 -1
- package/build/lib/commands/context.d.ts +130 -161
- package/build/lib/commands/context.d.ts.map +1 -1
- package/build/lib/commands/context.js +123 -108
- package/build/lib/commands/context.js.map +1 -1
- package/build/lib/commands/device-info.d.ts +13 -0
- package/build/lib/commands/device-info.d.ts.map +1 -0
- package/build/lib/commands/device-info.js +20 -0
- package/build/lib/commands/device-info.js.map +1 -0
- package/build/lib/commands/element.d.ts +83 -67
- package/build/lib/commands/element.d.ts.map +1 -1
- package/build/lib/commands/element.js +111 -134
- package/build/lib/commands/element.js.map +1 -1
- package/build/lib/commands/execute.d.ts +10 -22
- package/build/lib/commands/execute.d.ts.map +1 -1
- package/build/lib/commands/execute.js +13 -29
- package/build/lib/commands/execute.js.map +1 -1
- package/build/lib/commands/file-movement.d.ts +31 -42
- package/build/lib/commands/file-movement.d.ts.map +1 -1
- package/build/lib/commands/file-movement.js +146 -205
- package/build/lib/commands/file-movement.js.map +1 -1
- package/build/lib/commands/find.d.ts +20 -12
- package/build/lib/commands/find.d.ts.map +1 -1
- package/build/lib/commands/find.js +27 -65
- package/build/lib/commands/find.js.map +1 -1
- package/build/lib/commands/general.d.ts +84 -80
- package/build/lib/commands/general.d.ts.map +1 -1
- package/build/lib/commands/general.js +67 -54
- package/build/lib/commands/general.js.map +1 -1
- package/build/lib/commands/geolocation.d.ts +16 -36
- package/build/lib/commands/geolocation.d.ts.map +1 -1
- package/build/lib/commands/geolocation.js +8 -25
- package/build/lib/commands/geolocation.js.map +1 -1
- package/build/lib/commands/gesture.d.ts +103 -119
- package/build/lib/commands/gesture.d.ts.map +1 -1
- package/build/lib/commands/gesture.js +98 -138
- package/build/lib/commands/gesture.js.map +1 -1
- package/build/lib/commands/increase-contrast.d.ts +10 -13
- package/build/lib/commands/increase-contrast.d.ts.map +1 -1
- package/build/lib/commands/increase-contrast.js +8 -16
- package/build/lib/commands/increase-contrast.js.map +1 -1
- package/build/lib/commands/iohid.d.ts +6 -1359
- package/build/lib/commands/iohid.d.ts.map +1 -1
- package/build/lib/commands/iohid.js +5 -10
- package/build/lib/commands/iohid.js.map +1 -1
- package/build/lib/commands/keyboard.d.ts +16 -13
- package/build/lib/commands/keyboard.d.ts.map +1 -1
- package/build/lib/commands/keyboard.js +14 -18
- package/build/lib/commands/keyboard.js.map +1 -1
- package/build/lib/commands/keychains.d.ts +2 -2
- package/build/lib/commands/keychains.d.ts.map +1 -1
- package/build/lib/commands/keychains.js +1 -4
- package/build/lib/commands/keychains.js.map +1 -1
- package/build/lib/commands/localization.d.ts +7 -6
- package/build/lib/commands/localization.d.ts.map +1 -1
- package/build/lib/commands/localization.js +7 -8
- package/build/lib/commands/localization.js.map +1 -1
- package/build/lib/commands/location.d.ts +8 -11
- package/build/lib/commands/location.d.ts.map +1 -1
- package/build/lib/commands/location.js +7 -15
- package/build/lib/commands/location.js.map +1 -1
- package/build/lib/commands/lock.d.ts +6 -10
- package/build/lib/commands/lock.d.ts.map +1 -1
- package/build/lib/commands/lock.js +3 -10
- package/build/lib/commands/lock.js.map +1 -1
- package/build/lib/commands/log.d.ts +42 -44
- package/build/lib/commands/log.d.ts.map +1 -1
- package/build/lib/commands/log.js +32 -53
- package/build/lib/commands/log.js.map +1 -1
- package/build/lib/commands/memory.d.ts +4 -5
- package/build/lib/commands/memory.d.ts.map +1 -1
- package/build/lib/commands/memory.js +3 -8
- package/build/lib/commands/memory.js.map +1 -1
- package/build/lib/commands/navigation.d.ts +14 -26
- package/build/lib/commands/navigation.d.ts.map +1 -1
- package/build/lib/commands/navigation.js +22 -32
- package/build/lib/commands/navigation.js.map +1 -1
- package/build/lib/commands/notifications.d.ts +10 -10
- package/build/lib/commands/notifications.d.ts.map +1 -1
- package/build/lib/commands/notifications.js +8 -12
- package/build/lib/commands/notifications.js.map +1 -1
- package/build/lib/commands/pasteboard.d.ts +9 -10
- package/build/lib/commands/pasteboard.d.ts.map +1 -1
- package/build/lib/commands/pasteboard.js +8 -13
- package/build/lib/commands/pasteboard.js.map +1 -1
- package/build/lib/commands/pcap.d.ts +18 -38
- package/build/lib/commands/pcap.d.ts.map +1 -1
- package/build/lib/commands/pcap.js +9 -14
- package/build/lib/commands/pcap.js.map +1 -1
- package/build/lib/commands/performance.d.ts +36 -55
- package/build/lib/commands/performance.d.ts.map +1 -1
- package/build/lib/commands/performance.js +93 -86
- package/build/lib/commands/performance.js.map +1 -1
- package/build/lib/commands/permissions.d.ts +15 -17
- package/build/lib/commands/permissions.d.ts.map +1 -1
- package/build/lib/commands/permissions.js +12 -18
- package/build/lib/commands/permissions.js.map +1 -1
- package/build/lib/commands/proxy-helper.d.ts +11 -11
- package/build/lib/commands/proxy-helper.d.ts.map +1 -1
- package/build/lib/commands/proxy-helper.js +15 -24
- package/build/lib/commands/proxy-helper.js.map +1 -1
- package/build/lib/commands/record-audio.d.ts +25 -52
- package/build/lib/commands/record-audio.d.ts.map +1 -1
- package/build/lib/commands/record-audio.js +17 -19
- package/build/lib/commands/record-audio.js.map +1 -1
- package/build/lib/commands/recordscreen.d.ts +31 -62
- package/build/lib/commands/recordscreen.d.ts.map +1 -1
- package/build/lib/commands/recordscreen.js +29 -28
- package/build/lib/commands/recordscreen.js.map +1 -1
- package/build/lib/commands/screenshots.d.ts +15 -9
- package/build/lib/commands/screenshots.d.ts.map +1 -1
- package/build/lib/commands/screenshots.js +16 -16
- package/build/lib/commands/screenshots.js.map +1 -1
- package/build/lib/commands/simctl.d.ts +16 -22
- package/build/lib/commands/simctl.d.ts.map +1 -1
- package/build/lib/commands/simctl.js +13 -17
- package/build/lib/commands/simctl.js.map +1 -1
- package/build/lib/commands/source.d.ts +10 -8
- package/build/lib/commands/source.d.ts.map +1 -1
- package/build/lib/commands/source.js +11 -14
- package/build/lib/commands/source.js.map +1 -1
- package/build/lib/commands/timeouts.d.ts +25 -32
- package/build/lib/commands/timeouts.d.ts.map +1 -1
- package/build/lib/commands/timeouts.js +19 -15
- package/build/lib/commands/timeouts.js.map +1 -1
- package/build/lib/commands/types.d.ts +80 -0
- package/build/lib/commands/types.d.ts.map +1 -1
- package/build/lib/commands/web.d.ts +199 -202
- package/build/lib/commands/web.d.ts.map +1 -1
- package/build/lib/commands/web.js +216 -175
- package/build/lib/commands/web.js.map +1 -1
- package/build/lib/commands/xctest-record-screen.d.ts +17 -47
- package/build/lib/commands/xctest-record-screen.d.ts.map +1 -1
- package/build/lib/commands/xctest-record-screen.js +28 -59
- package/build/lib/commands/xctest-record-screen.js.map +1 -1
- package/build/lib/commands/xctest.d.ts +37 -37
- package/build/lib/commands/xctest.d.ts.map +1 -1
- package/build/lib/commands/xctest.js +38 -50
- package/build/lib/commands/xctest.js.map +1 -1
- package/build/lib/desired-caps.js +1 -1
- package/build/lib/device/log/ios-simulator-log.d.ts.map +1 -1
- package/build/lib/device/log/ios-simulator-log.js +2 -0
- package/build/lib/device/log/ios-simulator-log.js.map +1 -1
- package/build/lib/device/simulator-management.d.ts.map +1 -1
- package/build/lib/device/simulator-management.js +0 -2
- package/build/lib/device/simulator-management.js.map +1 -1
- package/build/lib/doctor/optional-checks.d.ts +0 -9
- package/build/lib/doctor/optional-checks.d.ts.map +1 -1
- package/build/lib/doctor/optional-checks.js +1 -30
- package/build/lib/doctor/optional-checks.js.map +1 -1
- package/build/lib/driver.d.ts +6 -5
- package/build/lib/driver.d.ts.map +1 -1
- package/build/lib/driver.js +16 -14
- 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 +0 -9
- package/build/lib/execute-method-map.js.map +1 -1
- package/lib/commands/active-app-info.ts +15 -0
- package/lib/commands/alert.ts +98 -0
- package/lib/commands/app-management.ts +414 -0
- package/lib/commands/{app-strings.js → app-strings.ts} +10 -9
- package/lib/commands/appearance.ts +70 -0
- package/lib/commands/audit.ts +25 -0
- package/lib/commands/{battery.js → battery.ts} +10 -12
- package/lib/commands/biometric.ts +52 -0
- package/lib/commands/{certificate.js → certificate.ts} +55 -50
- package/lib/commands/clipboard.ts +37 -0
- package/lib/commands/{condition.js → condition.ts} +21 -77
- package/lib/commands/content-size.ts +67 -0
- package/lib/commands/{context.js → context.ts} +174 -146
- package/lib/commands/device-info.ts +24 -0
- package/lib/commands/element.ts +419 -0
- package/lib/commands/{execute.js → execute.ts} +42 -38
- package/lib/commands/{file-movement.js → file-movement.ts} +212 -235
- package/lib/commands/find.ts +277 -0
- package/lib/commands/{general.js → general.ts} +102 -77
- package/lib/commands/geolocation.ts +55 -0
- package/lib/commands/{gesture.js → gesture.ts} +225 -183
- package/lib/commands/increase-contrast.ts +49 -0
- package/lib/commands/{iohid.js → iohid.ts} +15 -13
- package/lib/commands/keyboard.ts +70 -0
- package/lib/commands/keychains.ts +16 -0
- package/lib/commands/{localization.js → localization.ts} +22 -12
- package/lib/commands/{location.js → location.ts} +19 -22
- package/lib/commands/lock.ts +43 -0
- package/lib/commands/{log.js → log.ts} +68 -68
- package/lib/commands/{memory.js → memory.ts} +9 -9
- package/lib/commands/{navigation.js → navigation.ts} +42 -39
- package/lib/commands/{notifications.js → notifications.ts} +22 -14
- package/lib/commands/pasteboard.ts +44 -0
- package/lib/commands/{pcap.js → pcap.ts} +28 -28
- package/lib/commands/{performance.js → performance.ts} +133 -114
- package/lib/commands/permissions.ts +90 -0
- package/lib/commands/{proxy-helper.js → proxy-helper.ts} +26 -26
- package/lib/commands/{record-audio.js → record-audio.ts} +35 -33
- package/lib/commands/{recordscreen.js → recordscreen.ts} +78 -50
- package/lib/commands/{screenshots.js → screenshots.ts} +27 -21
- package/lib/commands/simctl.ts +82 -0
- package/lib/commands/{source.js → source.ts} +23 -20
- package/lib/commands/timeouts.ts +95 -0
- package/lib/commands/types.ts +86 -0
- package/lib/commands/{web.js → web.ts} +314 -264
- package/lib/commands/{xctest-record-screen.js → xctest-record-screen.ts} +54 -71
- package/lib/commands/{xctest.js → xctest.ts} +78 -71
- package/lib/desired-caps.ts +1 -1
- package/lib/device/log/ios-simulator-log.ts +2 -0
- package/lib/device/simulator-management.ts +0 -2
- package/lib/doctor/optional-checks.ts +0 -33
- package/lib/driver.ts +19 -17
- package/lib/execute-method-map.ts +0 -9
- package/package.json +6 -6
- package/lib/commands/active-app-info.js +0 -12
- package/lib/commands/alert.js +0 -88
- package/lib/commands/app-management.js +0 -346
- package/lib/commands/appearance.js +0 -71
- package/lib/commands/audit.js +0 -31
- package/lib/commands/biometric.js +0 -52
- package/lib/commands/clipboard.js +0 -35
- package/lib/commands/content-size.js +0 -68
- package/lib/commands/deviceInfo.js +0 -27
- package/lib/commands/element.js +0 -423
- package/lib/commands/find.js +0 -205
- package/lib/commands/geolocation.js +0 -56
- package/lib/commands/increase-contrast.js +0 -50
- package/lib/commands/keyboard.js +0 -62
- package/lib/commands/keychains.js +0 -17
- package/lib/commands/lock.js +0 -46
- package/lib/commands/pasteboard.js +0 -43
- package/lib/commands/permissions.js +0 -85
- package/lib/commands/simctl.js +0 -71
- package/lib/commands/timeouts.js +0 -68
|
@@ -2,6 +2,8 @@ import {fs, tempDir, logger, util} from 'appium/support';
|
|
|
2
2
|
import {SubProcess} from 'teen_process';
|
|
3
3
|
import {encodeBase64OrUpload} from '../utils';
|
|
4
4
|
import {waitForCondition} from 'asyncbox';
|
|
5
|
+
import type {XCUITestDriver} from '../driver';
|
|
6
|
+
import type {AudioRecorderOptions} from './types';
|
|
5
7
|
|
|
6
8
|
const MAX_RECORDING_TIME_SEC = 43200;
|
|
7
9
|
const AUDIO_RECORD_FEAT_NAME = 'audio_record';
|
|
@@ -13,7 +15,13 @@ const FFMPEG_BINARY = 'ffmpeg';
|
|
|
13
15
|
const ffmpegLogger = logger.getLogger(FFMPEG_BINARY);
|
|
14
16
|
|
|
15
17
|
export class AudioRecorder {
|
|
16
|
-
|
|
18
|
+
private readonly input: string | number;
|
|
19
|
+
private readonly log: any;
|
|
20
|
+
private readonly audioPath: string;
|
|
21
|
+
private readonly opts: AudioRecorderOptions;
|
|
22
|
+
private mainProcess: SubProcess | null;
|
|
23
|
+
|
|
24
|
+
constructor(input: string | number, log: any, audioPath: string, opts: AudioRecorderOptions = {} as AudioRecorderOptions) {
|
|
17
25
|
this.input = input;
|
|
18
26
|
this.log = log;
|
|
19
27
|
this.audioPath = audioPath;
|
|
@@ -21,7 +29,7 @@ export class AudioRecorder {
|
|
|
21
29
|
this.mainProcess = null;
|
|
22
30
|
}
|
|
23
31
|
|
|
24
|
-
async start(timeoutSeconds) {
|
|
32
|
+
async start(timeoutSeconds: number): Promise<void> {
|
|
25
33
|
try {
|
|
26
34
|
await fs.which(FFMPEG_BINARY);
|
|
27
35
|
} catch {
|
|
@@ -39,7 +47,7 @@ export class AudioRecorder {
|
|
|
39
47
|
'-f',
|
|
40
48
|
audioSource,
|
|
41
49
|
'-i',
|
|
42
|
-
this.input,
|
|
50
|
+
String(this.input),
|
|
43
51
|
'-c:a',
|
|
44
52
|
audioCodec,
|
|
45
53
|
'-b:a',
|
|
@@ -90,7 +98,7 @@ export class AudioRecorder {
|
|
|
90
98
|
);
|
|
91
99
|
this.mainProcess.once('exit', (code, signal) => {
|
|
92
100
|
// ffmpeg returns code 255 if SIGINT arrives
|
|
93
|
-
if ([0, 255].includes(code)) {
|
|
101
|
+
if ([0, 255].includes(code ?? 0)) {
|
|
94
102
|
this.log.info(`The recording session on audio input '${this.input}' has been finished`);
|
|
95
103
|
} else {
|
|
96
104
|
this.log.debug(
|
|
@@ -101,17 +109,17 @@ export class AudioRecorder {
|
|
|
101
109
|
});
|
|
102
110
|
}
|
|
103
111
|
|
|
104
|
-
isRecording() {
|
|
112
|
+
isRecording(): boolean {
|
|
105
113
|
return !!this.mainProcess?.isRunning;
|
|
106
114
|
}
|
|
107
115
|
|
|
108
|
-
async interrupt(force = false) {
|
|
116
|
+
async interrupt(force = false): Promise<boolean> {
|
|
109
117
|
if (this.isRecording()) {
|
|
110
118
|
const interruptPromise = this.mainProcess?.stop(force ? 'SIGTERM' : 'SIGINT');
|
|
111
119
|
this.mainProcess = null;
|
|
112
120
|
try {
|
|
113
121
|
await interruptPromise;
|
|
114
|
-
} catch (e) {
|
|
122
|
+
} catch (e: any) {
|
|
115
123
|
this.log.warn(
|
|
116
124
|
`Cannot ${force ? 'terminate' : 'interrupt'} ${FFMPEG_BINARY}. ` +
|
|
117
125
|
`Original error: ${e.message}`,
|
|
@@ -123,12 +131,12 @@ export class AudioRecorder {
|
|
|
123
131
|
return true;
|
|
124
132
|
}
|
|
125
133
|
|
|
126
|
-
async finish() {
|
|
134
|
+
async finish(): Promise<string> {
|
|
127
135
|
await this.interrupt();
|
|
128
136
|
return this.audioPath;
|
|
129
137
|
}
|
|
130
138
|
|
|
131
|
-
async cleanup() {
|
|
139
|
+
async cleanup(): Promise<void> {
|
|
132
140
|
if (await fs.exists(this.audioPath)) {
|
|
133
141
|
await fs.rimraf(this.audioPath);
|
|
134
142
|
}
|
|
@@ -140,27 +148,25 @@ export class AudioRecorder {
|
|
|
140
148
|
*
|
|
141
149
|
* **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
150
|
*
|
|
143
|
-
* @param
|
|
144
|
-
* @param
|
|
145
|
-
* @param
|
|
146
|
-
* @param
|
|
147
|
-
* @param
|
|
148
|
-
* @param
|
|
149
|
-
* @param
|
|
151
|
+
* @param 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 ""`
|
|
152
|
+
* @param timeLimit - The maximum recording time, in seconds.
|
|
153
|
+
* @param audioCodec - The name of the audio codec.
|
|
154
|
+
* @param audioBitrate - The bitrate of the resulting audio stream.
|
|
155
|
+
* @param audioChannels - The count of audio channels in the resulting stream. Setting it to `1` will create a single channel (mono) audio stream.
|
|
156
|
+
* @param audioRate - The sampling rate of the resulting audio stream (in Hz).
|
|
157
|
+
* @param 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
158
|
* @group Real Device Only
|
|
151
|
-
* @this {XCUITestDriver}
|
|
152
|
-
* @returns {Promise<void>}
|
|
153
|
-
* @privateRemarks Using string literals for the default parameters makes better documentation.
|
|
154
159
|
*/
|
|
155
160
|
export async function startAudioRecording(
|
|
156
|
-
|
|
157
|
-
|
|
161
|
+
this: XCUITestDriver,
|
|
162
|
+
audioInput: string | number,
|
|
163
|
+
timeLimit: string | number = 180,
|
|
158
164
|
audioCodec = 'aac',
|
|
159
165
|
audioBitrate = '128k',
|
|
160
|
-
audioChannels = 2,
|
|
161
|
-
audioRate = 44100,
|
|
166
|
+
audioChannels: string | number = 2,
|
|
167
|
+
audioRate: string | number = 44100,
|
|
162
168
|
forceRestart = false,
|
|
163
|
-
) {
|
|
169
|
+
): Promise<void> {
|
|
164
170
|
if (!this.isFeatureEnabled(AUDIO_RECORD_FEAT_NAME)) {
|
|
165
171
|
throw this.log.errorWithException(
|
|
166
172
|
`Audio capture feature must be enabled on the server side. ` +
|
|
@@ -203,8 +209,8 @@ export async function startAudioRecording(
|
|
|
203
209
|
audioSource: DEFAULT_SOURCE,
|
|
204
210
|
audioCodec,
|
|
205
211
|
audioBitrate,
|
|
206
|
-
audioChannels,
|
|
207
|
-
audioRate,
|
|
212
|
+
audioChannels: Number(audioChannels),
|
|
213
|
+
audioRate: Number(audioRate),
|
|
208
214
|
});
|
|
209
215
|
|
|
210
216
|
const timeoutSeconds = parseInt(String(timeLimit), 10);
|
|
@@ -231,18 +237,17 @@ export async function startAudioRecording(
|
|
|
231
237
|
* If no previously recorded file is found and no active audio recording
|
|
232
238
|
* processes are running then the method returns an empty string.
|
|
233
239
|
*
|
|
234
|
-
* @returns
|
|
240
|
+
* @returns Base64-encoded content of the recorded media file or an
|
|
235
241
|
* empty string if no audio recording has been started before.
|
|
236
242
|
* @throws {Error} If there was an error while getting the recorded file.
|
|
237
|
-
* @this {XCUITestDriver}
|
|
238
243
|
*/
|
|
239
|
-
export async function stopAudioRecording() {
|
|
244
|
+
export async function stopAudioRecording(this: XCUITestDriver): Promise<string> {
|
|
240
245
|
if (!this._audioRecorder) {
|
|
241
246
|
this.log.info('Audio recording has not been started. There is nothing to stop');
|
|
242
247
|
return '';
|
|
243
248
|
}
|
|
244
249
|
|
|
245
|
-
let resultPath;
|
|
250
|
+
let resultPath: string;
|
|
246
251
|
try {
|
|
247
252
|
resultPath = await this._audioRecorder.finish();
|
|
248
253
|
if (!(await fs.exists(resultPath))) {
|
|
@@ -259,6 +264,3 @@ export async function stopAudioRecording() {
|
|
|
259
264
|
return await encodeBase64OrUpload(resultPath);
|
|
260
265
|
}
|
|
261
266
|
|
|
262
|
-
/**
|
|
263
|
-
* @typedef {import('../driver').XCUITestDriver} XCUITestDriver
|
|
264
|
-
*/
|
|
@@ -5,6 +5,9 @@ import {encodeBase64OrUpload} from '../utils';
|
|
|
5
5
|
import {WDA_BASE_URL} from 'appium-webdriveragent';
|
|
6
6
|
import {waitForCondition} from 'asyncbox';
|
|
7
7
|
import url from 'url';
|
|
8
|
+
import type {XCUITestDriver} from '../driver';
|
|
9
|
+
import type {StartRecordingScreenOptions, StopRecordingScreenOptions} from './types';
|
|
10
|
+
import type {WDASettings} from 'appium-webdriveragent';
|
|
8
11
|
|
|
9
12
|
/**
|
|
10
13
|
* Set max timeout for 'reconnect_delay_max' ffmpeg argument usage.
|
|
@@ -20,14 +23,22 @@ const DEFAULT_VCODEC = 'mjpeg';
|
|
|
20
23
|
const MP4_EXT = '.mp4';
|
|
21
24
|
const FFMPEG_BINARY = 'ffmpeg';
|
|
22
25
|
const ffmpegLogger = logger.getLogger(FFMPEG_BINARY);
|
|
23
|
-
const QUALITY_MAPPING = {
|
|
26
|
+
const QUALITY_MAPPING: Record<string, number> = {
|
|
24
27
|
low: 10,
|
|
25
28
|
medium: 25,
|
|
26
29
|
high: 75,
|
|
27
30
|
photo: 100,
|
|
28
31
|
};
|
|
29
32
|
|
|
30
|
-
const HARDWARE_ACCELERATION_PARAMETERS
|
|
33
|
+
const HARDWARE_ACCELERATION_PARAMETERS: Record<
|
|
34
|
+
string,
|
|
35
|
+
{
|
|
36
|
+
hwaccel: string;
|
|
37
|
+
hwaccelOutputFormat: string;
|
|
38
|
+
scaleFilterHWAccel: string;
|
|
39
|
+
videoTypeHWAccel: string;
|
|
40
|
+
}
|
|
41
|
+
> = {
|
|
31
42
|
/* https://trac.ffmpeg.org/wiki/HWAccelIntro#VideoToolbox */
|
|
32
43
|
videoToolbox: {
|
|
33
44
|
hwaccel: 'videotoolbox',
|
|
@@ -54,21 +65,28 @@ const HARDWARE_ACCELERATION_PARAMETERS = {
|
|
|
54
65
|
hwaccel: 'qsv',
|
|
55
66
|
hwaccelOutputFormat: '',
|
|
56
67
|
scaleFilterHWAccel: 'scale_qsv',
|
|
57
|
-
videoTypeHWAccel: 'h264_qsv'
|
|
68
|
+
videoTypeHWAccel: 'h264_qsv',
|
|
58
69
|
},
|
|
59
70
|
/* https://trac.ffmpeg.org/wiki/Hardware/VAAPI */
|
|
60
71
|
vaapi: {
|
|
61
72
|
hwaccel: 'vaapi',
|
|
62
73
|
hwaccelOutputFormat: 'vaapi',
|
|
63
74
|
scaleFilterHWAccel: 'scale_vaapi',
|
|
64
|
-
videoTypeHWAccel: 'h264_vaapi'
|
|
65
|
-
}
|
|
75
|
+
videoTypeHWAccel: 'h264_vaapi',
|
|
76
|
+
},
|
|
66
77
|
};
|
|
67
78
|
|
|
68
79
|
const CAPTURE_START_MARKER = /^\s*frame=/;
|
|
69
80
|
|
|
70
81
|
export class ScreenRecorder {
|
|
71
|
-
|
|
82
|
+
private readonly videoPath: string;
|
|
83
|
+
private readonly log: any;
|
|
84
|
+
private readonly opts: ScreenRecorderOptions;
|
|
85
|
+
private readonly udid: string;
|
|
86
|
+
private mainProcess: SubProcess | null;
|
|
87
|
+
private timeoutHandler: NodeJS.Timeout | null;
|
|
88
|
+
|
|
89
|
+
constructor(udid: string, log: any, videoPath: string, opts: ScreenRecorderOptions = {}) {
|
|
72
90
|
this.videoPath = videoPath;
|
|
73
91
|
this.log = log;
|
|
74
92
|
this.opts = opts;
|
|
@@ -77,7 +95,7 @@ export class ScreenRecorder {
|
|
|
77
95
|
this.timeoutHandler = null;
|
|
78
96
|
}
|
|
79
97
|
|
|
80
|
-
async start(timeoutMs) {
|
|
98
|
+
async start(timeoutMs: number): Promise<void> {
|
|
81
99
|
try {
|
|
82
100
|
await fs.which(FFMPEG_BINARY);
|
|
83
101
|
} catch {
|
|
@@ -98,7 +116,7 @@ export class ScreenRecorder {
|
|
|
98
116
|
pixelFormat,
|
|
99
117
|
} = this.opts;
|
|
100
118
|
|
|
101
|
-
const args = [
|
|
119
|
+
const args: string[] = [
|
|
102
120
|
'-f',
|
|
103
121
|
'mjpeg',
|
|
104
122
|
// https://github.com/appium/appium/issues/16294
|
|
@@ -115,8 +133,8 @@ export class ScreenRecorder {
|
|
|
115
133
|
hwaccel,
|
|
116
134
|
hwaccelOutputFormat,
|
|
117
135
|
scaleFilterHWAccel,
|
|
118
|
-
videoTypeHWAccel
|
|
119
|
-
} = HARDWARE_ACCELERATION_PARAMETERS[hardwareAcceleration] ?? {};
|
|
136
|
+
videoTypeHWAccel,
|
|
137
|
+
} = HARDWARE_ACCELERATION_PARAMETERS[hardwareAcceleration || ''] ?? {};
|
|
120
138
|
|
|
121
139
|
if (hwaccel) {
|
|
122
140
|
args.push('-hwaccel', hwaccel);
|
|
@@ -127,10 +145,10 @@ export class ScreenRecorder {
|
|
|
127
145
|
}
|
|
128
146
|
|
|
129
147
|
//Parameter `-r` is optional. See details: https://github.com/appium/appium/issues/12067
|
|
130
|
-
if (videoFps && videoType === 'libx264' || videoTypeHWAccel) {
|
|
131
|
-
args.push('-r', videoFps);
|
|
148
|
+
if ((videoFps && videoType === 'libx264') || videoTypeHWAccel) {
|
|
149
|
+
args.push('-r', String(videoFps));
|
|
132
150
|
}
|
|
133
|
-
const {protocol, hostname} = url.parse(remoteUrl);
|
|
151
|
+
const {protocol, hostname} = url.parse(remoteUrl || '');
|
|
134
152
|
args.push('-i', `${protocol}//${hostname}:${remotePort}`);
|
|
135
153
|
|
|
136
154
|
if (videoFilters || videoScale) {
|
|
@@ -141,13 +159,13 @@ export class ScreenRecorder {
|
|
|
141
159
|
if (pixelFormat) {
|
|
142
160
|
args.push('-pix_fmt', pixelFormat);
|
|
143
161
|
}
|
|
144
|
-
args.push('-vcodec', videoTypeHWAccel || videoType);
|
|
162
|
+
args.push('-vcodec', videoTypeHWAccel || videoType || DEFAULT_VCODEC);
|
|
145
163
|
args.push('-y');
|
|
146
164
|
args.push(this.videoPath);
|
|
147
165
|
|
|
148
166
|
this.mainProcess = new SubProcess(FFMPEG_BINARY, args);
|
|
149
167
|
let isCaptureStarted = false;
|
|
150
|
-
this.mainProcess.on('line-stderr', (line) => {
|
|
168
|
+
this.mainProcess.on('line-stderr', (line: string) => {
|
|
151
169
|
if (CAPTURE_START_MARKER.test(line)) {
|
|
152
170
|
if (!isCaptureStarted) {
|
|
153
171
|
isCaptureStarted = true;
|
|
@@ -175,9 +193,8 @@ export class ScreenRecorder {
|
|
|
175
193
|
);
|
|
176
194
|
}
|
|
177
195
|
this.log.info(
|
|
178
|
-
`Starting screen capture on the device '${
|
|
179
|
-
|
|
180
|
-
}' with command: '${FFMPEG_BINARY} ${args.join(' ')}'. ` + `Will timeout in ${timeoutMs}ms`,
|
|
196
|
+
`Starting screen capture on the device '${this.udid}' with command: '${FFMPEG_BINARY} ${args.join(' ')}'. ` +
|
|
197
|
+
`Will timeout in ${timeoutMs}ms`,
|
|
181
198
|
);
|
|
182
199
|
|
|
183
200
|
this.timeoutHandler = setTimeout(async () => {
|
|
@@ -189,7 +206,7 @@ export class ScreenRecorder {
|
|
|
189
206
|
}, timeoutMs);
|
|
190
207
|
}
|
|
191
208
|
|
|
192
|
-
async interrupt(force = false) {
|
|
209
|
+
async interrupt(force = false): Promise<boolean> {
|
|
193
210
|
let result = true;
|
|
194
211
|
|
|
195
212
|
if (this.timeoutHandler) {
|
|
@@ -202,7 +219,7 @@ export class ScreenRecorder {
|
|
|
202
219
|
this.mainProcess = null;
|
|
203
220
|
try {
|
|
204
221
|
await interruptPromise;
|
|
205
|
-
} catch (e) {
|
|
222
|
+
} catch (e: any) {
|
|
206
223
|
this.log.warn(
|
|
207
224
|
`Cannot ${force ? 'terminate' : 'interrupt'} ${FFMPEG_BINARY}. ` +
|
|
208
225
|
`Original error: ${e.message}`,
|
|
@@ -214,12 +231,12 @@ export class ScreenRecorder {
|
|
|
214
231
|
return result;
|
|
215
232
|
}
|
|
216
233
|
|
|
217
|
-
async finish() {
|
|
234
|
+
async finish(): Promise<string> {
|
|
218
235
|
await this.interrupt();
|
|
219
236
|
return this.videoPath;
|
|
220
237
|
}
|
|
221
238
|
|
|
222
|
-
async cleanup() {
|
|
239
|
+
async cleanup(): Promise<void> {
|
|
223
240
|
if (await fs.exists(this.videoPath)) {
|
|
224
241
|
await fs.rimraf(this.videoPath);
|
|
225
242
|
}
|
|
@@ -235,13 +252,15 @@ export class ScreenRecorder {
|
|
|
235
252
|
* If screen recording has been already started then the command will stop it forcefully and start a new one.
|
|
236
253
|
* The previously recorded video file will be deleted.
|
|
237
254
|
*
|
|
238
|
-
* @param
|
|
239
|
-
* @returns
|
|
255
|
+
* @param options - The available options.
|
|
256
|
+
* @returns Base64-encoded content of the recorded media file if
|
|
240
257
|
* any screen recording is currently running or an empty string.
|
|
241
258
|
* @throws {Error} If screen recording has failed to start.
|
|
242
|
-
* @this {XCUITestDriver}
|
|
243
259
|
*/
|
|
244
|
-
export async function startRecordingScreen(
|
|
260
|
+
export async function startRecordingScreen(
|
|
261
|
+
this: XCUITestDriver,
|
|
262
|
+
options: StartRecordingScreenOptions = {},
|
|
263
|
+
): Promise<string> {
|
|
245
264
|
const {
|
|
246
265
|
videoType = DEFAULT_VCODEC,
|
|
247
266
|
timeLimit = DEFAULT_RECORDING_TIME_SEC,
|
|
@@ -251,21 +270,21 @@ export async function startRecordingScreen(options = {}) {
|
|
|
251
270
|
videoScale,
|
|
252
271
|
forceRestart,
|
|
253
272
|
pixelFormat,
|
|
254
|
-
hardwareAcceleration
|
|
273
|
+
hardwareAcceleration,
|
|
255
274
|
} = options;
|
|
256
275
|
|
|
257
276
|
let result = '';
|
|
258
277
|
if (!forceRestart) {
|
|
259
278
|
this.log.info(
|
|
260
279
|
`Checking if there is/was a previous screen recording. ` +
|
|
261
|
-
|
|
280
|
+
`Set 'forceRestart' option to 'true' if you'd like to skip this step.`,
|
|
262
281
|
);
|
|
263
282
|
result = (await this.stopRecordingScreen(options)) ?? result;
|
|
264
283
|
}
|
|
265
284
|
|
|
266
285
|
const videoPath = await tempDir.path({
|
|
267
286
|
prefix: `appium_${Math.random().toString(16).substring(2, 8)}`,
|
|
268
|
-
suffix: MP4_EXT
|
|
287
|
+
suffix: MP4_EXT,
|
|
269
288
|
});
|
|
270
289
|
|
|
271
290
|
const wdaBaseUrl = this.opts.wdaBaseUrl || WDA_BASE_URL;
|
|
@@ -275,9 +294,9 @@ export async function startRecordingScreen(options = {}) {
|
|
|
275
294
|
videoType,
|
|
276
295
|
videoFilters,
|
|
277
296
|
videoScale,
|
|
278
|
-
videoFps,
|
|
297
|
+
videoFps: typeof videoFps === 'string' ? parseInt(videoFps, 10) : videoFps,
|
|
279
298
|
pixelFormat,
|
|
280
|
-
hardwareAcceleration
|
|
299
|
+
hardwareAcceleration,
|
|
281
300
|
});
|
|
282
301
|
if (!(await screenRecorder.interrupt(true))) {
|
|
283
302
|
throw this.log.errorWithException('Unable to stop screen recording process');
|
|
@@ -295,10 +314,10 @@ export async function startRecordingScreen(options = {}) {
|
|
|
295
314
|
);
|
|
296
315
|
}
|
|
297
316
|
|
|
298
|
-
let {mjpegServerScreenshotQuality, mjpegServerFramerate} =
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
);
|
|
317
|
+
let {mjpegServerScreenshotQuality, mjpegServerFramerate} = (await this.proxyCommand(
|
|
318
|
+
'/appium/settings',
|
|
319
|
+
'GET',
|
|
320
|
+
)) as WDASettings;
|
|
302
321
|
if (videoQuality) {
|
|
303
322
|
const quality = _.isInteger(videoQuality)
|
|
304
323
|
? videoQuality
|
|
@@ -306,11 +325,12 @@ export async function startRecordingScreen(options = {}) {
|
|
|
306
325
|
if (!quality) {
|
|
307
326
|
throw new Error(
|
|
308
327
|
`videoQuality value should be one of ${JSON.stringify(
|
|
309
|
-
_.keys(QUALITY_MAPPING)
|
|
310
|
-
)} or a number in range 1..100. ` + `'${videoQuality}' is given instead
|
|
328
|
+
_.keys(QUALITY_MAPPING),
|
|
329
|
+
)} or a number in range 1..100. ` + `'${videoQuality}' is given instead`,
|
|
311
330
|
);
|
|
312
331
|
}
|
|
313
|
-
mjpegServerScreenshotQuality =
|
|
332
|
+
mjpegServerScreenshotQuality =
|
|
333
|
+
mjpegServerScreenshotQuality !== quality ? (quality as number) : undefined;
|
|
314
334
|
} else {
|
|
315
335
|
mjpegServerScreenshotQuality = undefined;
|
|
316
336
|
}
|
|
@@ -319,7 +339,7 @@ export async function startRecordingScreen(options = {}) {
|
|
|
319
339
|
if (isNaN(fps)) {
|
|
320
340
|
throw new Error(
|
|
321
341
|
`videoFps value should be a valid number in range 1..60. ` +
|
|
322
|
-
|
|
342
|
+
`'${videoFps}' is given instead`,
|
|
323
343
|
);
|
|
324
344
|
}
|
|
325
345
|
mjpegServerFramerate = mjpegServerFramerate !== fps ? fps : undefined;
|
|
@@ -330,8 +350,8 @@ export async function startRecordingScreen(options = {}) {
|
|
|
330
350
|
await this.proxyCommand('/appium/settings', 'POST', {
|
|
331
351
|
settings: {
|
|
332
352
|
mjpegServerScreenshotQuality,
|
|
333
|
-
mjpegServerFramerate
|
|
334
|
-
}
|
|
353
|
+
mjpegServerFramerate,
|
|
354
|
+
},
|
|
335
355
|
});
|
|
336
356
|
}
|
|
337
357
|
|
|
@@ -355,16 +375,17 @@ export async function startRecordingScreen(options = {}) {
|
|
|
355
375
|
* active screen recording processes are running then the method returns an
|
|
356
376
|
* empty string.
|
|
357
377
|
*
|
|
358
|
-
* @param
|
|
359
|
-
*
|
|
360
|
-
* @returns {Promise<string?>} Base64-encoded content of the recorded media
|
|
378
|
+
* @param options - The available options.
|
|
379
|
+
* @returns Base64-encoded content of the recorded media
|
|
361
380
|
* file if `remotePath` parameter is empty or null or an empty string.
|
|
362
381
|
* @throws {Error} If there was an error while getting the name of a media
|
|
363
382
|
* file or the file content cannot be uploaded to the remote
|
|
364
383
|
* location.
|
|
365
|
-
* @this {XCUITestDriver}
|
|
366
384
|
*/
|
|
367
|
-
export async function stopRecordingScreen(
|
|
385
|
+
export async function stopRecordingScreen(
|
|
386
|
+
this: XCUITestDriver,
|
|
387
|
+
options: StopRecordingScreenOptions = {},
|
|
388
|
+
): Promise<string | null> {
|
|
368
389
|
if (!this._recentScreenRecorder) {
|
|
369
390
|
this.log.info('Screen recording is not running. There is nothing to stop.');
|
|
370
391
|
return '';
|
|
@@ -375,7 +396,7 @@ export async function stopRecordingScreen(options = {}) {
|
|
|
375
396
|
if (!(await fs.exists(videoPath))) {
|
|
376
397
|
throw this.log.errorWithException(
|
|
377
398
|
`The screen recorder utility has failed ` +
|
|
378
|
-
|
|
399
|
+
`to store the actual screen recording at '${videoPath}'`,
|
|
379
400
|
);
|
|
380
401
|
}
|
|
381
402
|
return await encodeBase64OrUpload(videoPath, options.remotePath, options);
|
|
@@ -386,6 +407,13 @@ export async function stopRecordingScreen(options = {}) {
|
|
|
386
407
|
}
|
|
387
408
|
}
|
|
388
409
|
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
410
|
+
interface ScreenRecorderOptions {
|
|
411
|
+
hardwareAcceleration?: string;
|
|
412
|
+
remotePort?: number;
|
|
413
|
+
remoteUrl?: string;
|
|
414
|
+
videoFps?: number;
|
|
415
|
+
videoType?: string;
|
|
416
|
+
videoScale?: string;
|
|
417
|
+
videoFilters?: string;
|
|
418
|
+
pixelFormat?: string;
|
|
419
|
+
}
|
|
@@ -2,19 +2,23 @@ import {retryInterval} from 'asyncbox';
|
|
|
2
2
|
import _ from 'lodash';
|
|
3
3
|
import {errors} from 'appium/driver';
|
|
4
4
|
import {util, imageUtil} from 'appium/support';
|
|
5
|
+
import type {XCUITestDriver} from '../driver';
|
|
6
|
+
import type {Simulator} from 'appium-ios-simulator';
|
|
7
|
+
import type {Element} from '@appium/types';
|
|
5
8
|
|
|
6
9
|
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
10
|
+
* Takes a screenshot of the current screen.
|
|
11
|
+
*
|
|
12
|
+
* @returns Base64-encoded screenshot data
|
|
9
13
|
*/
|
|
10
|
-
export async function getScreenshot() {
|
|
14
|
+
export async function getScreenshot(this: XCUITestDriver): Promise<string> {
|
|
11
15
|
if (this.isWebContext()) {
|
|
12
16
|
const webScreenshotMode = (await this.settings.getSettings()).webScreenshotMode;
|
|
13
17
|
switch (_.toLower(webScreenshotMode)) {
|
|
14
18
|
case 'page':
|
|
15
19
|
case 'viewport':
|
|
16
|
-
return await
|
|
17
|
-
coordinateSystem:
|
|
20
|
+
return await this.remote.captureScreenshot({
|
|
21
|
+
coordinateSystem: _.capitalize(webScreenshotMode) as 'Viewport' | 'Page',
|
|
18
22
|
});
|
|
19
23
|
case 'native':
|
|
20
24
|
case undefined:
|
|
@@ -29,7 +33,7 @@ export async function getScreenshot() {
|
|
|
29
33
|
}
|
|
30
34
|
}
|
|
31
35
|
|
|
32
|
-
const getScreenshotFromWDA = async () => {
|
|
36
|
+
const getScreenshotFromWDA = async (): Promise<string> => {
|
|
33
37
|
this.log.debug(`Taking screenshot with WDA`);
|
|
34
38
|
const data = await this.proxyCommand('/screenshot', 'GET');
|
|
35
39
|
if (!_.isString(data)) {
|
|
@@ -53,14 +57,14 @@ export async function getScreenshot() {
|
|
|
53
57
|
|
|
54
58
|
try {
|
|
55
59
|
return await getScreenshotFromWDA();
|
|
56
|
-
} catch (err) {
|
|
60
|
+
} catch (err: any) {
|
|
57
61
|
this.log.warn(`Error getting screenshot: ${err.message}`);
|
|
58
62
|
}
|
|
59
63
|
|
|
60
64
|
// simulator attempt
|
|
61
65
|
if (this.isSimulator()) {
|
|
62
66
|
this.log.info(`Falling back to 'simctl io screenshot' API`);
|
|
63
|
-
const payload = await
|
|
67
|
+
const payload = await (this.device as Simulator).simctl.getScreenshot();
|
|
64
68
|
if (!payload) {
|
|
65
69
|
throw new errors.UnableToCaptureScreen();
|
|
66
70
|
}
|
|
@@ -68,13 +72,19 @@ export async function getScreenshot() {
|
|
|
68
72
|
}
|
|
69
73
|
|
|
70
74
|
// Retry for real devices only. Fail fast on Simulator if simctl does not work as expected
|
|
71
|
-
return
|
|
75
|
+
return await retryInterval(2, 1000, getScreenshotFromWDA) as string;
|
|
72
76
|
}
|
|
73
77
|
|
|
74
78
|
/**
|
|
75
|
-
*
|
|
79
|
+
* Takes a screenshot of a specific element.
|
|
80
|
+
*
|
|
81
|
+
* @param el - Element to capture
|
|
82
|
+
* @returns Base64-encoded screenshot data
|
|
76
83
|
*/
|
|
77
|
-
export async function getElementScreenshot(
|
|
84
|
+
export async function getElementScreenshot(
|
|
85
|
+
this: XCUITestDriver,
|
|
86
|
+
el: Element<string> | string,
|
|
87
|
+
): Promise<string> {
|
|
78
88
|
el = util.unwrapElement(el);
|
|
79
89
|
if (this.isWebContext()) {
|
|
80
90
|
const atomsElement = this.getAtomsElement(el);
|
|
@@ -83,8 +93,7 @@ export async function getElementScreenshot(el) {
|
|
|
83
93
|
throw new errors.UnableToCaptureScreen('Cannot take a screenshot of a zero-size element');
|
|
84
94
|
}
|
|
85
95
|
const {x, y} = await this.executeAtom('get_top_left_coordinates', [atomsElement]);
|
|
86
|
-
return await (
|
|
87
|
-
.captureScreenshot({rect: {x, y, width, height}});
|
|
96
|
+
return await this.remote.captureScreenshot({rect: {x, y, width, height}});
|
|
88
97
|
}
|
|
89
98
|
|
|
90
99
|
const data = await this.proxyCommand(`/element/${el}/screenshot`, 'GET');
|
|
@@ -97,13 +106,13 @@ export async function getElementScreenshot(el) {
|
|
|
97
106
|
}
|
|
98
107
|
|
|
99
108
|
/**
|
|
100
|
-
*
|
|
101
|
-
*
|
|
109
|
+
* Takes a screenshot of the current viewport.
|
|
110
|
+
*
|
|
111
|
+
* @returns Base64-encoded screenshot data
|
|
102
112
|
*/
|
|
103
|
-
export async function getViewportScreenshot() {
|
|
113
|
+
export async function getViewportScreenshot(this: XCUITestDriver): Promise<string> {
|
|
104
114
|
if (this.isWebContext()) {
|
|
105
|
-
return await
|
|
106
|
-
.captureScreenshot();
|
|
115
|
+
return await this.remote.captureScreenshot();
|
|
107
116
|
}
|
|
108
117
|
|
|
109
118
|
const screenshot = await this.getScreenshot();
|
|
@@ -132,6 +141,3 @@ export async function getViewportScreenshot() {
|
|
|
132
141
|
return await imageUtil.cropBase64Image(screenshot, region);
|
|
133
142
|
}
|
|
134
143
|
|
|
135
|
-
/**
|
|
136
|
-
* @typedef {import('../driver').XCUITestDriver} XCUITestDriver
|
|
137
|
-
*/
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { errors } from 'appium/driver';
|
|
2
|
+
import type {XCUITestDriver} from '../driver';
|
|
3
|
+
import type {Simulator} from 'appium-ios-simulator';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* List of subcommands for `simctl` we provide as mobile simctl command.
|
|
7
|
+
* They accept 'device' target.
|
|
8
|
+
*/
|
|
9
|
+
const SUBCOMMANDS_HAS_DEVICE = [
|
|
10
|
+
'boot',
|
|
11
|
+
'get_app_container',
|
|
12
|
+
'getenv',
|
|
13
|
+
'icloud_sync',
|
|
14
|
+
'install',
|
|
15
|
+
'install_app_data',
|
|
16
|
+
'io',
|
|
17
|
+
'keychain',
|
|
18
|
+
'launch',
|
|
19
|
+
'location',
|
|
20
|
+
'logverbose',
|
|
21
|
+
'openurl',
|
|
22
|
+
'pbcopy',
|
|
23
|
+
'pbpaste',
|
|
24
|
+
'privacy',
|
|
25
|
+
'push',
|
|
26
|
+
'shutdown',
|
|
27
|
+
'spawn',
|
|
28
|
+
'status_bar',
|
|
29
|
+
'terminate',
|
|
30
|
+
'ui',
|
|
31
|
+
'uninstall'
|
|
32
|
+
] as const;
|
|
33
|
+
|
|
34
|
+
export interface SimctlExecResponse {
|
|
35
|
+
/** The output of standard out. */
|
|
36
|
+
stdout: string;
|
|
37
|
+
/** The output of standard error. */
|
|
38
|
+
stderr: string;
|
|
39
|
+
/** Return code. */
|
|
40
|
+
code: number;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Run the given command with arguments as `xcrun simctl` subcommand.
|
|
45
|
+
* This method works behind the 'simctl' security flag.
|
|
46
|
+
*
|
|
47
|
+
* @param command - Subcommand to run with `xcrun simctl`. Must be one of the supported commands.
|
|
48
|
+
* @param args - Arguments for the subcommand. The arguments should be after <device> in the help.
|
|
49
|
+
* @param timeout - The maximum number of milliseconds
|
|
50
|
+
* @returns The execution result with stdout, stderr, and return code
|
|
51
|
+
* @throws If the simctl subcommand command returns non-zero return code, or the given subcommand was invalid.
|
|
52
|
+
*/
|
|
53
|
+
export async function mobileSimctl(
|
|
54
|
+
this: XCUITestDriver,
|
|
55
|
+
command: string,
|
|
56
|
+
args: string[] = [],
|
|
57
|
+
timeout?: number,
|
|
58
|
+
): Promise<SimctlExecResponse> {
|
|
59
|
+
if (!this.isSimulator()) {
|
|
60
|
+
throw new errors.UnsupportedOperationError(`Only simulator is supported.`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (!this.opts.udid) {
|
|
64
|
+
throw new errors.InvalidArgumentError(`Unknown device or simulator UDID: '${this.opts.udid}'`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (!(SUBCOMMANDS_HAS_DEVICE as readonly string[]).includes(command)) {
|
|
68
|
+
throw new errors.InvalidArgumentError(`The given command '${command}' is not supported. ` +
|
|
69
|
+
`Available subcommands are ${SUBCOMMANDS_HAS_DEVICE.join(',')}`);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const result = await (this.device as Simulator).simctl.exec(
|
|
73
|
+
command as typeof SUBCOMMANDS_HAS_DEVICE[number],
|
|
74
|
+
{args: [this.opts.udid, ...args], timeout}
|
|
75
|
+
);
|
|
76
|
+
return {
|
|
77
|
+
stdout: result?.stdout ?? '',
|
|
78
|
+
stderr: result?.stderr ?? '',
|
|
79
|
+
code: result?.code ?? 0
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|