appium-xcuitest-driver 10.13.4 → 10.14.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.
@@ -1,24 +1,25 @@
1
1
  import {INSTRUMENT_CHANNEL, services} from 'appium-ios-device';
2
2
  import _ from 'lodash';
3
3
  import { isIos18OrNewer } from '../utils';
4
+ import type {XCUITestDriver} from '../driver';
5
+ import type {DVTServiceWithConnection} from 'appium-ios-remotexpc';
6
+ import type {Condition} from './types';
4
7
 
5
8
  /**
6
9
  * Get all available ConditionInducer configuration information, which can be used with
7
10
  * {@linkcode XCUITestDriver.enableConditionInducer}
8
- * @returns {Promise<Condition[]>}
9
11
  * @since 4.9.0
10
12
  * @see {@link https://help.apple.com/xcode/mac/current/#/dev308429d42}
11
- * @this {XCUITestDriver}
12
13
  */
13
- export async function listConditionInducers() {
14
+ export async function listConditionInducers(this: XCUITestDriver): Promise<Condition[]> {
14
15
  requireConditionInducerCompatibleDevice.call(this);
15
16
 
16
17
  if (isIos18OrNewer(this.opts)) {
17
18
  const dvtConnection = await startRemoteXPC(this.device.udid);
18
19
  try {
19
20
  const result = await dvtConnection.conditionInducer.list();
20
- return /** @type {Condition[]} */ (result);
21
- } catch (err) {
21
+ return result as Condition[];
22
+ } catch (err: any) {
22
23
  this.log.error(`Failed to list condition inducers via RemoteXPC: ${err.message}`);
23
24
  throw err;
24
25
  } finally {
@@ -50,15 +51,18 @@ export async function listConditionInducers() {
50
51
  * (Note: the socket needs to remain connected during operation)
51
52
  * (Note: Device conditions are available only for real devices running iOS 13.0 and later.)
52
53
  *
53
- * @param {string} conditionID - Determine which condition IDs are available with the {@linkcode XCUITestDriver.listConditionInducers} command
54
- * @param {string} profileID - Determine which profile IDs are available with the {@linkcode XCUITestDriver.listConditionInducers} command
55
- * @returns {Promise<boolean>} `true` if enabling the condition succeeded
54
+ * @param conditionID - Determine which condition IDs are available with the {@linkcode XCUITestDriver.listConditionInducers} command
55
+ * @param profileID - Determine which profile IDs are available with the {@linkcode XCUITestDriver.listConditionInducers} command
56
+ * @returns `true` if enabling the condition succeeded
56
57
  * @throws {Error} If you try to start another Condition and the previous Condition has not stopped
57
58
  * @since 4.9.0
58
59
  * @see {@link https://help.apple.com/xcode/mac/current/#/dev308429d42}
59
- * @this {XCUITestDriver}
60
60
  */
61
- export async function enableConditionInducer(conditionID, profileID) {
61
+ export async function enableConditionInducer(
62
+ this: XCUITestDriver,
63
+ conditionID: string,
64
+ profileID: string,
65
+ ): Promise<boolean> {
62
66
  requireConditionInducerCompatibleDevice.call(this);
63
67
 
64
68
  if (isIos18OrNewer(this.opts)) {
@@ -76,7 +80,7 @@ export async function enableConditionInducer(conditionID, profileID) {
76
80
 
77
81
  this.log.info(`Successfully enabled condition profile: ${profileID}`);
78
82
  return true;
79
- } catch (err) {
83
+ } catch (err: any) {
80
84
  await closeRemoteXPC.call(this);
81
85
  throw this.log.errorWithException(`Condition inducer '${profileID}' cannot be enabled: '${err.message}'`);
82
86
  }
@@ -109,12 +113,11 @@ export async function enableConditionInducer(conditionID, profileID) {
109
113
  * condition inducer will be automatically disabled
110
114
  *
111
115
  * (Note: this is also automatically called upon session cleanup)
112
- * @returns {Promise<boolean>} `true` if disable the condition succeeded
116
+ * @returns `true` if disable the condition succeeded
113
117
  * @since 4.9.0
114
118
  * @see {@link https://help.apple.com/xcode/mac/current/#/dev308429d42}
115
- * @this {XCUITestDriver}
116
119
  */
117
- export async function disableConditionInducer() {
120
+ export async function disableConditionInducer(this: XCUITestDriver): Promise<boolean> {
118
121
  requireConditionInducerCompatibleDevice.call(this);
119
122
 
120
123
  if (isIos18OrNewer(this.opts)) {
@@ -127,7 +130,7 @@ export async function disableConditionInducer() {
127
130
  await this._remoteXPCConditionInducerConnection.conditionInducer.disable();
128
131
  this.log.info('Successfully disabled condition inducer');
129
132
  return true;
130
- } catch (err) {
133
+ } catch (err: any) {
131
134
  this.log.warn(`Failed to disable condition inducer via RemoteXPC: ${err.message}`);
132
135
  return false;
133
136
  } finally {
@@ -158,81 +161,22 @@ export async function disableConditionInducer() {
158
161
  }
159
162
  }
160
163
 
161
- /**
162
- * @this {XCUITestDriver}
163
- * @returns {void}
164
- */
165
- function requireConditionInducerCompatibleDevice() {
164
+ function requireConditionInducerCompatibleDevice(this: XCUITestDriver): void {
166
165
  if (this.isSimulator()) {
167
166
  throw this.log.errorWithException('Condition inducer only works on real devices');
168
167
  }
169
168
  }
170
169
 
171
- /**
172
- * @param {string} udid
173
- * @returns {Promise<DVTServiceWithConnection>}
174
- */
175
- async function startRemoteXPC(udid) {
170
+ async function startRemoteXPC(udid: string): Promise<DVTServiceWithConnection> {
176
171
  const {Services} = await import('appium-ios-remotexpc');
177
172
  return Services.startDVTService(udid);
178
173
  }
179
174
 
180
- /**
181
- * @this {XCUITestDriver}
182
- * @returns {Promise<void>}
183
- */
184
- async function closeRemoteXPC() {
175
+ async function closeRemoteXPC(this: XCUITestDriver): Promise<void> {
185
176
  if (this._remoteXPCConditionInducerConnection) {
186
177
  await this._remoteXPCConditionInducerConnection.remoteXPC.close();
187
178
  this._remoteXPCConditionInducerConnection = null;
188
179
  }
189
180
  }
190
181
 
191
- /**
192
- * @typedef {import('appium-ios-remotexpc', {with: {'resolution-mode': 'import'}}).DVTServiceWithConnection} DVTServiceWithConnection
193
- */
194
-
195
- /**
196
- * @typedef {Object} Profile
197
- * @property {string} name
198
- * @property {string} identifier the property is profileID used in {@linkcode XCUITestDriver.enableConditionInducer}
199
- * @property {string} description Configuration details
200
- */
201
-
202
- /**
203
- * We can use the returned data to determine whether the Condition is enabled and the currently enabled configuration information
204
- * @typedef {Object} Condition
205
- * @property {Profile[]} profiles
206
- * @property {string} identifier the property is conditionID used in {@linkcode XCUITestDriver.enableConditionInducer}
207
- * @property {boolean} profilesSorted
208
- * @property {boolean} isDestructive
209
- * @property {boolean} isInternal
210
- * @property {boolean} isActive `true` if this condition identifier is enabled
211
- * @property {string} activeProfile enabled profiles identifier
212
- * @example {
213
- * "profiles": [
214
- * {
215
- * "name": "100% packet loss",
216
- * "identifier": "SlowNetwork100PctLoss", // MobileEnableConditionInducer profileID
217
- * "description": "Name: 100% Loss Scenario\n
218
- * Downlink Bandwidth: 0 Mbps\n
219
- * Downlink Latency:0 ms\n
220
- * Downlink Packet Loss Ratio: 100%\n
221
- * Uplink Bandwidth: 0 Mbps\n
222
- * Uplink Latency: 0 ms\n
223
- * Uplink Packet Loss Ratio: 100%"
224
- * }
225
- * ],
226
- * "profilesSorted": true,
227
- * "identifier": "SlowNetworkCondition", // MobileEnableConditionInducer conditionID
228
- * "isDestructive": false,
229
- * "isInternal": false,
230
- * "activeProfile": "",
231
- * "name": "Network Link",
232
- * "isActive": false
233
- * }
234
- */
235
182
 
236
- /**
237
- * @typedef {import('../driver').XCUITestDriver} XCUITestDriver
238
- */
@@ -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
- constructor(input, log, audioPath, opts = {}) {
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 {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`).
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
- audioInput,
157
- timeLimit = 180,
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 {Promise<string>} Base64-encoded content of the recorded media file or an
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
- */
@@ -646,3 +646,66 @@ export interface LogEntry {
646
646
  }
647
647
 
648
648
  export type LogListener = (logEntry: LogEntry) => any;
649
+
650
+ /**
651
+ * Condition inducer profile configuration
652
+ */
653
+ export interface Profile {
654
+ name: string;
655
+ /** The property is profileID used in {@linkcode XCUITestDriver.enableConditionInducer} */
656
+ identifier: string;
657
+ /** Configuration details */
658
+ description: string;
659
+ }
660
+
661
+ /**
662
+ * We can use the returned data to determine whether the Condition is enabled and the currently enabled configuration information
663
+ */
664
+ export interface Condition {
665
+ profiles: Profile[];
666
+ /** The property is conditionID used in {@linkcode XCUITestDriver.enableConditionInducer} */
667
+ identifier: string;
668
+ profilesSorted: boolean;
669
+ isDestructive: boolean;
670
+ isInternal: boolean;
671
+ /** `true` if this condition identifier is enabled */
672
+ isActive: boolean;
673
+ /** Enabled profiles identifier */
674
+ activeProfile: string;
675
+ }
676
+
677
+ /**
678
+ * Information about an XCTest screen recording session
679
+ */
680
+ export interface XcTestScreenRecordingInfo {
681
+ /** Unique identifier of the video being recorded */
682
+ uuid: string;
683
+ /** FPS value */
684
+ fps: number;
685
+ /** Video codec, where 0 is h264 */
686
+ codec: number;
687
+ /** The timestamp when the screen recording has started in float Unix seconds */
688
+ startedAt: number;
689
+ }
690
+
691
+ /**
692
+ * XCTest screen recording result with payload
693
+ */
694
+ export interface XcTestScreenRecording extends XcTestScreenRecordingInfo {
695
+ /**
696
+ * Base64-encoded content of the recorded media file if `remotePath` parameter is empty or null or an empty string otherwise.
697
+ * The media is expected to a be a valid QuickTime movie (.mov).
698
+ */
699
+ payload: string;
700
+ }
701
+
702
+ /**
703
+ * Options for AudioRecorder
704
+ */
705
+ export interface AudioRecorderOptions {
706
+ audioSource: string;
707
+ audioCodec: string;
708
+ audioBitrate: string;
709
+ audioChannels: number;
710
+ audioRate: number;
711
+ }
@@ -2,6 +2,11 @@ import _ from 'lodash';
2
2
  import {fs, util} from 'appium/support';
3
3
  import {encodeBase64OrUpload} from '../utils';
4
4
  import path from 'node:path';
5
+ import type {XCUITestDriver} from '../driver';
6
+ import type {Simulator} from 'appium-ios-simulator';
7
+ import type {RealDevice} from '../device/real-device-management';
8
+ import type {HTTPHeaders} from '@appium/types';
9
+ import type {XcTestScreenRecordingInfo, XcTestScreenRecording} from './types';
5
10
 
6
11
  const MOV_EXT = '.mov';
7
12
  const FEATURE_NAME = 'xctest_screen_record';
@@ -10,30 +15,9 @@ const DOMAIN_TYPE = 'appDataContainer';
10
15
  const USERNAME = 'mobile';
11
16
  const SUBDIRECTORY = 'Attachments';
12
17
 
13
- /**
14
- * @typedef {Object} XcTestScreenRecordingInfo
15
- * @property {string} uuid Unique identifier of the video being recorded
16
- * @property {number} fps FPS value
17
- * @property {number} codec Video codec, where 0 is h264
18
- * @property {number} startedAt The timestamp when the screen recording has started in float Unix seconds
19
- */
20
-
21
- /**
22
- * @typedef {Object} XcTestScreenRecordingType
23
- * @property {string} payload Base64-encoded content of the recorded media
24
- * file if `remotePath` parameter is empty or null or an empty string otherwise.
25
- * The media is expected to a be a valid QuickTime movie (.mov).
26
- * @typedef {XcTestScreenRecordingInfo & XcTestScreenRecordingType} XcTestScreenRecording
27
- */
28
-
29
- /**
30
- * @this {XCUITestDriver}
31
- * @param {string} uuid Unique identifier of the video being recorded
32
- * @returns {Promise<string>} The full path to the screen recording movie
33
- */
34
- async function retrieveRecodingFromSimulator(uuid) {
35
- const device = /** @type {import('appium-ios-simulator').Simulator} */ (this.device);
36
- const dataRoot = /** @type {string} */ (device.getDir());
18
+ async function retrieveRecodingFromSimulator(this: XCUITestDriver, uuid: string): Promise<string> {
19
+ const device = this.device as Simulator;
20
+ const dataRoot = device.getDir();
37
21
  // On Simulators the path looks like
38
22
  // $HOME/Library/Developer/CoreSimulator/Devices/F8E1968A-8443-4A9A-AB86-27C54C36A2F6/data/Containers/Data/InternalDaemon/4E3FE8DF-AD0A-41DA-B6EC-C35E5798C219/Attachments/A044DAF7-4A58-4CD5-95C3-29B4FE80C377
39
23
  const internalDaemonRoot = path.resolve(dataRoot, 'Containers', 'Data', 'InternalDaemon');
@@ -52,13 +36,8 @@ async function retrieveRecodingFromSimulator(uuid) {
52
36
  return videoPath;
53
37
  }
54
38
 
55
- /**
56
- * @this {XCUITestDriver}
57
- * @param {string} uuid Unique identifier of the video being recorded
58
- * @returns {Promise<string>} The full path to the screen recording movie
59
- */
60
- async function retrieveRecodingFromRealDevice(uuid) {
61
- const device = /** @type {import('../device/real-device-management').RealDevice} */ (this.device);
39
+ async function retrieveRecodingFromRealDevice(this: XCUITestDriver, uuid: string): Promise<string> {
40
+ const device = this.device as RealDevice;
62
41
 
63
42
  const fileNames = await device.devicectl.listFiles(DOMAIN_TYPE, DOMAIN_IDENTIFIER, {
64
43
  username: USERNAME,
@@ -69,7 +48,10 @@ async function retrieveRecodingFromRealDevice(uuid) {
69
48
  `Unable to locate XCTest screen recording identified by '${uuid}' for the device ${this.opts.udid}`
70
49
  );
71
50
  }
72
- const videoPath = path.join(/** @type {string} */ (this.opts.tmpDir), `${uuid}${MOV_EXT}`);
51
+ if (!this.opts.tmpDir) {
52
+ throw new Error('tmpDir is not set in driver options');
53
+ }
54
+ const videoPath = path.join(this.opts.tmpDir, `${uuid}${MOV_EXT}`);
73
55
  await device.devicectl.pullFile(`${SUBDIRECTORY}/${uuid}`, videoPath, {
74
56
  username: USERNAME,
75
57
  domainIdentifier: DOMAIN_IDENTIFIER,
@@ -80,15 +62,10 @@ async function retrieveRecodingFromRealDevice(uuid) {
80
62
  return videoPath;
81
63
  }
82
64
 
83
- /**
84
- * @this {XCUITestDriver}
85
- * @param {string} uuid Unique identifier of the video being recorded
86
- * @returns {Promise<string>} The full path to the screen recording movie
87
- */
88
- async function retrieveXcTestScreenRecording(uuid) {
65
+ async function retrieveXcTestScreenRecording(this: XCUITestDriver, uuid: string): Promise<string> {
89
66
  return this.isRealDevice()
90
- ? await retrieveRecodingFromRealDevice.bind(this)(uuid)
91
- : await retrieveRecodingFromSimulator.bind(this)(uuid);
67
+ ? await retrieveRecodingFromRealDevice.call(this, uuid)
68
+ : await retrieveRecodingFromSimulator.call(this, uuid);
92
69
  }
93
70
 
94
71
  /**
@@ -102,14 +79,16 @@ async function retrieveXcTestScreenRecording(uuid) {
102
79
  * If the recording is already running this API is a noop.
103
80
  *
104
81
  * @since Xcode 15/iOS 17
105
- * @param {number} [fps] FPS value
106
- * @param {number} [codec] Video codec, where 0 is h264, 1 is HEVC
107
- * @returns {Promise<XcTestScreenRecordingInfo>} The information
108
- * about a newly created or a running the screen recording.
82
+ * @param fps - FPS value
83
+ * @param codec - Video codec, where 0 is h264, 1 is HEVC
84
+ * @returns The information about a newly created or a running the screen recording.
109
85
  * @throws {Error} If screen recording has failed to start.
110
- * @this {XCUITestDriver}
111
86
  */
112
- export async function mobileStartXctestScreenRecording(fps, codec) {
87
+ export async function mobileStartXctestScreenRecording(
88
+ this: XCUITestDriver,
89
+ fps?: number,
90
+ codec?: number,
91
+ ): Promise<XcTestScreenRecordingInfo> {
113
92
  if (this.isRealDevice()) {
114
93
  // This feature might be used to abuse real devices as there is no
115
94
  // reliable way (yet) to cleanup video recordings stored there
@@ -117,16 +96,14 @@ export async function mobileStartXctestScreenRecording(fps, codec) {
117
96
  this.assertFeatureEnabled(FEATURE_NAME);
118
97
  }
119
98
 
120
- const opts = {};
99
+ const opts: {codec?: number; fps?: number} = {};
121
100
  if (_.isInteger(codec)) {
122
101
  opts.codec = codec;
123
102
  }
124
103
  if (_.isInteger(fps)) {
125
104
  opts.fps = fps;
126
105
  }
127
- const response = /** @type {XcTestScreenRecordingInfo} */ (
128
- await this.proxyCommand('/wda/video/start', 'POST', opts)
129
- );
106
+ const response = await this.proxyCommand('/wda/video/start', 'POST', opts) as XcTestScreenRecordingInfo;
130
107
  this.log.info(`Started a new screen recording: ${JSON.stringify(response)}`);
131
108
  return response;
132
109
  }
@@ -134,13 +111,11 @@ export async function mobileStartXctestScreenRecording(fps, codec) {
134
111
  /**
135
112
  * Retrieves information about the current running screen recording.
136
113
  * If no screen recording is running then `null` is returned.
137
- *
138
- * @returns {Promise<XcTestScreenRecordingInfo?>}
139
114
  */
140
- export async function mobileGetXctestScreenRecordingInfo() {
141
- return /** @type {XcTestScreenRecordingInfo?} */ (
142
- await this.proxyCommand('/wda/video', 'GET')
143
- );
115
+ export async function mobileGetXctestScreenRecordingInfo(
116
+ this: XCUITestDriver,
117
+ ): Promise<XcTestScreenRecordingInfo | null> {
118
+ return (await this.proxyCommand('/wda/video', 'GET')) as XcTestScreenRecordingInfo | null;
144
119
  }
145
120
 
146
121
  /**
@@ -157,29 +132,37 @@ export async function mobileGetXctestScreenRecordingInfo() {
157
132
  * on the device directly or by doing factory reset.
158
133
  *
159
134
  * @since Xcode 15/iOS 17
160
- * @param {string} [remotePath] The path to the remote location, where the resulting video should be
135
+ * @param remotePath - The path to the remote location, where the resulting video should be
161
136
  * uploaded.
162
137
  * The following protocols are supported: `http`, `https`, `ftp`. Null or empty
163
138
  * string value (the default setting) means the content of resulting file
164
139
  * should be encoded as Base64 and passed as the endpoint response value. An
165
140
  * exception will be thrown if the generated media file is too big to fit into
166
141
  * the available process memory.
167
- * @param {string} [user] The name of the user for the remote authentication.
142
+ * @param user - The name of the user for the remote authentication.
168
143
  * Only works if `remotePath` is provided.
169
- * @param {string} [pass] The password for the remote authentication.
144
+ * @param pass - The password for the remote authentication.
170
145
  * Only works if `remotePath` is provided.
171
- * @param {import('@appium/types').HTTPHeaders} [headers] Additional headers mapping for multipart http(s) uploads
172
- * @param {string} [fileFieldName] The name of the form field where the file content BLOB should be stored for
146
+ * @param headers - Additional headers mapping for multipart http(s) uploads
147
+ * @param fileFieldName - The name of the form field where the file content BLOB should be stored for
173
148
  * http(s) uploads
174
- * @param {Record<string, any> | [string, any][]} [formFields] Additional form fields for multipart http(s) uploads
175
- * @param {'PUT' | 'POST' | 'PATCH'} [method='PUT'] The http multipart upload method name.
149
+ * @param formFields - Additional form fields for multipart http(s) uploads
150
+ * @param method - The http multipart upload method name.
176
151
  * Only works if `remotePath` is provided.
177
- * @returns {Promise<XcTestScreenRecording>}
152
+ * @returns The resulting movie with base64-encoded content or empty string if uploaded remotely.
178
153
  * @throws {Error} If there was an error while retrieving the video
179
154
  * file or the file content cannot be uploaded to the remote location.
180
- * @this {XCUITestDriver}
181
155
  */
182
- export async function mobileStopXctestScreenRecording(remotePath, user, pass, headers, fileFieldName, formFields, method) {
156
+ export async function mobileStopXctestScreenRecording(
157
+ this: XCUITestDriver,
158
+ remotePath?: string,
159
+ user?: string,
160
+ pass?: string,
161
+ headers?: HTTPHeaders,
162
+ fileFieldName?: string,
163
+ formFields?: Record<string, any> | [string, any][],
164
+ method: 'PUT' | 'POST' | 'PATCH' = 'PUT',
165
+ ): Promise<XcTestScreenRecording> {
183
166
  const screenRecordingInfo = await this.mobileGetXctestScreenRecordingInfo();
184
167
  if (!screenRecordingInfo) {
185
168
  throw new Error('There is no active screen recording. Did you start one beforehand?');
@@ -187,8 +170,11 @@ export async function mobileStopXctestScreenRecording(remotePath, user, pass, he
187
170
 
188
171
  this.log.debug(`Stopping the active screen recording: ${JSON.stringify(screenRecordingInfo)}`);
189
172
  await this.proxyCommand('/wda/video/stop', 'POST', {});
190
- const videoPath = await retrieveXcTestScreenRecording.bind(this)(screenRecordingInfo.uuid);
191
- const result = /** @type {XcTestScreenRecording} */ (screenRecordingInfo);
173
+ const videoPath = await retrieveXcTestScreenRecording.call(this, screenRecordingInfo.uuid);
174
+ const result: XcTestScreenRecording = {
175
+ ...screenRecordingInfo,
176
+ payload: '', // Will be set below
177
+ };
192
178
  try {
193
179
  result.payload = await encodeBase64OrUpload(videoPath, remotePath, {
194
180
  user, pass, headers, fileFieldName, formFields, method
@@ -199,6 +185,3 @@ export async function mobileStopXctestScreenRecording(remotePath, user, pass, he
199
185
  return result;
200
186
  }
201
187
 
202
- /**
203
- * @typedef {import('../driver').XCUITestDriver} XCUITestDriver
204
- */
package/lib/driver.ts CHANGED
@@ -121,7 +121,7 @@ import type { PerfRecorder } from './commands/performance';
121
121
  import type { AudioRecorder } from './commands/record-audio';
122
122
  import type { TrafficCapture } from './commands/pcap';
123
123
  import type { ScreenRecorder } from './commands/recordscreen';
124
- import type { DVTServiceWithConnection } from './commands/condition.js';
124
+ import type { DVTServiceWithConnection } from 'appium-ios-remotexpc';
125
125
  import type { IOSDeviceLog } from './device/log/ios-device-log';
126
126
  import type { IOSSimulatorLog } from './device/log/ios-simulator-log';
127
127
  import type { IOSCrashLog } from './device/log/ios-crash-log';