doc-detective 4.10.0 → 4.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"stopRecording.d.ts","sourceRoot":"","sources":["../../../src/core/tests/stopRecording.ts"],"names":[],"mappings":"AA2BA,OAAO,EAAE,aAAa,EAAE,CAAC;AAEzB,iBAAe,aAAa,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE;IAAE,MAAM,EAAE,GAAG,CAAC;IAAC,IAAI,EAAE,GAAG,CAAC;IAAC,MAAM,EAAE,GAAG,CAAA;CAAE,gBA6H7F"}
1
+ {"version":3,"file":"stopRecording.d.ts","sourceRoot":"","sources":["../../../src/core/tests/stopRecording.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,aAAa,EAAE,CAAC;AAEzB,iBAAe,aAAa,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE;IAAE,MAAM,EAAE,GAAG,CAAC;IAAC,IAAI,EAAE,GAAG,CAAC;IAAC,MAAM,EAAE,GAAG,CAAA;CAAE,gBAwJ7F"}
@@ -1,41 +1,24 @@
1
1
  import { validate } from "../../common/src/validate.js";
2
2
  import { log } from "../utils.js";
3
- import { execFile } from "node:child_process";
3
+ import { spawn } from "node:child_process";
4
4
  import path from "node:path";
5
5
  import fs from "node:fs";
6
- import { loadHeavyDep } from "../../runtime/loader.js";
7
- // Resolve the ffmpeg binary path lazily — @ffmpeg-installer/ffmpeg is a
8
- // heavy runtime dep that should only be loaded when a stopRecording step
9
- // actually runs. The ctx is threaded through so a user-overridden
10
- // cacheDir is honored here just as it is by the JIT pre-flight installer.
11
- async function getFfmpegPath(ctx = {}) {
12
- const mod = await loadHeavyDep("@ffmpeg-installer/ffmpeg", { ctx });
13
- // The package's CJS entry exports an object with a .path field; in ESM
14
- // dynamic import we get { default: { path }, path? } shape depending on
15
- // bundler. Try both, then guard before handing it to execFile so a
16
- // malformed install fails with an actionable message instead of a
17
- // confusing "argument must be of type string" deep in node's exec.
18
- const candidate = mod && (mod.path ?? mod.default?.path);
19
- if (typeof candidate !== "string" || candidate.length === 0) {
20
- throw new Error("ffmpeg binary path is missing or malformed in the installed @ffmpeg-installer/ffmpeg package. Try `doc-detective install runtime --force` to reinstall.");
21
- }
22
- return candidate;
23
- }
6
+ import { getFfmpegPath } from "./ffmpegRecorder.js";
24
7
  export { stopRecording };
25
8
  async function stopRecording({ config, step, driver }) {
26
9
  let result = {
27
10
  status: "PASS",
28
11
  description: "Stopped recording.",
29
12
  };
30
- // Validate step payload
13
+ // Validate step payload. (The stopRecord step carries no fields we read
14
+ // here — the recording state lives on driver.state — so we only assert
15
+ // validity and don't keep the coerced object.)
31
16
  const isValidStep = validate({ schemaKey: "step_v3", object: step });
32
17
  if (!isValidStep.valid) {
33
18
  result.status = "FAIL";
34
19
  result.description = `Invalid step definition: ${isValidStep.errors}`;
35
20
  return result;
36
21
  }
37
- // Accept coerced and defaulted values
38
- step = isValidStep.object;
39
22
  // Skip if recording is not started. Recording state is per-context (it
40
23
  // lives on driver.state), so concurrent contexts can't see each other's
41
24
  // recordings.
@@ -47,7 +30,7 @@ async function stopRecording({ config, step, driver }) {
47
30
  }
48
31
  try {
49
32
  if (recording.type === "MediaRecorder") {
50
- // MediaRecorder
33
+ // Browser engine.
51
34
  // Switch to recording tab
52
35
  await driver.switchToWindow(recording.tab);
53
36
  // Check that recorder was properly initialized
@@ -71,13 +54,12 @@ async function stopRecording({ config, step, driver }) {
71
54
  await driver.execute(() => {
72
55
  window.recorder.stop();
73
56
  });
74
- // Wait for file to be in download path
75
- let waitCount = 0;
76
- while (!fs.existsSync(recording.downloadPath) && waitCount < 60) {
77
- await new Promise((r) => setTimeout(r, 1000));
78
- waitCount++;
79
- }
80
- if (!fs.existsSync(recording.downloadPath)) {
57
+ // Wait for the download to appear AND finish writing. Chrome streams the
58
+ // blob to disk (and may use a .crdownload temp first), so existence
59
+ // alone isn't enough transcoding a still-growing file makes ffmpeg
60
+ // fail. Wait for the size to hold steady across two reads.
61
+ const downloaded = await waitForStableFile(recording.downloadPath, 60);
62
+ if (!downloaded) {
81
63
  result.status = "FAIL";
82
64
  result.description = "Recording download timed out.";
83
65
  // Clear the state so the auto-stop in runContext doesn't re-invoke
@@ -92,43 +74,68 @@ async function stopRecording({ config, step, driver }) {
92
74
  if (remainingHandles.length > 0) {
93
75
  await driver.switchToWindow(remainingHandles[0]);
94
76
  }
95
- // Convert the file into the target format/location
96
- const targetPath = `${recording.targetPath}`;
97
- const downloadPath = `${recording.downloadPath}`;
98
- const endMessage = `Finished processing file: ${recording.targetPath}`;
99
- const ffmpegArgs = ["-y", "-i", downloadPath, "-pix_fmt", "yuv420p"];
100
- if (path.extname(targetPath) === ".gif") {
101
- ffmpegArgs.push("-vf", "scale=iw:-1:flags=lanczos");
77
+ // Convert the downloaded .webm into the target format/location.
78
+ await transcode({
79
+ config,
80
+ sourcePath: recording.downloadPath,
81
+ targetPath: recording.targetPath,
82
+ deleteSource: true,
83
+ });
84
+ driver.state.recording = null;
85
+ }
86
+ else if (recording.type === "ffmpeg") {
87
+ // ffmpeg engine. Stop the capture gracefully (write "q" to stdin so the
88
+ // container is finalized), then transcode the temp .mkv into the target
89
+ // format, cropping to the requested window/viewport if one was resolved.
90
+ const proc = recording.process;
91
+ try {
92
+ proc.stdin?.write("q");
93
+ proc.stdin?.end?.();
94
+ }
95
+ catch {
96
+ /* fall through to kill */
102
97
  }
103
- ffmpegArgs.push(targetPath);
104
- // Await transcoding to complete before returning
105
- const ffmpegPath = await getFfmpegPath({ cacheDir: config?.cacheDir });
106
- await new Promise((resolve, reject) => {
107
- execFile(ffmpegPath, ffmpegArgs)
108
- .on("close", (code) => {
109
- if (code === 0) {
110
- // Only delete the downloaded file after successful transcoding
111
- if (targetPath !== downloadPath) {
112
- try {
113
- fs.unlinkSync(downloadPath);
114
- }
115
- catch { /* ignore */ }
116
- }
117
- log(config, "debug", endMessage);
98
+ await new Promise((resolve) => {
99
+ // Already exited (e.g. ffmpeg reacted to "q" before we got here) —
100
+ // don't wait on a "close" that will never fire again.
101
+ if (proc.exitCode !== null || proc.signalCode !== null) {
102
+ resolve();
103
+ return;
104
+ }
105
+ let settled = false;
106
+ const finish = () => {
107
+ if (!settled) {
108
+ settled = true;
118
109
  resolve();
119
110
  }
120
- else {
121
- reject(new Error(`ffmpeg exited with code ${code}`));
111
+ };
112
+ // Normal path: ffmpeg exits after "q"; close clears the kill timer and
113
+ // we transcode the fully-flushed .mkv.
114
+ const killTimer = setTimeout(() => {
115
+ try {
116
+ proc.kill("SIGKILL");
117
+ }
118
+ catch {
119
+ /* ignore */
122
120
  }
123
- })
124
- .on("error", reject);
121
+ // Resolve on the post-kill close, or after a short grace if it never
122
+ // arrives. The .mkv survives a hard kill.
123
+ setTimeout(finish, 2000);
124
+ }, 15000);
125
+ proc.once("close", () => {
126
+ clearTimeout(killTimer);
127
+ finish();
128
+ });
129
+ });
130
+ await transcode({
131
+ config,
132
+ sourcePath: recording.tempPath,
133
+ targetPath: recording.targetPath,
134
+ deleteSource: true,
135
+ crop: recording.crop,
125
136
  });
126
137
  driver.state.recording = null;
127
138
  }
128
- else {
129
- // FFMPEG
130
- // recording.stdin.write("q");
131
- }
132
139
  }
133
140
  catch (error) {
134
141
  // Couldn't stop recording
@@ -142,4 +149,95 @@ async function stopRecording({ config, step, driver }) {
142
149
  // PASS
143
150
  return result;
144
151
  }
152
+ // Transcode a recording into the requested target format/location with
153
+ // ffmpeg, applying an optional crop and the gif scale filter. Deletes the
154
+ // source on success when requested (and when it isn't the target itself).
155
+ async function transcode({ config, sourcePath, targetPath, deleteSource, crop, }) {
156
+ // Drop audio (-an): doc recordings are visual, and the browser engine's
157
+ // captured opus track fails to mux into mp4 ("Too many packets buffered for
158
+ // output stream"). Silent video is the intended, reliable output.
159
+ const ffmpegArgs = ["-y", "-i", `${sourcePath}`, "-an", "-pix_fmt", "yuv420p"];
160
+ const filters = [];
161
+ if (crop) {
162
+ // Clamp the crop to the captured frame using ffmpeg expressions (iw/ih),
163
+ // so a window/viewport rectangle larger than the captured display can't
164
+ // make the crop filter fail with "Invalid too big size". Commas inside
165
+ // min()/max() are escaped (\,) so they aren't read as filter separators.
166
+ const cw = `min(iw\\,${crop.w})`;
167
+ const ch = `min(ih\\,${crop.h})`;
168
+ const cx = `max(0\\,min(${crop.x}\\,iw-${cw}))`;
169
+ const cy = `max(0\\,min(${crop.y}\\,ih-${ch}))`;
170
+ filters.push(`crop=w=${cw}:h=${ch}:x=${cx}:y=${cy}`);
171
+ }
172
+ if (path.extname(targetPath) === ".gif") {
173
+ filters.push("scale=iw:-1:flags=lanczos");
174
+ }
175
+ if (filters.length > 0) {
176
+ ffmpegArgs.push("-vf", filters.join(","));
177
+ }
178
+ ffmpegArgs.push(`${targetPath}`);
179
+ const ffmpegPath = await getFfmpegPath({ cacheDir: config?.cacheDir });
180
+ await new Promise((resolve, reject) => {
181
+ // spawn (not execFile): a long/noisy ffmpeg transcode can emit megabytes
182
+ // of progress on stderr, which would overflow execFile's internal buffer
183
+ // (ERR_CHILD_PROCESS_STDIO_MAXBUFFER). We stream stderr into a bounded tail.
184
+ const child = spawn(ffmpegPath, ffmpegArgs);
185
+ let stderr = "";
186
+ child.stderr?.on("data", (d) => {
187
+ stderr = (stderr + d.toString()).slice(-2000);
188
+ });
189
+ child
190
+ .on("close", (code) => {
191
+ if (code === 0) {
192
+ if (deleteSource && sourcePath !== targetPath) {
193
+ try {
194
+ fs.unlinkSync(sourcePath);
195
+ }
196
+ catch {
197
+ /* ignore */
198
+ }
199
+ }
200
+ log(config, "debug", `Finished processing file: ${targetPath}`);
201
+ resolve();
202
+ }
203
+ else {
204
+ reject(new Error(`ffmpeg exited with code ${code}: ${stderr.slice(-600)}`));
205
+ }
206
+ })
207
+ .on("error", reject);
208
+ });
209
+ }
210
+ // Wait for a file to exist and stop growing (size unchanged across two reads
211
+ // ~500ms apart), up to `maxSeconds`. Returns true once stable, false on
212
+ // timeout. Guards against transcoding a download that's still being written.
213
+ async function waitForStableFile(filePath, maxSeconds) {
214
+ let lastSize = -1;
215
+ let stableReads = 0;
216
+ const deadline = maxSeconds * 2; // two checks per second
217
+ for (let i = 0; i < deadline; i++) {
218
+ let size = -1;
219
+ try {
220
+ size = fs.statSync(filePath).size;
221
+ }
222
+ catch {
223
+ size = -1;
224
+ }
225
+ // Require a non-empty, steady size: Chrome may pre-create the .webm
226
+ // before writing data, and transcoding an empty file fails.
227
+ if (size > 0 && size === lastSize) {
228
+ stableReads++;
229
+ if (stableReads >= 1)
230
+ return true;
231
+ }
232
+ else {
233
+ stableReads = 0;
234
+ }
235
+ lastSize = size;
236
+ await new Promise((r) => setTimeout(r, 500));
237
+ }
238
+ // Timed out without the size ever holding steady — the file is missing or
239
+ // still being written. Report not-stable so the caller fails cleanly rather
240
+ // than transcoding a partial download.
241
+ return false;
242
+ }
145
243
  //# sourceMappingURL=stopRecording.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"stopRecording.js","sourceRoot":"","sources":["../../../src/core/tests/stopRecording.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"stopRecording.js","sourceRoot":"","sources":["../../../src/core/tests/stopRecording.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"tests.d.ts","sourceRoot":"","sources":["../../src/core/tests.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"tests.d.ts","sourceRoot":"","sources":["../../src/core/tests.ts"],"names":[],"mappings":"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"}
@@ -20,6 +20,7 @@ import { wait } from "./tests/wait.js";
20
20
  import { saveScreenshot } from "./tests/saveScreenshot.js";
21
21
  import { startRecording } from "./tests/startRecording.js";
22
22
  import { stopRecording } from "./tests/stopRecording.js";
23
+ import { browserCaptureTitle, browserDownloadDir, coerceRecordContextBrowser, jobIsFfmpegRecording, computeEffectiveConcurrency, checkSystemBinary, xvfbDisplay, startXvfb, XVFB_SCREEN_SIZE, } from "./tests/ffmpegRecorder.js";
23
24
  import { loadVariables } from "./tests/loadVariables.js";
24
25
  import { saveCookie } from "./tests/saveCookie.js";
25
26
  import { loadCookie } from "./tests/loadCookie.js";
@@ -152,7 +153,12 @@ function getDriverCapabilities({ runnerDetails, name, options }) {
152
153
  break;
153
154
  // Set args
154
155
  args.push(`--enable-chrome-browser-cloud-management`);
155
- args.push(`--auto-select-desktop-capture-source=RECORD_ME`);
156
+ // Auto-select the getDisplayMedia capture source by window title. A
157
+ // per-context title (set on document.title in startRecording) makes
158
+ // concurrent Chrome recordings safe: each browser process auto-selects
159
+ // only its own window. Falls back to the shared default for callers
160
+ // (warm-up, non-record contexts) that don't supply one.
161
+ args.push(`--auto-select-desktop-capture-source=${options.captureSourceTitle || "RECORD_ME"}`);
156
162
  if (options.headless)
157
163
  args.push("--headless", "--disable-gpu");
158
164
  if (process.platform === "linux") {
@@ -176,7 +182,9 @@ function getDriverCapabilities({ runnerDetails, name, options }) {
176
182
  // Reference: https://chromedriver.chromium.org/capabilities#h.p_ID_102
177
183
  args,
178
184
  prefs: {
179
- "download.default_directory": os.tmpdir(),
185
+ // Per-context download dir keeps concurrent recordings from
186
+ // colliding on the same .webm filename in a shared temp dir.
187
+ "download.default_directory": options.downloadDir || os.tmpdir(),
180
188
  "download.prompt_for_download": false,
181
189
  "download.directory_upgrade": true,
182
190
  },
@@ -496,8 +504,8 @@ async function runSpecs({ resolvedTests }) {
496
504
  // Resolve concurrency up front (defensive re-resolve: API callers can hand
497
505
  // runSpecs a config that never went through core setConfig, leaving
498
506
  // concurrentRunners as `true`). Drives both the worker pool and how many
499
- // Appium servers to start.
500
- const limit = resolveConcurrentRunners(config);
507
+ // Appium servers to start. Mutable: recording constraints may cap it below.
508
+ let limit = resolveConcurrentRunners(config);
501
509
  // Phase 1: pre-build the report skeleton and a flat list of context jobs
502
510
  // across all specs and tests. Slots are pre-assigned so report order always
503
511
  // matches input order, no matter what order concurrent contexts finish in.
@@ -550,15 +558,45 @@ async function runSpecs({ resolvedTests }) {
550
558
  usedContextIds.add(id);
551
559
  context.contextId = id;
552
560
  }
561
+ // Auto-resolution: when a record step has no explicit engine and the
562
+ // user never chose a browser, prefer the concurrency-safe browser
563
+ // engine by coercing to headed Chrome (when available). Done here,
564
+ // before the concurrency calc below, so each job's engine is settled.
565
+ // Non-record contexts keep runContext's normal browser defaulting.
566
+ const coercedBrowser = coerceRecordContextBrowser({
567
+ context,
568
+ availableApps: runnerDetails.availableApps,
569
+ });
570
+ if (coercedBrowser)
571
+ context.browser = coercedBrowser;
553
572
  jobs.push({ spec, test, context, contexts: testReport.contexts, slot });
554
573
  });
555
574
  }
556
575
  }
557
- if (limit > 1 &&
558
- jobs.some((job) => job.context.steps?.some((step) => step.record !== undefined))) {
559
- // Concurrent headed recordings capture via getDisplayMedia and can grab
560
- // the wrong window. Recording filenames in the temp dir can also collide.
561
- log(config, "warning", "Tests include record steps while concurrentRunners is greater than 1. Concurrent recordings can capture the wrong window; set concurrentRunners to 1 for recording runs.");
576
+ // Recording concurrency. The browser (Chrome getDisplayMedia) engine is
577
+ // concurrency-safe via per-context capture titles, but the ffmpeg engine
578
+ // grabs the whole physical display and must own it — so concurrent ffmpeg
579
+ // recordings are only safe on Linux with per-runner Xvfb displays. Probe
580
+ // Xvfb only when it could matter, then let computeEffectiveConcurrency
581
+ // decide the effective limit.
582
+ // Only ffmpeg-engine recordings need Xvfb; a browser-engine-only run
583
+ // shouldn't pay for an `Xvfb -help` spawn. Contexts are already coerced
584
+ // above, so resolveRecordPlan reflects the engine that will actually run.
585
+ const anyFfmpegRecording = jobs.some(jobIsFfmpegRecording);
586
+ let xvfbAvailable = false;
587
+ if (anyFfmpegRecording && process.platform === "linux") {
588
+ xvfbAvailable = await checkSystemBinary("Xvfb");
589
+ }
590
+ const concurrency = computeEffectiveConcurrency({
591
+ requestedLimit: limit,
592
+ jobs,
593
+ platform: process.platform,
594
+ xvfbAvailable,
595
+ });
596
+ limit = concurrency.limit;
597
+ if (concurrency.forcedSerial) {
598
+ log(config, "warning", "Recording with the ffmpeg engine needs exclusive use of the display, so this run is executing serially (concurrentRunners=1). To record concurrently, use the Chrome browser engine (record: { engine: \"browser\" }) or, on Linux, install Xvfb.");
599
+ report.recordingForcedSerial = true;
562
600
  }
563
601
  // Start one Appium server per concurrent runner that will actually use a
564
602
  // driver (capped at the number of driver contexts). Each server owns a
@@ -568,6 +606,12 @@ async function runSpecs({ resolvedTests }) {
568
606
  const driverJobCount = jobs.filter((job) => isDriverRequired({ test: job.context })).length;
569
607
  let appiumServers = [];
570
608
  let appiumPool;
609
+ // Per-server virtual displays (Linux Xvfb) for concurrent ffmpeg recording,
610
+ // and the port→display map so a context that acquires a server records the
611
+ // same display its browser renders on.
612
+ const xvfbProcesses = [];
613
+ const useXvfbDisplays = concurrency.xvfbContexts.length > 0;
614
+ let portToDisplay;
571
615
  if (driverJobCount > 0) {
572
616
  setAppiumHome({ cacheDir: config?.cacheDir });
573
617
  // Resolve appium's actual JS entrypoint via `require.resolve` (shim
@@ -593,7 +637,13 @@ async function runSpecs({ resolvedTests }) {
593
637
  // come up, tearing down any already started so they don't leak.
594
638
  try {
595
639
  for (let i = 0; i < serverCount; i++) {
596
- appiumServers.push(await startAppiumServer(appiumEntry, config));
640
+ let display;
641
+ if (useXvfbDisplays) {
642
+ display = xvfbDisplay(i);
643
+ xvfbProcesses.push(await startXvfb(display));
644
+ log(config, "debug", `Started Xvfb on ${display} for recording.`);
645
+ }
646
+ appiumServers.push(await startAppiumServer(appiumEntry, config, display));
597
647
  }
598
648
  }
599
649
  catch (error) {
@@ -605,9 +655,22 @@ async function runSpecs({ resolvedTests }) {
605
655
  // best-effort
606
656
  }
607
657
  }
658
+ for (const xvfb of xvfbProcesses) {
659
+ try {
660
+ xvfb.kill();
661
+ }
662
+ catch {
663
+ // best-effort
664
+ }
665
+ }
608
666
  throw error;
609
667
  }
610
668
  appiumPool = createAppiumPool(appiumServers.map((s) => s.port));
669
+ if (useXvfbDisplays) {
670
+ portToDisplay = new Map(appiumServers
671
+ .filter((s) => s.display)
672
+ .map((s) => [s.port, s.display]));
673
+ }
611
674
  }
612
675
  // Everything that uses the Appium servers runs inside this try so the
613
676
  // shutdown in `finally` always reaches them — otherwise a throw in
@@ -645,6 +708,7 @@ async function runSpecs({ resolvedTests }) {
645
708
  context: job.context,
646
709
  runnerDetails,
647
710
  appiumPool,
711
+ portToDisplay,
648
712
  metaValues,
649
713
  installAttempts,
650
714
  warmUpResults,
@@ -700,6 +764,15 @@ async function runSpecs({ resolvedTests }) {
700
764
  // Process may already be terminated
701
765
  }
702
766
  }
767
+ // Tear down any Xvfb virtual displays started for recording.
768
+ for (const xvfb of xvfbProcesses) {
769
+ try {
770
+ xvfb.kill();
771
+ }
772
+ catch {
773
+ // Process may already be terminated
774
+ }
775
+ }
703
776
  }
704
777
  // Upload changed files back to source integrations (best-effort)
705
778
  // This automatically syncs any changed screenshots back to their source CMS
@@ -944,7 +1017,7 @@ async function captureAutoScreenshot({ config, driver, spec, test, context, step
944
1017
  * report or summary counters — the caller owns aggregation, which keeps this
945
1018
  * function safe to run concurrently with sibling contexts.
946
1019
  */
947
- async function runContext({ config, spec, test, context, runnerDetails, appiumPool, metaValues, installAttempts, warmUpResults, logPrefix = "", }) {
1020
+ async function runContext({ config, spec, test, context, runnerDetails, appiumPool, portToDisplay, metaValues, installAttempts, warmUpResults, logPrefix = "", }) {
948
1021
  const platform = runnerDetails.environment.platform;
949
1022
  // `let`, not `const`: an on-demand browser install below re-detects available
950
1023
  // apps and reassigns this snapshot.
@@ -1082,8 +1155,26 @@ async function runContext({ config, spec, test, context, runnerDetails, appiumPo
1082
1155
  // Check out a server for this context's lifetime — released in the
1083
1156
  // finally so the next queued context can reuse it.
1084
1157
  appiumPort = await appiumPool.acquire();
1158
+ // If this server runs on a dedicated Xvfb display, record it on the
1159
+ // context so the ffmpeg recorder captures the same display the browser
1160
+ // renders on.
1161
+ if (portToDisplay) {
1162
+ const display = portToDisplay.get(appiumPort);
1163
+ if (display) {
1164
+ context.__display = display;
1165
+ // The Xvfb displays are created at a known fixed size; record it so
1166
+ // x11grab captures the full display (its default grabs only 640x480).
1167
+ context.__displaySize = XVFB_SCREEN_SIZE;
1168
+ }
1169
+ }
1085
1170
  // Define driver capabilities
1086
1171
  // TODO: Support custom apps
1172
+ // Per-context recording identifiers so concurrent Chrome recordings
1173
+ // auto-select their own window and download to their own dir.
1174
+ const recordOptions = {
1175
+ captureSourceTitle: browserCaptureTitle(context.contextId),
1176
+ downloadDir: browserDownloadDir(context.contextId),
1177
+ };
1087
1178
  let caps = getDriverCapabilities({
1088
1179
  runnerDetails: runnerDetails,
1089
1180
  name: context.browser.name,
@@ -1091,6 +1182,7 @@ async function runContext({ config, spec, test, context, runnerDetails, appiumPo
1091
1182
  width: context.browser?.window?.width || 1200,
1092
1183
  height: context.browser?.window?.height || 800,
1093
1184
  headless: context.browser?.headless !== false,
1185
+ ...recordOptions,
1094
1186
  },
1095
1187
  });
1096
1188
  clog("debug", "CAPABILITIES:");
@@ -1111,6 +1203,7 @@ async function runContext({ config, spec, test, context, runnerDetails, appiumPo
1111
1203
  width: context.browser?.window?.width || 1200,
1112
1204
  height: context.browser?.window?.height || 800,
1113
1205
  headless: context.browser?.headless !== false,
1206
+ ...recordOptions,
1114
1207
  },
1115
1208
  });
1116
1209
  driver = await driverStart(caps, appiumPort, 4, { cacheDir: config?.cacheDir });
@@ -1414,12 +1507,17 @@ async function runStep({ config = {}, context = {}, step, driver, metaValues = {
1414
1507
  // Start one Appium server on a free port and resolve once it answers /status.
1415
1508
  // Each concurrent runner gets its own server (own port) so parallel contexts
1416
1509
  // never create sessions on the same Appium instance.
1417
- async function startAppiumServer(appiumEntry, config) {
1510
+ async function startAppiumServer(appiumEntry, config, display) {
1418
1511
  const port = await findFreePort();
1419
1512
  log(config, "debug", `Starting Appium on port ${port}`);
1513
+ // When a virtual display is supplied (Linux Xvfb recording), launch the
1514
+ // server with DISPLAY set so the browser it spawns (via chromedriver)
1515
+ // renders on that display — which is what ffmpeg x11grab then captures.
1516
+ const env = display ? { ...process.env, DISPLAY: display } : process.env;
1420
1517
  const proc = spawn(process.execPath, [appiumEntry, "-a", "127.0.0.1", "-p", String(port)], {
1421
1518
  windowsHide: true,
1422
1519
  cwd: path.join(__dirname, "../.."),
1520
+ env,
1423
1521
  });
1424
1522
  proc.on("error", (err) => {
1425
1523
  log(config, "warning", `Appium process error: ${err?.stack ?? err?.message ?? String(err)}`);
@@ -1443,7 +1541,7 @@ async function startAppiumServer(appiumEntry, config) {
1443
1541
  throw error;
1444
1542
  }
1445
1543
  log(config, "debug", `Appium is ready on port ${port}.`);
1446
- return { port, process: proc };
1544
+ return { port, process: proc, display };
1447
1545
  }
1448
1546
  // Delay execution until Appium server is available.
1449
1547
  async function appiumIsReady(port, timeoutMs = 120000) {