appium-xcuitest-driver 12.1.0 → 12.1.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 +12 -0
- package/build/lib/commands/helpers/mjpeg.d.ts +15 -1
- package/build/lib/commands/helpers/mjpeg.d.ts.map +1 -1
- package/build/lib/commands/helpers/mjpeg.js +26 -6
- package/build/lib/commands/helpers/mjpeg.js.map +1 -1
- package/build/lib/commands/keyboard.d.ts +1 -1
- package/build/lib/commands/keyboard.d.ts.map +1 -1
- package/build/lib/commands/keyboard.js +1 -1
- package/build/lib/commands/keyboard.js.map +1 -1
- package/build/lib/commands/log.d.ts +1 -1
- package/build/lib/commands/log.js +1 -1
- package/lib/commands/helpers/mjpeg.ts +27 -6
- package/lib/commands/keyboard.ts +1 -1
- package/lib/commands/log.ts +1 -1
- package/npm-shrinkwrap.json +313 -387
- package/package.json +15 -20
- package/scripts/tunnel-creation.mjs +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
## [12.1.2](https://github.com/appium/appium-xcuitest-driver/compare/v12.1.1...v12.1.2) (2026-07-30)
|
|
2
|
+
|
|
3
|
+
### Miscellaneous Chores
|
|
4
|
+
|
|
5
|
+
* Harden MJPEG frame parsing and stream startup ([#2927](https://github.com/appium/appium-xcuitest-driver/issues/2927)) ([36bf6a0](https://github.com/appium/appium-xcuitest-driver/commit/36bf6a00f63265cd54b561e71ddc2bdae25da91e))
|
|
6
|
+
|
|
7
|
+
## [12.1.1](https://github.com/appium/appium-xcuitest-driver/compare/v12.1.0...v12.1.1) (2026-07-29)
|
|
8
|
+
|
|
9
|
+
### Miscellaneous Chores
|
|
10
|
+
|
|
11
|
+
* Integrate oxc and release configs ([#2925](https://github.com/appium/appium-xcuitest-driver/issues/2925)) ([6711a34](https://github.com/appium/appium-xcuitest-driver/commit/6711a34419402894d25cae00811da25cef6cc4ae))
|
|
12
|
+
|
|
1
13
|
## [12.1.0](https://github.com/appium/appium-xcuitest-driver/compare/v12.0.0...v12.1.0) (2026-07-24)
|
|
2
14
|
|
|
3
15
|
### Features
|
|
@@ -1,5 +1,19 @@
|
|
|
1
|
-
import { Writable, type WritableOptions } from 'node:stream';
|
|
1
|
+
import { Transform, Writable, type TransformCallback, type WritableOptions } from 'node:stream';
|
|
2
2
|
import type { XCUITestDriver } from '../../driver.js';
|
|
3
|
+
/**
|
|
4
|
+
* Extracts individual JPEG frames out of a multipart MJPEG-over-HTTP byte stream by
|
|
5
|
+
* scanning for JPEG start/end-of-image markers and the multipart `Content-Length` header.
|
|
6
|
+
*/
|
|
7
|
+
export declare class MjpegFrameParser extends Transform {
|
|
8
|
+
private buffer;
|
|
9
|
+
private expectedLength;
|
|
10
|
+
private bytesWritten;
|
|
11
|
+
private isReading;
|
|
12
|
+
_transform(chunk: Buffer, _encoding: BufferEncoding, callback: TransformCallback): void;
|
|
13
|
+
private startFrame;
|
|
14
|
+
private appendChunk;
|
|
15
|
+
private emitFrame;
|
|
16
|
+
}
|
|
3
17
|
/**
|
|
4
18
|
* Connects to an MJPEG-over-HTTP stream and keeps track of the last JPEG frame received,
|
|
5
19
|
* so that it can be used as a cheap, low-latency screenshot source.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mjpeg.d.ts","sourceRoot":"","sources":["../../../../lib/commands/helpers/mjpeg.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"mjpeg.d.ts","sourceRoot":"","sources":["../../../../lib/commands/helpers/mjpeg.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,SAAS,EAAE,QAAQ,EAAiB,KAAK,iBAAiB,EAAE,KAAK,eAAe,EAAC,MAAM,aAAa,CAAC;AAM7G,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,iBAAiB,CAAC;AAUpD;;;GAGG;AACH,qBAAa,gBAAiB,SAAQ,SAAS;IAC7C,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,cAAc,CAAK;IAC3B,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,SAAS,CAAS;IAGjB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,cAAc,EAAE,QAAQ,EAAE,iBAAiB,GAAG,IAAI;IAehG,OAAO,CAAC,UAAU;IAsBlB,OAAO,CAAC,WAAW;IAqBnB,OAAO,CAAC,SAAS;CAelB;AAwBD;;;GAGG;AACH,qBAAa,WAAY,SAAQ,QAAQ;IACvC,QAAQ,CAAC,YAAY,EAAE,CAAC,GAAG,EAAE,KAAK,KAAK,IAAI,CAAC;IAC5C,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,WAAW,CAAK;IACxB,OAAO,CAAC,SAAS,CAAuB;IACxC,OAAO,CAAC,oBAAoB,CAA6B;IACzD,OAAO,CAAC,oBAAoB,CAAuC;IACnE,OAAO,CAAC,cAAc,CAAyB;IAC/C,OAAO,CAAC,QAAQ,CAAiC;IAEjD;;;;OAIG;gBACS,QAAQ,EAAE,MAAM,EAAE,YAAY,GAAE,CAAC,GAAG,EAAE,KAAK,KAAK,IAAW,EAAE,OAAO,GAAE,eAAoB;IAOtG,IAAI,eAAe,IAAI,MAAM,GAAG,IAAI,CAGnC;IAEK,YAAY,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IActC,kBAAkB,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAKlD,KAAK,IAAI,IAAI;IASP,KAAK,CAAC,aAAa,SAA4B,GAAG,OAAO,CAAC,IAAI,CAAC;IA8ErE,IAAI,IAAI,IAAI;IAcH,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,SAAS,EAAE,cAAc,EAAE,QAAQ,EAAE,CAAC,KAAK,CAAC,EAAE,KAAK,GAAG,IAAI,KAAK,IAAI,GAAG,IAAI;CAUnH;AAED;;;GAGG;AACH,wBAAsB,kBAAkB,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAQ9E;AAED;;GAEG;AACH,wBAAsB,uBAAuB,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CA0BnF"}
|
|
@@ -11,7 +11,7 @@ const CONTENT_LENGTH_RE = /Content-Length:\s*(\d+)/i;
|
|
|
11
11
|
* Extracts individual JPEG frames out of a multipart MJPEG-over-HTTP byte stream by
|
|
12
12
|
* scanning for JPEG start/end-of-image markers and the multipart `Content-Length` header.
|
|
13
13
|
*/
|
|
14
|
-
class MjpegFrameParser extends Transform {
|
|
14
|
+
export class MjpegFrameParser extends Transform {
|
|
15
15
|
buffer = null;
|
|
16
16
|
expectedLength = 0;
|
|
17
17
|
bytesWritten = 0;
|
|
@@ -22,7 +22,7 @@ class MjpegFrameParser extends Transform {
|
|
|
22
22
|
const endIdx = chunk.indexOf(JPEG_EOI);
|
|
23
23
|
const lengthMatch = CONTENT_LENGTH_RE.exec(chunk.toString('latin1'));
|
|
24
24
|
if (this.buffer && (this.isReading || startIdx > -1)) {
|
|
25
|
-
this.appendChunk(chunk,
|
|
25
|
+
this.appendChunk(chunk, endIdx);
|
|
26
26
|
}
|
|
27
27
|
if (lengthMatch) {
|
|
28
28
|
this.startFrame(Number(lengthMatch[1]), chunk, startIdx, endIdx);
|
|
@@ -50,15 +50,19 @@ class MjpegFrameParser extends Transform {
|
|
|
50
50
|
this.isReading = true;
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
|
-
appendChunk(chunk,
|
|
53
|
+
appendChunk(chunk, end) {
|
|
54
54
|
if (!this.buffer) {
|
|
55
55
|
return;
|
|
56
56
|
}
|
|
57
|
-
|
|
57
|
+
// We are continuing a frame that is already open, so the whole chunk belongs to it
|
|
58
|
+
// and must be copied starting at offset 0. A JPEG SOI marker found anywhere in this
|
|
59
|
+
// chunk can only belong to the *next* frame (it necessarily comes after our own EOI),
|
|
60
|
+
// so it must never be used as the copy start here, or the current frame's tail bytes
|
|
61
|
+
// between offset 0 and that marker would be skipped, truncating it.
|
|
58
62
|
const copyEnd = end > -1 ? end + JPEG_EOI.length : chunk.length;
|
|
59
63
|
// Buffer.copy() silently truncates if the destination has less room than requested,
|
|
60
64
|
// so bytesWritten must track what was actually copied, not the requested range size.
|
|
61
|
-
this.bytesWritten += chunk.copy(this.buffer, this.bytesWritten,
|
|
65
|
+
this.bytesWritten += chunk.copy(this.buffer, this.bytesWritten, 0, copyEnd);
|
|
62
66
|
if (end > -1 || this.bytesWritten === this.expectedLength) {
|
|
63
67
|
this.emitFrame();
|
|
64
68
|
}
|
|
@@ -69,8 +73,17 @@ class MjpegFrameParser extends Transform {
|
|
|
69
73
|
emitFrame() {
|
|
70
74
|
this.isReading = false;
|
|
71
75
|
if (this.buffer) {
|
|
72
|
-
|
|
76
|
+
// Only push what was actually copied: the buffer is allocated to `expectedLength`
|
|
77
|
+
// up front, so pushing it as-is would leak trailing zero bytes whenever fewer
|
|
78
|
+
// bytes were actually written (e.g. a mismatched/truncated Content-Length).
|
|
79
|
+
this.push(this.buffer.subarray(0, this.bytesWritten));
|
|
73
80
|
}
|
|
81
|
+
// Clear the frame state so an unrelated later chunk (e.g. one with a JPEG SOI
|
|
82
|
+
// marker but no Content-Length header) cannot be appended onto a buffer that
|
|
83
|
+
// has already been pushed downstream.
|
|
84
|
+
this.buffer = null;
|
|
85
|
+
this.expectedLength = 0;
|
|
86
|
+
this.bytesWritten = 0;
|
|
74
87
|
}
|
|
75
88
|
}
|
|
76
89
|
let sharpModule = null;
|
|
@@ -189,6 +202,8 @@ export class MJpegStream extends Writable {
|
|
|
189
202
|
const onClose = () => {
|
|
190
203
|
log.debug(`The connection to MJPEG server at ${url} has been closed`);
|
|
191
204
|
this.lastChunk = null;
|
|
205
|
+
// No-op if start() has already resolved; only rejects a still-pending start().
|
|
206
|
+
this.registerStartFailure?.(new Error(`The connection to the MJPEG stream at ${url} has been closed before any frame was received`));
|
|
192
207
|
};
|
|
193
208
|
let timeoutId;
|
|
194
209
|
const startPromise = new Promise((resolve, reject) => {
|
|
@@ -204,6 +219,11 @@ export class MJpegStream extends Writable {
|
|
|
204
219
|
try {
|
|
205
220
|
await startPromise;
|
|
206
221
|
}
|
|
222
|
+
catch (err) {
|
|
223
|
+
// Do not leak the underlying HTTP connection/pipes if we never reached a usable state.
|
|
224
|
+
this.stop();
|
|
225
|
+
throw err;
|
|
226
|
+
}
|
|
207
227
|
finally {
|
|
208
228
|
clearTimeout(timeoutId);
|
|
209
229
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mjpeg.js","sourceRoot":"","sources":["../../../../lib/commands/helpers/mjpeg.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,SAAS,EAAE,QAAQ,EAA8D,MAAM,aAAa,CAAC;AAE7G,OAAO,EAAC,MAAM,EAAC,MAAM,mBAAmB,CAAC;AACzC,OAAO,KAAK,MAAM,OAAO,CAAC;AAK1B,MAAM,GAAG,GAAG,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;AAEtC,MAAM,yBAAyB,GAAG,KAAK,CAAC;AACxC,MAAM,yBAAyB,GAAG,IAAI,CAAC;AACvC,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;AAC3C,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;AAC3C,MAAM,iBAAiB,GAAG,0BAA0B,CAAC;AAErD;;;GAGG;AACH,MAAM,gBAAiB,SAAQ,SAAS;
|
|
1
|
+
{"version":3,"file":"mjpeg.js","sourceRoot":"","sources":["../../../../lib/commands/helpers/mjpeg.ts"],"names":[],"mappings":"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"}
|
|
@@ -5,7 +5,7 @@ import type { KeyboardKey } from './types.js';
|
|
|
5
5
|
*
|
|
6
6
|
* @deprecated
|
|
7
7
|
*/
|
|
8
|
-
export declare function hideKeyboard(this: XCUITestDriver,
|
|
8
|
+
export declare function hideKeyboard(this: XCUITestDriver, _strategy?: string, ...possibleKeys: string[]): Promise<boolean>;
|
|
9
9
|
/**
|
|
10
10
|
* Hides the keyboard using the specified key names.
|
|
11
11
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"keyboard.d.ts","sourceRoot":"","sources":["../../../lib/commands/keyboard.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,cAAc,CAAC;AACjD,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,YAAY,CAAC;AAE5C;;;;GAIG;AACH,wBAAsB,YAAY,CAChC,IAAI,EAAE,cAAc,EACpB,
|
|
1
|
+
{"version":3,"file":"keyboard.d.ts","sourceRoot":"","sources":["../../../lib/commands/keyboard.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,cAAc,CAAC;AACjD,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,YAAY,CAAC;AAE5C;;;;GAIG;AACH,wBAAsB,YAAY,CAChC,IAAI,EAAE,cAAc,EACpB,SAAS,CAAC,EAAE,MAAM,EAClB,GAAG,YAAY,EAAE,MAAM,EAAE,GACxB,OAAO,CAAC,OAAO,CAAC,CAQlB;AAED;;;;GAIG;AACH,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,cAAc,EAAE,IAAI,GAAE,MAAM,EAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAIjG;AAED;;;;GAIG;AACH,wBAAsB,eAAe,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,OAAO,CAAC,CAO5E;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,UAAU,CAC9B,IAAI,EAAE,cAAc,EACpB,IAAI,EAAE,CAAC,WAAW,GAAG,MAAM,CAAC,EAAE,EAC9B,SAAS,GAAE,MAAM,GAAG,IAAW,GAC9B,OAAO,CAAC,IAAI,CAAC,CAGf"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"keyboard.js","sourceRoot":"","sources":["../../../lib/commands/keyboard.ts"],"names":[],"mappings":"AAGA;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAEhC,
|
|
1
|
+
{"version":3,"file":"keyboard.js","sourceRoot":"","sources":["../../../lib/commands/keyboard.ts"],"names":[],"mappings":"AAGA;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAEhC,SAAkB,EAClB,GAAG,YAAsB;IAEzB,mCAAmC;IACnC,MAAM,QAAQ,GAAG,YAAY;SAC1B,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;SACZ,MAAM,CAAC,OAAO,CAAC;SACf,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IACtB,MAAM,IAAI,CAAC,kBAAkB,CAAC,QAAQ,CAAC,CAAC;IACxC,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAuB,OAAiB,EAAE;IAChF,MAAM,IAAI,CAAC,YAAY,CAAC,uBAAuB,EAAE,MAAM,EAAE;QACvD,QAAQ,EAAE,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,EAAE,MAAM,CAAC;KAC3D,CAAC,CAAC;AACL,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe;IACnC,IAAI,CAAC;QACH,MAAM,IAAI,CAAC,2BAA2B,CAAC,YAAY,EAAE,yBAAyB,EAAE,KAAK,CAAC,CAAC;QACvF,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAE9B,IAA8B,EAC9B,YAA2B,IAAI;IAE/B,MAAM,GAAG,GAAG,gBAAgB,SAAS,IAAI,CAAC,gBAAgB,CAAC;IAC3D,MAAM,IAAI,CAAC,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,EAAC,IAAI,EAAC,CAAC,CAAC;AAC/C,CAAC"}
|
|
@@ -44,7 +44,7 @@ export declare function startLogCapture(this: XCUITestDriver): Promise<boolean>;
|
|
|
44
44
|
* If the websocket is already running, this command does nothing.
|
|
45
45
|
*
|
|
46
46
|
* Each connected websocket listener will receive syslog lines as soon as they are visible to Appium.
|
|
47
|
-
* @see https://
|
|
47
|
+
* @see https://www.headspin.io/blog/using-mobile-execution-commands-to-continuously-stream-device-logs-with-appium
|
|
48
48
|
*/
|
|
49
49
|
export declare function mobileStartLogsBroadcast(this: XCUITestDriver): Promise<void>;
|
|
50
50
|
/**
|
|
@@ -166,7 +166,7 @@ export async function startLogCapture() {
|
|
|
166
166
|
* If the websocket is already running, this command does nothing.
|
|
167
167
|
*
|
|
168
168
|
* Each connected websocket listener will receive syslog lines as soon as they are visible to Appium.
|
|
169
|
-
* @see https://
|
|
169
|
+
* @see https://www.headspin.io/blog/using-mobile-execution-commands-to-continuously-stream-device-logs-with-appium
|
|
170
170
|
*/
|
|
171
171
|
export async function mobileStartLogsBroadcast() {
|
|
172
172
|
const pathname = WEBSOCKET_ENDPOINT(this.sessionId);
|
|
@@ -18,7 +18,7 @@ const CONTENT_LENGTH_RE = /Content-Length:\s*(\d+)/i;
|
|
|
18
18
|
* Extracts individual JPEG frames out of a multipart MJPEG-over-HTTP byte stream by
|
|
19
19
|
* scanning for JPEG start/end-of-image markers and the multipart `Content-Length` header.
|
|
20
20
|
*/
|
|
21
|
-
class MjpegFrameParser extends Transform {
|
|
21
|
+
export class MjpegFrameParser extends Transform {
|
|
22
22
|
private buffer: Buffer | null = null;
|
|
23
23
|
private expectedLength = 0;
|
|
24
24
|
private bytesWritten = 0;
|
|
@@ -31,7 +31,7 @@ class MjpegFrameParser extends Transform {
|
|
|
31
31
|
const lengthMatch = CONTENT_LENGTH_RE.exec(chunk.toString('latin1'));
|
|
32
32
|
|
|
33
33
|
if (this.buffer && (this.isReading || startIdx > -1)) {
|
|
34
|
-
this.appendChunk(chunk,
|
|
34
|
+
this.appendChunk(chunk, endIdx);
|
|
35
35
|
}
|
|
36
36
|
if (lengthMatch) {
|
|
37
37
|
this.startFrame(Number(lengthMatch[1]), chunk, startIdx, endIdx);
|
|
@@ -62,15 +62,19 @@ class MjpegFrameParser extends Transform {
|
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
private appendChunk(chunk: Buffer,
|
|
65
|
+
private appendChunk(chunk: Buffer, end: number): void {
|
|
66
66
|
if (!this.buffer) {
|
|
67
67
|
return;
|
|
68
68
|
}
|
|
69
|
-
|
|
69
|
+
// We are continuing a frame that is already open, so the whole chunk belongs to it
|
|
70
|
+
// and must be copied starting at offset 0. A JPEG SOI marker found anywhere in this
|
|
71
|
+
// chunk can only belong to the *next* frame (it necessarily comes after our own EOI),
|
|
72
|
+
// so it must never be used as the copy start here, or the current frame's tail bytes
|
|
73
|
+
// between offset 0 and that marker would be skipped, truncating it.
|
|
70
74
|
const copyEnd = end > -1 ? end + JPEG_EOI.length : chunk.length;
|
|
71
75
|
// Buffer.copy() silently truncates if the destination has less room than requested,
|
|
72
76
|
// so bytesWritten must track what was actually copied, not the requested range size.
|
|
73
|
-
this.bytesWritten += chunk.copy(this.buffer, this.bytesWritten,
|
|
77
|
+
this.bytesWritten += chunk.copy(this.buffer, this.bytesWritten, 0, copyEnd);
|
|
74
78
|
|
|
75
79
|
if (end > -1 || this.bytesWritten === this.expectedLength) {
|
|
76
80
|
this.emitFrame();
|
|
@@ -82,8 +86,17 @@ class MjpegFrameParser extends Transform {
|
|
|
82
86
|
private emitFrame(): void {
|
|
83
87
|
this.isReading = false;
|
|
84
88
|
if (this.buffer) {
|
|
85
|
-
|
|
89
|
+
// Only push what was actually copied: the buffer is allocated to `expectedLength`
|
|
90
|
+
// up front, so pushing it as-is would leak trailing zero bytes whenever fewer
|
|
91
|
+
// bytes were actually written (e.g. a mismatched/truncated Content-Length).
|
|
92
|
+
this.push(this.buffer.subarray(0, this.bytesWritten));
|
|
86
93
|
}
|
|
94
|
+
// Clear the frame state so an unrelated later chunk (e.g. one with a JPEG SOI
|
|
95
|
+
// marker but no Content-Length header) cannot be appended onto a buffer that
|
|
96
|
+
// has already been pushed downstream.
|
|
97
|
+
this.buffer = null;
|
|
98
|
+
this.expectedLength = 0;
|
|
99
|
+
this.bytesWritten = 0;
|
|
87
100
|
}
|
|
88
101
|
}
|
|
89
102
|
|
|
@@ -213,6 +226,10 @@ export class MJpegStream extends Writable {
|
|
|
213
226
|
const onClose = () => {
|
|
214
227
|
log.debug(`The connection to MJPEG server at ${url} has been closed`);
|
|
215
228
|
this.lastChunk = null;
|
|
229
|
+
// No-op if start() has already resolved; only rejects a still-pending start().
|
|
230
|
+
this.registerStartFailure?.(
|
|
231
|
+
new Error(`The connection to the MJPEG stream at ${url} has been closed before any frame was received`),
|
|
232
|
+
);
|
|
216
233
|
};
|
|
217
234
|
|
|
218
235
|
let timeoutId: NodeJS.Timeout | undefined;
|
|
@@ -233,6 +250,10 @@ export class MJpegStream extends Writable {
|
|
|
233
250
|
|
|
234
251
|
try {
|
|
235
252
|
await startPromise;
|
|
253
|
+
} catch (err) {
|
|
254
|
+
// Do not leak the underlying HTTP connection/pipes if we never reached a usable state.
|
|
255
|
+
this.stop();
|
|
256
|
+
throw err;
|
|
236
257
|
} finally {
|
|
237
258
|
clearTimeout(timeoutId);
|
|
238
259
|
}
|
package/lib/commands/keyboard.ts
CHANGED
package/lib/commands/log.ts
CHANGED
|
@@ -221,7 +221,7 @@ export async function startLogCapture(this: XCUITestDriver): Promise<boolean> {
|
|
|
221
221
|
* If the websocket is already running, this command does nothing.
|
|
222
222
|
*
|
|
223
223
|
* Each connected websocket listener will receive syslog lines as soon as they are visible to Appium.
|
|
224
|
-
* @see https://
|
|
224
|
+
* @see https://www.headspin.io/blog/using-mobile-execution-commands-to-continuously-stream-device-logs-with-appium
|
|
225
225
|
*/
|
|
226
226
|
export async function mobileStartLogsBroadcast(this: XCUITestDriver): Promise<void> {
|
|
227
227
|
const pathname = WEBSOCKET_ENDPOINT(this.sessionId as string);
|