appium-mac2-driver 3.4.0 → 3.4.2

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 CHANGED
@@ -1,3 +1,15 @@
1
+ ## [3.4.2](https://github.com/appium/appium-mac2-driver/compare/v3.4.1...v3.4.2) (2026-05-13)
2
+
3
+ ### Bug Fixes
4
+
5
+ * native video broadcast cleanup ([#384](https://github.com/appium/appium-mac2-driver/issues/384)) ([ea81c2f](https://github.com/appium/appium-mac2-driver/commit/ea81c2fd7b5177de2f8d7ae9f157c1ec73c55d3d))
6
+
7
+ ## [3.4.1](https://github.com/appium/appium-mac2-driver/compare/v3.4.0...v3.4.1) (2026-05-13)
8
+
9
+ ### Bug Fixes
10
+
11
+ * Unhandled error for native video recording ([#383](https://github.com/appium/appium-mac2-driver/issues/383)) ([22a8bff](https://github.com/appium/appium-mac2-driver/commit/22a8bff4efc731b81737fa6d5c36de63582db4f3))
12
+
1
13
  ## [3.4.0](https://github.com/appium/appium-mac2-driver/compare/v3.3.4...v3.4.0) (2026-05-09)
2
14
 
3
15
  ### Features
@@ -13,18 +13,54 @@ interface DisplayInfo {
13
13
  isMain: boolean;
14
14
  }
15
15
  export declare class NativeVideoChunksBroadcaster {
16
- private _ee;
17
- private _log;
18
- private _publishers;
16
+ private readonly _ee;
17
+ private readonly _log;
18
+ private readonly _publishers;
19
+ private readonly _wakeups;
20
+ private readonly _terminationRequests;
19
21
  private _terminated;
20
22
  constructor(ee: EventEmitter, log: AppiumLogger);
21
23
  get hasPublishers(): boolean;
22
24
  schedule(uuid: string): void;
25
+ /**
26
+ * Signals the publisher for `uuid` that its underlying recording has been
27
+ * stopped. The publisher wakes from its current polling sleep, performs a
28
+ * final stat + read + emit pass to flush any remaining bytes of the
29
+ * recording file to BiDi consumers, and exits.
30
+ *
31
+ * Idempotent and safe to call for an unknown uuid (no-op in that case).
32
+ *
33
+ * Callers SHOULD invoke this immediately after the recording has been
34
+ * stopped on the underlying XCTest side; `waitFor(uuid)` is then
35
+ * contractually guaranteed to resolve as soon as the publisher has
36
+ * finished flushing, without having to wait for the next polling tick.
37
+ */
38
+ notifyStopped(uuid: string): void;
23
39
  waitFor(uuid: string): Promise<void>;
24
40
  shutdown(timeoutMs: number): Promise<void>;
41
+ /**
42
+ * Background monitor for a native screen recording attachment.
43
+ *
44
+ * This is scheduled as a fire-and-forget task by {@link schedule}; the
45
+ * promise is removed from `_publishers` when the monitor exits (see
46
+ * {@link _finishPublisher}). To make sure a failure here can never surface as an
47
+ * unhandled promise rejection (which terminates the Appium server process
48
+ * on Node.js 24+), this method MUST never reject: every error path logs a
49
+ * warning and returns normally.
50
+ */
25
51
  private _createPublisher;
26
52
  private _wait;
27
53
  private _cleanup;
54
+ /**
55
+ * Runs {@link _createPublisher} and always clears `_publishers` /
56
+ * `_wakeups` / `_terminationRequests` for `uuid` when the monitor exits.
57
+ */
58
+ private _runPublisher;
59
+ /**
60
+ * Drops all bookkeeping for a finished publisher so {@link hasPublishers}
61
+ * reflects reality and `notifyStopped` state does not leak across sessions.
62
+ */
63
+ private _finishPublisher;
28
64
  }
29
65
  /**
30
66
  * Initiates a new native screen recording session via XCTest.
@@ -1 +1 @@
1
- {"version":3,"file":"native-record-screen.d.ts","sourceRoot":"","sources":["../../../lib/commands/native-record-screen.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,WAAW,CAAC;AAE1C,OAAO,KAAK,EAAC,YAAY,EAAE,YAAY,EAAC,MAAM,eAAe,CAAC;AAC9D,OAAO,KAAK,YAAY,MAAM,aAAa,CAAC;AAY5C,UAAU,eAAe;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,UAAU,WAAW;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,qBAAa,4BAA4B;IACvC,OAAO,CAAC,GAAG,CAAe;IAC1B,OAAO,CAAC,IAAI,CAAe;IAC3B,OAAO,CAAC,WAAW,CAA6B;IAChD,OAAO,CAAC,WAAW,CAAU;gBAEjB,EAAE,EAAE,YAAY,EAAE,GAAG,EAAE,YAAY;IAO/C,IAAI,aAAa,IAAI,OAAO,CAE3B;IAED,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAMtB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAOpC,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;YAYlC,gBAAgB;YAgEhB,KAAK;YAuBL,QAAQ;CA2BvB;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,+BAA+B,CACnD,IAAI,EAAE,UAAU,EAChB,GAAG,CAAC,EAAE,MAAM,EACZ,KAAK,CAAC,EAAE,MAAM,EACd,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,eAAe,CAAC,CAQ1B;AAED;;;;GAIG;AACH,wBAAsB,iCAAiC,CACrD,IAAI,EAAE,UAAU,GACf,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAEjC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAsB,8BAA8B,CAClD,IAAI,EAAE,UAAU,EAChB,UAAU,CAAC,EAAE,MAAM,EACnB,IAAI,CAAC,EAAE,MAAM,EACb,IAAI,CAAC,EAAE,MAAM,EACb,MAAM,CAAC,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,YAAY,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,EACxC,aAAa,CAAC,EAAE,MAAM,EACtB,UAAU,CAAC,EAAE,YAAY,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,EAC9C,aAAa,CAAC,EAAE,OAAO,GACtB,OAAO,CAAC,MAAM,CAAC,CAkDjB;AAED;;;;GAIG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,YAAY,CAAC,WAAW,CAAC,CAAC,CAE5F"}
1
+ {"version":3,"file":"native-record-screen.d.ts","sourceRoot":"","sources":["../../../lib/commands/native-record-screen.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,WAAW,CAAC;AAE1C,OAAO,KAAK,EAAC,YAAY,EAAE,YAAY,EAAC,MAAM,eAAe,CAAC;AAC9D,OAAO,KAAK,YAAY,MAAM,aAAa,CAAC;AAc5C,UAAU,eAAe;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,UAAU,WAAW;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,qBAAa,4BAA4B;IACvC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;IACnC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAe;IACpC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA6B;IAIzD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAwC;IAKjE,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAAc;IACnD,OAAO,CAAC,WAAW,CAAU;gBAEjB,EAAE,EAAE,YAAY,EAAE,GAAG,EAAE,YAAY;IAS/C,IAAI,aAAa,IAAI,OAAO,CAE3B;IAED,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAM5B;;;;;;;;;;;;OAYG;IACH,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAQ3B,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAOpC,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAuBhD;;;;;;;;;OASG;YACW,gBAAgB;YA6GhB,KAAK;YAiBL,QAAQ;IA6BtB;;;OAGG;YACW,aAAa;IAQ3B;;;OAGG;IACH,OAAO,CAAC,gBAAgB;CAKzB;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,+BAA+B,CACnD,IAAI,EAAE,UAAU,EAChB,GAAG,CAAC,EAAE,MAAM,EACZ,KAAK,CAAC,EAAE,MAAM,EACd,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,eAAe,CAAC,CAQ1B;AAED;;;;GAIG;AACH,wBAAsB,iCAAiC,CACrD,IAAI,EAAE,UAAU,GACf,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAEjC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAsB,8BAA8B,CAClD,IAAI,EAAE,UAAU,EAChB,UAAU,CAAC,EAAE,MAAM,EACnB,IAAI,CAAC,EAAE,MAAM,EACb,IAAI,CAAC,EAAE,MAAM,EACb,MAAM,CAAC,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,YAAY,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,EACxC,aAAa,CAAC,EAAE,MAAM,EACtB,UAAU,CAAC,EAAE,YAAY,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,EAC9C,aAAa,CAAC,EAAE,OAAO,GACtB,OAAO,CAAC,MAAM,CAAC,CAuDjB;AAED;;;;GAIG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,YAAY,CAAC,WAAW,CAAC,CAAC,CAE5F"}
@@ -8,7 +8,6 @@ exports.macosStartNativeScreenRecording = macosStartNativeScreenRecording;
8
8
  exports.macosGetNativeScreenRecordingInfo = macosGetNativeScreenRecordingInfo;
9
9
  exports.macosStopNativeScreenRecording = macosStopNativeScreenRecording;
10
10
  exports.macosListDisplays = macosListDisplays;
11
- const promises_1 = require("node:timers/promises");
12
11
  const node_path_1 = __importDefault(require("node:path"));
13
12
  const support_1 = require("appium/support");
14
13
  const helpers_1 = require("./helpers");
@@ -21,15 +20,27 @@ const RECORDING_STARTUP_TIMEOUT_MS = 5000;
21
20
  const BUFFER_SIZE = 0xffff;
22
21
  const MONITORING_INTERVAL_DURATION_MS = 1000;
23
22
  const MAX_MONITORING_DURATION_MS = 24 * 60 * 60 * 1000; // 1 day
23
+ const STOP_WAIT_TIMEOUT_MS = 5000;
24
24
  class NativeVideoChunksBroadcaster {
25
25
  _ee;
26
26
  _log;
27
27
  _publishers;
28
+ // The currently scheduled polling sleep of each publisher, indexed by uuid.
29
+ // Cancelling it wakes the publisher up before the next polling tick so it
30
+ // can perform a final stat + read + emit pass and exit.
31
+ _wakeups;
32
+ // Set of uuids whose underlying recording has been explicitly stopped via
33
+ // `notifyStopped`. The publisher uses this as the primary completion
34
+ // signal; the `lsof` check is only consulted as a fallback for the case
35
+ // where the host XCTest process disappears without anyone telling us.
36
+ _terminationRequests;
28
37
  _terminated;
29
38
  constructor(ee, log) {
30
39
  this._ee = ee;
31
40
  this._log = log;
32
41
  this._publishers = new Map();
42
+ this._wakeups = new Map();
43
+ this._terminationRequests = new Set();
33
44
  this._terminated = false;
34
45
  }
35
46
  get hasPublishers() {
@@ -37,9 +48,29 @@ class NativeVideoChunksBroadcaster {
37
48
  }
38
49
  schedule(uuid) {
39
50
  if (!this._publishers.has(uuid)) {
40
- this._publishers.set(uuid, this._createPublisher(uuid));
51
+ this._publishers.set(uuid, this._runPublisher(uuid));
41
52
  }
42
53
  }
54
+ /**
55
+ * Signals the publisher for `uuid` that its underlying recording has been
56
+ * stopped. The publisher wakes from its current polling sleep, performs a
57
+ * final stat + read + emit pass to flush any remaining bytes of the
58
+ * recording file to BiDi consumers, and exits.
59
+ *
60
+ * Idempotent and safe to call for an unknown uuid (no-op in that case).
61
+ *
62
+ * Callers SHOULD invoke this immediately after the recording has been
63
+ * stopped on the underlying XCTest side; `waitFor(uuid)` is then
64
+ * contractually guaranteed to resolve as soon as the publisher has
65
+ * finished flushing, without having to wait for the next polling tick.
66
+ */
67
+ notifyStopped(uuid) {
68
+ if (!this._publishers.has(uuid)) {
69
+ return;
70
+ }
71
+ this._terminationRequests.add(uuid);
72
+ this._wakeups.get(uuid)?.cancel();
73
+ }
43
74
  async waitFor(uuid) {
44
75
  const publisher = this._publishers.get(uuid);
45
76
  if (publisher) {
@@ -47,18 +78,38 @@ class NativeVideoChunksBroadcaster {
47
78
  }
48
79
  }
49
80
  async shutdown(timeoutMs) {
81
+ // Snapshot keys before publishers remove themselves from `_publishers`
82
+ // in `_finishPublisher`, so we still know which attachment files to
83
+ // delete after `_wait` completes.
84
+ const uuidsAtShutdown = [...this._publishers.keys()];
85
+ // Wake up every active publisher so they don't have to wait for their
86
+ // next polling tick before flushing and exiting.
87
+ for (const uuid of uuidsAtShutdown) {
88
+ this.notifyStopped(uuid);
89
+ }
50
90
  try {
51
91
  await this._wait(timeoutMs);
52
92
  }
53
93
  catch (e) {
54
94
  this._log.warn(e instanceof Error ? e.message : String(e));
55
95
  }
56
- await this._cleanup();
57
- this._publishers = new Map();
96
+ await this._cleanup(uuidsAtShutdown);
97
+ this._publishers.clear();
98
+ this._wakeups.clear();
99
+ this._terminationRequests.clear();
58
100
  }
101
+ /**
102
+ * Background monitor for a native screen recording attachment.
103
+ *
104
+ * This is scheduled as a fire-and-forget task by {@link schedule}; the
105
+ * promise is removed from `_publishers` when the monitor exits (see
106
+ * {@link _finishPublisher}). To make sure a failure here can never surface as an
107
+ * unhandled promise rejection (which terminates the Appium server process
108
+ * on Node.js 24+), this method MUST never reject: every error path logs a
109
+ * warning and returns normally.
110
+ */
59
111
  async _createPublisher(uuid) {
60
112
  let fullPath = '';
61
- let bytesRead = 0n;
62
113
  try {
63
114
  await (0, asyncbox_1.waitForCondition)(async () => {
64
115
  const paths = await listAttachments();
@@ -74,33 +125,78 @@ class NativeVideoChunksBroadcaster {
74
125
  });
75
126
  }
76
127
  catch {
77
- throw new Error(`The video recording identified by ${uuid} did not ` +
128
+ this._log.warn(`The video recording identified by ${uuid} did not ` +
78
129
  `start within ${RECORDING_STARTUP_TIMEOUT_MS}ms timeout`);
130
+ return;
79
131
  }
132
+ let bytesRead = 0n;
80
133
  const startedMs = performance.now();
81
- while (!this._terminated && performance.now() - startedMs < MAX_MONITORING_DURATION_MS) {
82
- const isCompleted = !(await isFileUsed(fullPath, 'testman'));
83
- const { size } = await support_1.fs.stat(fullPath, { bigint: true });
84
- if (bytesRead < size) {
85
- const handle = await support_1.fs.open(fullPath, 'r');
134
+ try {
135
+ while (!this._terminated && performance.now() - startedMs < MAX_MONITORING_DURATION_MS) {
136
+ // Primary completion signal: `notifyStopped(uuid)` was called.
137
+ // Secondary fallback: lsof reports no `testman` process holding
138
+ // the recording file open (e.g. the host process exited without
139
+ // anyone telling us — see appium/appium#22269).
140
+ const stopRequested = this._terminationRequests.has(uuid);
141
+ const isCompleted = stopRequested || !(await isFileUsed(fullPath, 'testman'));
142
+ let size;
143
+ try {
144
+ ({ size } = await support_1.fs.stat(fullPath, { bigint: true }));
145
+ }
146
+ catch (e) {
147
+ // The recording file may have been removed by the OS once the
148
+ // host XCTest process owning the attachment has exited. In that
149
+ // case there is nothing more to read and we should stop monitoring.
150
+ if (e?.code === 'ENOENT') {
151
+ this._log.debug(`The native video recording file for ${uuid} is no longer available. ` +
152
+ `Assuming the recording has been terminated`);
153
+ return;
154
+ }
155
+ throw e;
156
+ }
157
+ if (bytesRead < size) {
158
+ const handle = await support_1.fs.open(fullPath, 'r');
159
+ try {
160
+ while (bytesRead < size) {
161
+ const bufferSize = Number(size - bytesRead > BUFFER_SIZE ? BUFFER_SIZE : size - bytesRead);
162
+ const buf = Buffer.alloc(bufferSize);
163
+ await support_1.fs.read(handle, buf, 0, bufferSize, bytesRead);
164
+ this._ee.emit(constants_1.BIDI_EVENT_NAME, (0, models_1.toNativeVideoChunkAddedEvent)(uuid, buf));
165
+ bytesRead += BigInt(bufferSize);
166
+ }
167
+ }
168
+ finally {
169
+ await support_1.fs.close(handle);
170
+ }
171
+ }
172
+ if (isCompleted) {
173
+ this._log.debug(`The native video recording identified by ${uuid} has been detected as completed`);
174
+ return;
175
+ }
176
+ // Cancellable sleep until the next polling tick. `notifyStopped`
177
+ // cancels this wakeup so the loop re-runs immediately and detects
178
+ // termination on the very next iteration. `cancelError: null` makes
179
+ // the cancel resolve the promise instead of rejecting it.
180
+ const wakeup = (0, asyncbox_1.sleep)({ ms: MONITORING_INTERVAL_DURATION_MS, cancelError: null });
181
+ this._wakeups.set(uuid, wakeup);
86
182
  try {
87
- while (bytesRead < size) {
88
- const bufferSize = Number(size - bytesRead > BUFFER_SIZE ? BUFFER_SIZE : size - bytesRead);
89
- const buf = Buffer.alloc(bufferSize);
90
- await support_1.fs.read(handle, buf, 0, bufferSize, bytesRead);
91
- this._ee.emit(constants_1.BIDI_EVENT_NAME, (0, models_1.toNativeVideoChunkAddedEvent)(uuid, buf));
92
- bytesRead += BigInt(bufferSize);
183
+ // Guard against a notifyStopped() that landed between the
184
+ // iteration's stop-check above and the wakeup being stored:
185
+ // the cancel above would have been a no-op, so cancel here.
186
+ if (this._terminationRequests.has(uuid)) {
187
+ wakeup.cancel();
93
188
  }
189
+ await wakeup;
94
190
  }
95
191
  finally {
96
- await support_1.fs.close(handle);
192
+ this._wakeups.delete(uuid);
97
193
  }
98
194
  }
99
- if (isCompleted) {
100
- this._log.debug(`The native video recording identified by ${uuid} has been detected as completed`);
101
- return;
102
- }
103
- await (0, promises_1.setTimeout)(MONITORING_INTERVAL_DURATION_MS);
195
+ }
196
+ catch (e) {
197
+ this._log.warn(`Native video chunks publisher for ${uuid} stopped unexpectedly: ` +
198
+ (e instanceof Error ? e.message : String(e)));
199
+ return;
104
200
  }
105
201
  this._log.warn(`Stopped monitoring of the native video recording identified by ${uuid} ` +
106
202
  `because of the timeout`);
@@ -112,31 +208,27 @@ class NativeVideoChunksBroadcaster {
112
208
  const timer = setTimeout(() => {
113
209
  this._terminated = true;
114
210
  }, timeoutMs);
115
- const publishingErrors = [];
116
- for (const publisher of this._publishers.values()) {
117
- try {
118
- await publisher;
119
- }
120
- catch (e) {
121
- publishingErrors.push(e instanceof Error ? e.message : String(e));
122
- }
211
+ try {
212
+ // Publishers are guaranteed by `_createPublisher` to never reject,
213
+ // so we can simply await all of them.
214
+ await Promise.all(this._publishers.values());
123
215
  }
124
- clearTimeout(timer);
125
- if (publishingErrors.length > 0) {
126
- throw new Error(publishingErrors.join('\n'));
216
+ finally {
217
+ clearTimeout(timer);
127
218
  }
128
219
  }
129
- async _cleanup() {
130
- if (!this.hasPublishers) {
220
+ async _cleanup(uuids) {
221
+ if (uuids.length === 0) {
131
222
  return;
132
223
  }
224
+ const uuidSet = new Set(uuids);
133
225
  const attachments = await listAttachments();
134
226
  if (attachments.length === 0) {
135
227
  return;
136
228
  }
137
229
  const tasks = attachments
138
230
  .map((attachmentPath) => [node_path_1.default.basename(attachmentPath), attachmentPath])
139
- .filter(([name]) => this._publishers.has(name))
231
+ .filter(([name]) => uuidSet.has(name))
140
232
  .map(([, attachmentPath]) => support_1.fs.rimraf(attachmentPath));
141
233
  if (tasks.length === 0) {
142
234
  return;
@@ -149,6 +241,27 @@ class NativeVideoChunksBroadcaster {
149
241
  this._log.warn(`Could not cleanup some leftover video recordings: ${e instanceof Error ? e.message : String(e)}`);
150
242
  }
151
243
  }
244
+ /**
245
+ * Runs {@link _createPublisher} and always clears `_publishers` /
246
+ * `_wakeups` / `_terminationRequests` for `uuid` when the monitor exits.
247
+ */
248
+ async _runPublisher(uuid) {
249
+ try {
250
+ await this._createPublisher(uuid);
251
+ }
252
+ finally {
253
+ this._finishPublisher(uuid);
254
+ }
255
+ }
256
+ /**
257
+ * Drops all bookkeeping for a finished publisher so {@link hasPublishers}
258
+ * reflects reality and `notifyStopped` state does not leak across sessions.
259
+ */
260
+ _finishPublisher(uuid) {
261
+ this._publishers.delete(uuid);
262
+ this._wakeups.delete(uuid);
263
+ this._terminationRequests.delete(uuid);
264
+ }
152
265
  }
153
266
  exports.NativeVideoChunksBroadcaster = NativeVideoChunksBroadcaster;
154
267
  /**
@@ -216,13 +329,18 @@ async function macosStopNativeScreenRecording(remotePath, user, pass, method, he
216
329
  throw new Error('There is no active screen recording, thus nothing to stop. Did you start it before?');
217
330
  }
218
331
  const { uuid } = response;
332
+ // Wake the publisher up so it stops polling and flushes any remaining
333
+ // bytes immediately. Without this, `waitFor` below could still block
334
+ // for up to `MONITORING_INTERVAL_DURATION_MS` while the publisher sits
335
+ // in its polling sleep.
336
+ this._videoChunksBroadcaster.notifyStopped(uuid);
219
337
  try {
220
- await (0, asyncbox_1.withTimeout)(this._videoChunksBroadcaster.waitFor(uuid), 5000);
338
+ await (0, asyncbox_1.withTimeout)(this._videoChunksBroadcaster.waitFor(uuid), STOP_WAIT_TIMEOUT_MS);
221
339
  }
222
340
  catch (e) {
223
341
  if (e instanceof asyncbox_1.TimeoutError) {
224
342
  this.log.debug(`The BiDi chunks broadcaster for the native screen recording identified ` +
225
- `by ${uuid} cannot complete within 5000ms timeout`);
343
+ `by ${uuid} cannot complete within ${STOP_WAIT_TIMEOUT_MS}ms timeout`);
226
344
  }
227
345
  else {
228
346
  this.log.debug(e instanceof Error ? e.stack : String(e));
@@ -268,7 +386,12 @@ async function listAttachments() {
268
386
  });
269
387
  }
270
388
  async function isFileUsed(fpath, userProcessName) {
271
- const { stdout } = await (0, teen_process_1.exec)('lsof', [fpath]);
272
- return stdout.includes(userProcessName);
389
+ try {
390
+ const { stdout } = await (0, teen_process_1.exec)('lsof', [fpath]);
391
+ return stdout.includes(userProcessName);
392
+ }
393
+ catch {
394
+ return false;
395
+ }
273
396
  }
274
397
  //# sourceMappingURL=native-record-screen.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"native-record-screen.js","sourceRoot":"","sources":["../../../lib/commands/native-record-screen.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"native-record-screen.js","sourceRoot":"","sources":["../../../lib/commands/native-record-screen.ts"],"names":[],"mappings":"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"}
@@ -1,11 +1,11 @@
1
- import {setTimeout as delay} from 'node:timers/promises';
2
1
  import path from 'node:path';
3
2
  import {fs, util} from 'appium/support';
4
3
  import type {Mac2Driver} from '../driver';
5
4
  import {uploadRecordedMedia} from './helpers';
6
5
  import type {AppiumLogger, StringRecord} from '@appium/types';
7
6
  import type EventEmitter from 'node:events';
8
- import {TimeoutError, waitForCondition, withTimeout} from 'asyncbox';
7
+ import type {CancellablePromise} from 'asyncbox';
8
+ import {TimeoutError, sleep, waitForCondition, withTimeout} from 'asyncbox';
9
9
  import {exec} from 'teen_process';
10
10
  import {BIDI_EVENT_NAME} from './bidi/constants';
11
11
  import {toNativeVideoChunkAddedEvent} from './bidi/models';
@@ -15,6 +15,7 @@ const RECORDING_STARTUP_TIMEOUT_MS = 5000;
15
15
  const BUFFER_SIZE = 0xffff;
16
16
  const MONITORING_INTERVAL_DURATION_MS = 1000;
17
17
  const MAX_MONITORING_DURATION_MS = 24 * 60 * 60 * 1000; // 1 day
18
+ const STOP_WAIT_TIMEOUT_MS = 5000;
18
19
 
19
20
  interface ActiveVideoInfo {
20
21
  fps: number;
@@ -30,15 +31,26 @@ interface DisplayInfo {
30
31
  }
31
32
 
32
33
  export class NativeVideoChunksBroadcaster {
33
- private _ee: EventEmitter;
34
- private _log: AppiumLogger;
35
- private _publishers: Map<string, Promise<void>>;
34
+ private readonly _ee: EventEmitter;
35
+ private readonly _log: AppiumLogger;
36
+ private readonly _publishers: Map<string, Promise<void>>;
37
+ // The currently scheduled polling sleep of each publisher, indexed by uuid.
38
+ // Cancelling it wakes the publisher up before the next polling tick so it
39
+ // can perform a final stat + read + emit pass and exit.
40
+ private readonly _wakeups: Map<string, CancellablePromise<void>>;
41
+ // Set of uuids whose underlying recording has been explicitly stopped via
42
+ // `notifyStopped`. The publisher uses this as the primary completion
43
+ // signal; the `lsof` check is only consulted as a fallback for the case
44
+ // where the host XCTest process disappears without anyone telling us.
45
+ private readonly _terminationRequests: Set<string>;
36
46
  private _terminated: boolean;
37
47
 
38
48
  constructor(ee: EventEmitter, log: AppiumLogger) {
39
49
  this._ee = ee;
40
50
  this._log = log;
41
51
  this._publishers = new Map();
52
+ this._wakeups = new Map();
53
+ this._terminationRequests = new Set();
42
54
  this._terminated = false;
43
55
  }
44
56
 
@@ -48,10 +60,31 @@ export class NativeVideoChunksBroadcaster {
48
60
 
49
61
  schedule(uuid: string): void {
50
62
  if (!this._publishers.has(uuid)) {
51
- this._publishers.set(uuid, this._createPublisher(uuid));
63
+ this._publishers.set(uuid, this._runPublisher(uuid));
52
64
  }
53
65
  }
54
66
 
67
+ /**
68
+ * Signals the publisher for `uuid` that its underlying recording has been
69
+ * stopped. The publisher wakes from its current polling sleep, performs a
70
+ * final stat + read + emit pass to flush any remaining bytes of the
71
+ * recording file to BiDi consumers, and exits.
72
+ *
73
+ * Idempotent and safe to call for an unknown uuid (no-op in that case).
74
+ *
75
+ * Callers SHOULD invoke this immediately after the recording has been
76
+ * stopped on the underlying XCTest side; `waitFor(uuid)` is then
77
+ * contractually guaranteed to resolve as soon as the publisher has
78
+ * finished flushing, without having to wait for the next polling tick.
79
+ */
80
+ notifyStopped(uuid: string): void {
81
+ if (!this._publishers.has(uuid)) {
82
+ return;
83
+ }
84
+ this._terminationRequests.add(uuid);
85
+ this._wakeups.get(uuid)?.cancel();
86
+ }
87
+
55
88
  async waitFor(uuid: string): Promise<void> {
56
89
  const publisher = this._publishers.get(uuid);
57
90
  if (publisher) {
@@ -60,20 +93,40 @@ export class NativeVideoChunksBroadcaster {
60
93
  }
61
94
 
62
95
  async shutdown(timeoutMs: number): Promise<void> {
96
+ // Snapshot keys before publishers remove themselves from `_publishers`
97
+ // in `_finishPublisher`, so we still know which attachment files to
98
+ // delete after `_wait` completes.
99
+ const uuidsAtShutdown = [...this._publishers.keys()];
100
+ // Wake up every active publisher so they don't have to wait for their
101
+ // next polling tick before flushing and exiting.
102
+ for (const uuid of uuidsAtShutdown) {
103
+ this.notifyStopped(uuid);
104
+ }
63
105
  try {
64
106
  await this._wait(timeoutMs);
65
107
  } catch (e) {
66
108
  this._log.warn(e instanceof Error ? e.message : String(e));
67
109
  }
68
110
 
69
- await this._cleanup();
111
+ await this._cleanup(uuidsAtShutdown);
70
112
 
71
- this._publishers = new Map();
113
+ this._publishers.clear();
114
+ this._wakeups.clear();
115
+ this._terminationRequests.clear();
72
116
  }
73
117
 
118
+ /**
119
+ * Background monitor for a native screen recording attachment.
120
+ *
121
+ * This is scheduled as a fire-and-forget task by {@link schedule}; the
122
+ * promise is removed from `_publishers` when the monitor exits (see
123
+ * {@link _finishPublisher}). To make sure a failure here can never surface as an
124
+ * unhandled promise rejection (which terminates the Appium server process
125
+ * on Node.js 24+), this method MUST never reject: every error path logs a
126
+ * warning and returns normally.
127
+ */
74
128
  private async _createPublisher(uuid: string): Promise<void> {
75
129
  let fullPath = '';
76
- let bytesRead = 0n;
77
130
  try {
78
131
  await waitForCondition(
79
132
  async () => {
@@ -91,42 +144,88 @@ export class NativeVideoChunksBroadcaster {
91
144
  },
92
145
  );
93
146
  } catch {
94
- throw new Error(
147
+ this._log.warn(
95
148
  `The video recording identified by ${uuid} did not ` +
96
149
  `start within ${RECORDING_STARTUP_TIMEOUT_MS}ms timeout`,
97
150
  );
151
+ return;
98
152
  }
99
153
 
154
+ let bytesRead = 0n;
100
155
  const startedMs = performance.now();
101
- while (!this._terminated && performance.now() - startedMs < MAX_MONITORING_DURATION_MS) {
102
- const isCompleted = !(await isFileUsed(fullPath, 'testman'));
156
+ try {
157
+ while (!this._terminated && performance.now() - startedMs < MAX_MONITORING_DURATION_MS) {
158
+ // Primary completion signal: `notifyStopped(uuid)` was called.
159
+ // Secondary fallback: lsof reports no `testman` process holding
160
+ // the recording file open (e.g. the host process exited without
161
+ // anyone telling us — see appium/appium#22269).
162
+ const stopRequested = this._terminationRequests.has(uuid);
163
+ const isCompleted = stopRequested || !(await isFileUsed(fullPath, 'testman'));
103
164
 
104
- const {size} = await fs.stat(fullPath, {bigint: true});
105
- if (bytesRead < size) {
106
- const handle = await fs.open(fullPath, 'r');
165
+ let size: bigint;
107
166
  try {
108
- while (bytesRead < size) {
109
- const bufferSize = Number(
110
- size - bytesRead > BUFFER_SIZE ? BUFFER_SIZE : size - bytesRead,
167
+ ({size} = await fs.stat(fullPath, {bigint: true}));
168
+ } catch (e) {
169
+ // The recording file may have been removed by the OS once the
170
+ // host XCTest process owning the attachment has exited. In that
171
+ // case there is nothing more to read and we should stop monitoring.
172
+ if ((e as NodeJS.ErrnoException)?.code === 'ENOENT') {
173
+ this._log.debug(
174
+ `The native video recording file for ${uuid} is no longer available. ` +
175
+ `Assuming the recording has been terminated`,
111
176
  );
112
- const buf = Buffer.alloc(bufferSize);
113
- await fs.read(handle, buf as any, 0, bufferSize, bytesRead as any);
114
- this._ee.emit(BIDI_EVENT_NAME, toNativeVideoChunkAddedEvent(uuid, buf));
115
- bytesRead += BigInt(bufferSize);
177
+ return;
178
+ }
179
+ throw e;
180
+ }
181
+ if (bytesRead < size) {
182
+ const handle = await fs.open(fullPath, 'r');
183
+ try {
184
+ while (bytesRead < size) {
185
+ const bufferSize = Number(
186
+ size - bytesRead > BUFFER_SIZE ? BUFFER_SIZE : size - bytesRead,
187
+ );
188
+ const buf = Buffer.alloc(bufferSize);
189
+ await fs.read(handle, buf as any, 0, bufferSize, bytesRead as any);
190
+ this._ee.emit(BIDI_EVENT_NAME, toNativeVideoChunkAddedEvent(uuid, buf));
191
+ bytesRead += BigInt(bufferSize);
192
+ }
193
+ } finally {
194
+ await fs.close(handle);
116
195
  }
117
- } finally {
118
- await fs.close(handle);
119
196
  }
120
- }
121
197
 
122
- if (isCompleted) {
123
- this._log.debug(
124
- `The native video recording identified by ${uuid} has been detected as completed`,
125
- );
126
- return;
127
- }
198
+ if (isCompleted) {
199
+ this._log.debug(
200
+ `The native video recording identified by ${uuid} has been detected as completed`,
201
+ );
202
+ return;
203
+ }
128
204
 
129
- await delay(MONITORING_INTERVAL_DURATION_MS);
205
+ // Cancellable sleep until the next polling tick. `notifyStopped`
206
+ // cancels this wakeup so the loop re-runs immediately and detects
207
+ // termination on the very next iteration. `cancelError: null` makes
208
+ // the cancel resolve the promise instead of rejecting it.
209
+ const wakeup = sleep({ms: MONITORING_INTERVAL_DURATION_MS, cancelError: null});
210
+ this._wakeups.set(uuid, wakeup);
211
+ try {
212
+ // Guard against a notifyStopped() that landed between the
213
+ // iteration's stop-check above and the wakeup being stored:
214
+ // the cancel above would have been a no-op, so cancel here.
215
+ if (this._terminationRequests.has(uuid)) {
216
+ wakeup.cancel();
217
+ }
218
+ await wakeup;
219
+ } finally {
220
+ this._wakeups.delete(uuid);
221
+ }
222
+ }
223
+ } catch (e) {
224
+ this._log.warn(
225
+ `Native video chunks publisher for ${uuid} stopped unexpectedly: ` +
226
+ (e instanceof Error ? e.message : String(e)),
227
+ );
228
+ return;
130
229
  }
131
230
 
132
231
  this._log.warn(
@@ -143,25 +242,20 @@ export class NativeVideoChunksBroadcaster {
143
242
  const timer = setTimeout(() => {
144
243
  this._terminated = true;
145
244
  }, timeoutMs);
146
- const publishingErrors: string[] = [];
147
- for (const publisher of this._publishers.values()) {
148
- try {
149
- await publisher;
150
- } catch (e) {
151
- publishingErrors.push(e instanceof Error ? e.message : String(e));
152
- }
153
- }
154
- clearTimeout(timer);
155
-
156
- if (publishingErrors.length > 0) {
157
- throw new Error(publishingErrors.join('\n'));
245
+ try {
246
+ // Publishers are guaranteed by `_createPublisher` to never reject,
247
+ // so we can simply await all of them.
248
+ await Promise.all(this._publishers.values());
249
+ } finally {
250
+ clearTimeout(timer);
158
251
  }
159
252
  }
160
253
 
161
- private async _cleanup(): Promise<void> {
162
- if (!this.hasPublishers) {
254
+ private async _cleanup(uuids: readonly string[]): Promise<void> {
255
+ if (uuids.length === 0) {
163
256
  return;
164
257
  }
258
+ const uuidSet = new Set(uuids);
165
259
 
166
260
  const attachments = await listAttachments();
167
261
  if (attachments.length === 0) {
@@ -169,7 +263,7 @@ export class NativeVideoChunksBroadcaster {
169
263
  }
170
264
  const tasks: Promise<any>[] = attachments
171
265
  .map((attachmentPath) => [path.basename(attachmentPath), attachmentPath])
172
- .filter(([name]) => this._publishers.has(name))
266
+ .filter(([name]) => uuidSet.has(name))
173
267
  .map(([, attachmentPath]) => fs.rimraf(attachmentPath));
174
268
  if (tasks.length === 0) {
175
269
  return;
@@ -185,6 +279,28 @@ export class NativeVideoChunksBroadcaster {
185
279
  );
186
280
  }
187
281
  }
282
+
283
+ /**
284
+ * Runs {@link _createPublisher} and always clears `_publishers` /
285
+ * `_wakeups` / `_terminationRequests` for `uuid` when the monitor exits.
286
+ */
287
+ private async _runPublisher(uuid: string): Promise<void> {
288
+ try {
289
+ await this._createPublisher(uuid);
290
+ } finally {
291
+ this._finishPublisher(uuid);
292
+ }
293
+ }
294
+
295
+ /**
296
+ * Drops all bookkeeping for a finished publisher so {@link hasPublishers}
297
+ * reflects reality and `notifyStopped` state does not leak across sessions.
298
+ */
299
+ private _finishPublisher(uuid: string): void {
300
+ this._publishers.delete(uuid);
301
+ this._wakeups.delete(uuid);
302
+ this._terminationRequests.delete(uuid);
303
+ }
188
304
  }
189
305
 
190
306
  /**
@@ -278,13 +394,18 @@ export async function macosStopNativeScreenRecording(
278
394
  }
279
395
 
280
396
  const {uuid} = response;
397
+ // Wake the publisher up so it stops polling and flushes any remaining
398
+ // bytes immediately. Without this, `waitFor` below could still block
399
+ // for up to `MONITORING_INTERVAL_DURATION_MS` while the publisher sits
400
+ // in its polling sleep.
401
+ this._videoChunksBroadcaster.notifyStopped(uuid);
281
402
  try {
282
- await withTimeout(this._videoChunksBroadcaster.waitFor(uuid), 5000);
403
+ await withTimeout(this._videoChunksBroadcaster.waitFor(uuid), STOP_WAIT_TIMEOUT_MS);
283
404
  } catch (e) {
284
405
  if (e instanceof TimeoutError) {
285
406
  this.log.debug(
286
407
  `The BiDi chunks broadcaster for the native screen recording identified ` +
287
- `by ${uuid} cannot complete within 5000ms timeout`,
408
+ `by ${uuid} cannot complete within ${STOP_WAIT_TIMEOUT_MS}ms timeout`,
288
409
  );
289
410
  } else {
290
411
  this.log.debug(e instanceof Error ? e.stack : String(e));
@@ -341,6 +462,10 @@ async function listAttachments(): Promise<string[]> {
341
462
  }
342
463
 
343
464
  async function isFileUsed(fpath: string, userProcessName: string): Promise<boolean> {
344
- const {stdout} = await exec('lsof', [fpath]);
345
- return stdout.includes(userProcessName);
465
+ try {
466
+ const {stdout} = await exec('lsof', [fpath]);
467
+ return stdout.includes(userProcessName);
468
+ } catch {
469
+ return false;
470
+ }
346
471
  }
@@ -1,20 +1,20 @@
1
1
  {
2
2
  "name": "appium-mac2-driver",
3
- "version": "3.4.0",
3
+ "version": "3.4.2",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "appium-mac2-driver",
9
- "version": "3.4.0",
9
+ "version": "3.4.2",
10
10
  "license": "Apache-2.0",
11
11
  "dependencies": {
12
12
  "@appium/strongbox": "^1.0.0-rc.1",
13
13
  "@colors/colors": "^1.6.0",
14
14
  "appium-xcode": "^6.0.0",
15
- "asyncbox": "^6.0.1",
16
- "axios": "^1.x",
17
- "portscanner": "2.2.0",
15
+ "asyncbox": "^6.3.0",
16
+ "axios": "^1.16.0",
17
+ "portscanner": "^2.2.0",
18
18
  "teen_process": "^4.0.4"
19
19
  },
20
20
  "devDependencies": {
@@ -138,6 +138,19 @@
138
138
  "sharp": "0.34.5"
139
139
  }
140
140
  },
141
+ "node_modules/@appium/support/node_modules/asyncbox": {
142
+ "version": "6.2.0",
143
+ "resolved": "https://registry.npmjs.org/asyncbox/-/asyncbox-6.2.0.tgz",
144
+ "integrity": "sha512-z1XpHkoT3y+1aXfazEY5d7HN2eOi50fLq7ZTxG0H4WegLxrtEAI5Vsc6OR9dOwoC3SJQLXyV0ZVnPEh6GIgMKQ==",
145
+ "license": "Apache-2.0",
146
+ "dependencies": {
147
+ "p-limit": "^7.2.0"
148
+ },
149
+ "engines": {
150
+ "node": "^20.19.0 || ^22.12.0 || >=24.0.0",
151
+ "npm": ">=10"
152
+ }
153
+ },
141
154
  "node_modules/@appium/tsconfig": {
142
155
  "version": "1.1.2",
143
156
  "resolved": "https://registry.npmjs.org/@appium/tsconfig/-/tsconfig-1.1.2.tgz",
@@ -556,9 +569,9 @@
556
569
  "license": "MIT"
557
570
  },
558
571
  "node_modules/asyncbox": {
559
- "version": "6.2.0",
560
- "resolved": "https://registry.npmjs.org/asyncbox/-/asyncbox-6.2.0.tgz",
561
- "integrity": "sha512-z1XpHkoT3y+1aXfazEY5d7HN2eOi50fLq7ZTxG0H4WegLxrtEAI5Vsc6OR9dOwoC3SJQLXyV0ZVnPEh6GIgMKQ==",
572
+ "version": "6.3.0",
573
+ "resolved": "https://registry.npmjs.org/asyncbox/-/asyncbox-6.3.0.tgz",
574
+ "integrity": "sha512-7IFpnQDltd5rYQjhIJIpyismJtdWmw/pOABZKJfv2WVo0a6iYh2ZzUuCJJclae5mBtK0H/EychxXg91GB7rGdQ==",
562
575
  "license": "Apache-2.0",
563
576
  "dependencies": {
564
577
  "p-limit": "^7.2.0"
package/package.json CHANGED
@@ -6,7 +6,7 @@
6
6
  "mac",
7
7
  "XCTest"
8
8
  ],
9
- "version": "3.4.0",
9
+ "version": "3.4.2",
10
10
  "author": "Appium Contributors",
11
11
  "license": "Apache-2.0",
12
12
  "repository": {
@@ -49,9 +49,9 @@
49
49
  "@appium/strongbox": "^1.0.0-rc.1",
50
50
  "@colors/colors": "^1.6.0",
51
51
  "appium-xcode": "^6.0.0",
52
- "asyncbox": "^6.0.1",
53
- "axios": "^1.x",
54
- "portscanner": "2.2.0",
52
+ "asyncbox": "^6.3.0",
53
+ "axios": "^1.16.0",
54
+ "portscanner": "^2.2.0",
55
55
  "teen_process": "^4.0.4"
56
56
  },
57
57
  "scripts": {