@rehearsal-labs/retest 0.2.0 → 0.3.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.
Files changed (218) hide show
  1. package/README.md +6 -5
  2. package/dist/api/source-location.d.ts.map +1 -1
  3. package/dist/api/source-location.js +3 -1
  4. package/dist/browser/builds.d.ts +35 -6
  5. package/dist/browser/builds.d.ts.map +1 -1
  6. package/dist/browser/builds.js +115 -16
  7. package/dist/browser/cdp/transport.d.ts.map +1 -1
  8. package/dist/browser/cdp/transport.js +4 -4
  9. package/dist/browser/chromium-process.d.ts +23 -0
  10. package/dist/browser/chromium-process.d.ts.map +1 -1
  11. package/dist/browser/chromium-process.js +78 -13
  12. package/dist/browser/electron.d.ts +1 -1
  13. package/dist/browser/electron.d.ts.map +1 -1
  14. package/dist/browser/electron.js +6 -3
  15. package/dist/browser/executables.d.ts +16 -1
  16. package/dist/browser/executables.d.ts.map +1 -1
  17. package/dist/browser/executables.js +50 -18
  18. package/dist/browser/firefox/accessible-names.d.ts +5 -4
  19. package/dist/browser/firefox/accessible-names.d.ts.map +1 -1
  20. package/dist/browser/firefox/accessible-names.js +11 -6
  21. package/dist/browser/firefox/bridge.js +3 -2
  22. package/dist/browser/firefox/executable.d.ts +14 -14
  23. package/dist/browser/firefox/executable.d.ts.map +1 -1
  24. package/dist/browser/firefox/executable.js +29 -21
  25. package/dist/browser/firefox/launch.js +1 -1
  26. package/dist/browser/firefox/navigation.d.ts +5 -2
  27. package/dist/browser/firefox/navigation.d.ts.map +1 -1
  28. package/dist/browser/firefox/navigation.js +48 -14
  29. package/dist/browser/firefox/orphans.d.ts.map +1 -1
  30. package/dist/browser/firefox/orphans.js +34 -2
  31. package/dist/browser/firefox/page.d.ts.map +1 -1
  32. package/dist/browser/firefox/page.js +113 -19
  33. package/dist/browser/firefox/process.d.ts +20 -1
  34. package/dist/browser/firefox/process.d.ts.map +1 -1
  35. package/dist/browser/firefox/process.js +124 -22
  36. package/dist/browser/firefox/profile.d.ts +5 -0
  37. package/dist/browser/firefox/profile.d.ts.map +1 -1
  38. package/dist/browser/firefox/profile.js +17 -1
  39. package/dist/browser/firefox/storage.d.ts.map +1 -1
  40. package/dist/browser/firefox/storage.js +4 -2
  41. package/dist/browser/firefox/subscriptions.d.ts +26 -0
  42. package/dist/browser/firefox/subscriptions.d.ts.map +1 -0
  43. package/dist/browser/firefox/subscriptions.js +10 -0
  44. package/dist/browser/launch.d.ts +6 -0
  45. package/dist/browser/launch.d.ts.map +1 -1
  46. package/dist/browser/launch.js +14 -1
  47. package/dist/cli/commands/doctor.js +3 -3
  48. package/dist/cli/commands/inspect.js +3 -2
  49. package/dist/cli/commands/install.d.ts.map +1 -1
  50. package/dist/cli/commands/install.js +26 -13
  51. package/dist/cli/commands/licences.d.ts +22 -0
  52. package/dist/cli/commands/licences.d.ts.map +1 -1
  53. package/dist/cli/commands/licences.js +62 -20
  54. package/dist/cli/commands/run.js +2 -2
  55. package/dist/cli/doctor/checks.js +4 -1
  56. package/dist/cli/inspect/diagnostics.js +3 -3
  57. package/dist/cli/inspect/test-timeline.js +10 -10
  58. package/dist/cli/install/doctor-rows.d.ts.map +1 -1
  59. package/dist/cli/install/doctor-rows.js +5 -4
  60. package/dist/cli/install/file-changes.d.ts +44 -0
  61. package/dist/cli/install/file-changes.d.ts.map +1 -0
  62. package/dist/cli/install/file-changes.js +81 -0
  63. package/dist/cli/install/install-archive.d.ts +3 -1
  64. package/dist/cli/install/install-archive.d.ts.map +1 -1
  65. package/dist/cli/install/install-archive.js +80 -33
  66. package/dist/cli/install/lock.d.ts.map +1 -1
  67. package/dist/cli/install/lock.js +108 -21
  68. package/dist/cli/install/media-doctor.d.ts.map +1 -1
  69. package/dist/cli/install/media-doctor.js +2 -1
  70. package/dist/cli/install/media-install.d.ts.map +1 -1
  71. package/dist/cli/install/media-install.js +38 -19
  72. package/dist/cli/install/media-pins.d.ts +7 -1
  73. package/dist/cli/install/media-pins.d.ts.map +1 -1
  74. package/dist/cli/install/media-pins.js +23 -12
  75. package/dist/cli/install/media-record.d.ts.map +1 -1
  76. package/dist/cli/install/media-record.js +10 -9
  77. package/dist/cli/install/media-report.d.ts +3 -1
  78. package/dist/cli/install/media-report.d.ts.map +1 -1
  79. package/dist/cli/install/media-report.js +7 -5
  80. package/dist/cli/install/media-tools.d.ts.map +1 -1
  81. package/dist/cli/install/media-tools.js +6 -4
  82. package/dist/cli/install/process-start.d.ts +3 -2
  83. package/dist/cli/install/process-start.d.ts.map +1 -1
  84. package/dist/cli/install/process-start.js +4 -0
  85. package/dist/cli/install/report.d.ts.map +1 -1
  86. package/dist/cli/install/report.js +11 -7
  87. package/dist/cli/install/supervisor-install.d.ts +33 -0
  88. package/dist/cli/install/supervisor-install.d.ts.map +1 -0
  89. package/dist/cli/install/supervisor-install.js +158 -0
  90. package/dist/cli/install/supervisor-locate.d.ts +47 -0
  91. package/dist/cli/install/supervisor-locate.d.ts.map +1 -0
  92. package/dist/cli/install/supervisor-locate.js +113 -0
  93. package/dist/cli/install/supervisor-pins.d.ts +21 -0
  94. package/dist/cli/install/supervisor-pins.d.ts.map +1 -0
  95. package/dist/cli/install/supervisor-pins.js +28 -0
  96. package/dist/cli/install/supervisor-record.d.ts +31 -0
  97. package/dist/cli/install/supervisor-record.d.ts.map +1 -0
  98. package/dist/cli/install/supervisor-record.js +104 -0
  99. package/dist/cli/install/supervisor-report.d.ts +11 -0
  100. package/dist/cli/install/supervisor-report.d.ts.map +1 -0
  101. package/dist/cli/install/supervisor-report.js +33 -0
  102. package/dist/cli/install/supervisor-tools.d.ts +43 -0
  103. package/dist/cli/install/supervisor-tools.d.ts.map +1 -0
  104. package/dist/cli/install/supervisor-tools.js +114 -0
  105. package/dist/cli/install/unpack.d.ts +23 -3
  106. package/dist/cli/install/unpack.d.ts.map +1 -1
  107. package/dist/cli/install/unpack.js +124 -10
  108. package/dist/cli/install/zip-member.d.ts +15 -0
  109. package/dist/cli/install/zip-member.d.ts.map +1 -0
  110. package/dist/cli/install/zip-member.js +77 -0
  111. package/dist/cli/interrupt.d.ts +1 -1
  112. package/dist/cli/interrupt.d.ts.map +1 -1
  113. package/dist/cli/interrupt.js +15 -10
  114. package/dist/cli/terminal.d.ts +9 -1
  115. package/dist/cli/terminal.d.ts.map +1 -1
  116. package/dist/cli/terminal.js +14 -0
  117. package/dist/config/define.d.ts +2 -2
  118. package/dist/config/define.js +2 -2
  119. package/dist/diagnostics/firefox-collector.d.ts +11 -10
  120. package/dist/diagnostics/firefox-collector.d.ts.map +1 -1
  121. package/dist/diagnostics/firefox-collector.js +16 -11
  122. package/dist/media/client.d.ts +2 -1
  123. package/dist/media/client.d.ts.map +1 -1
  124. package/dist/media/client.js +16 -5
  125. package/dist/media/host.d.ts +32 -0
  126. package/dist/media/host.d.ts.map +1 -0
  127. package/dist/media/host.js +50 -0
  128. package/dist/media/locate.d.ts.map +1 -1
  129. package/dist/media/locate.js +4 -3
  130. package/dist/native/processes.d.ts.map +1 -1
  131. package/dist/native/processes.js +7 -3
  132. package/dist/protocol/files.d.ts +1 -1
  133. package/dist/protocol/files.d.ts.map +1 -1
  134. package/dist/protocol/files.js +10 -5
  135. package/dist/reporters/commands.d.ts +12 -3
  136. package/dist/reporters/commands.d.ts.map +1 -1
  137. package/dist/reporters/commands.js +29 -11
  138. package/dist/reporters/human-card.js +1 -1
  139. package/dist/reporters/human.d.ts +3 -1
  140. package/dist/reporters/human.d.ts.map +1 -1
  141. package/dist/reporters/human.js +8 -8
  142. package/dist/reporters/style.d.ts +21 -2
  143. package/dist/reporters/style.d.ts.map +1 -1
  144. package/dist/reporters/style.js +11 -2
  145. package/dist/runner/app-server.d.ts +27 -7
  146. package/dist/runner/app-server.d.ts.map +1 -1
  147. package/dist/runner/app-server.js +74 -24
  148. package/dist/runner/child.js +6 -2
  149. package/dist/runner/media-leftovers.d.ts +7 -3
  150. package/dist/runner/media-leftovers.d.ts.map +1 -1
  151. package/dist/runner/media-leftovers.js +27 -6
  152. package/dist/runner/resources.d.ts +8 -0
  153. package/dist/runner/resources.d.ts.map +1 -1
  154. package/dist/runner/resources.js +12 -1
  155. package/dist/runner/run-session.d.ts.map +1 -1
  156. package/dist/runner/run-session.js +9 -4
  157. package/dist/runner/run.d.ts.map +1 -1
  158. package/dist/runner/run.js +2 -1
  159. package/dist/runner/target-drivers.d.ts +5 -2
  160. package/dist/runner/target-drivers.d.ts.map +1 -1
  161. package/dist/runner/target-drivers.js +10 -1
  162. package/dist/runner/test-file-process.d.ts +8 -0
  163. package/dist/runner/test-file-process.d.ts.map +1 -1
  164. package/dist/runner/test-file-process.js +26 -3
  165. package/dist/runner/test-pages.d.ts.map +1 -1
  166. package/dist/runner/test-pages.js +5 -2
  167. package/dist/shared/environment.d.ts +17 -0
  168. package/dist/shared/environment.d.ts.map +1 -0
  169. package/dist/shared/environment.js +27 -0
  170. package/dist/shared/metadata-process-worker.js +15 -7
  171. package/dist/shared/metadata-process.d.ts +5 -0
  172. package/dist/shared/metadata-process.d.ts.map +1 -1
  173. package/dist/shared/metadata-process.js +36 -6
  174. package/dist/shared/posix-path.d.ts +12 -0
  175. package/dist/shared/posix-path.d.ts.map +1 -1
  176. package/dist/shared/posix-path.js +16 -0
  177. package/dist/shared/private-file.d.ts +38 -0
  178. package/dist/shared/private-file.d.ts.map +1 -0
  179. package/dist/shared/private-file.js +132 -0
  180. package/dist/shared/process-ownership.d.ts +52 -2
  181. package/dist/shared/process-ownership.d.ts.map +1 -1
  182. package/dist/shared/process-ownership.js +104 -11
  183. package/dist/shared/process-supervisor.d.ts +309 -0
  184. package/dist/shared/process-supervisor.d.ts.map +1 -0
  185. package/dist/shared/process-supervisor.js +737 -0
  186. package/dist/shared/process-system.d.ts +113 -0
  187. package/dist/shared/process-system.d.ts.map +1 -0
  188. package/dist/shared/process-system.js +254 -0
  189. package/dist/shared/regular-file.d.ts +2 -2
  190. package/dist/shared/regular-file.d.ts.map +1 -1
  191. package/dist/shared/regular-file.js +19 -2
  192. package/dist/store/artifacts.d.ts +5 -4
  193. package/dist/store/artifacts.d.ts.map +1 -1
  194. package/dist/store/artifacts.js +18 -5
  195. package/dist/store/run-store.d.ts +5 -2
  196. package/dist/store/run-store.d.ts.map +1 -1
  197. package/dist/store/run-store.js +48 -4
  198. package/media/Cargo.lock +16 -0
  199. package/media/Cargo.toml +16 -1
  200. package/media/src/encoder.rs +56 -12
  201. package/media/src/frame.rs +7 -1
  202. package/media/src/main.rs +20 -2
  203. package/media/src/place.rs +230 -11
  204. package/media/src/process_ownership_windows.rs +1058 -0
  205. package/media/src/recording.rs +15 -7
  206. package/media/src/replies.rs +55 -0
  207. package/media/src/server.rs +72 -2
  208. package/media/src/store.rs +71 -2
  209. package/package.json +9 -4
  210. package/src/cli/install/media-notices.txt +465 -1
  211. package/supervisor/Cargo.lock +25 -0
  212. package/supervisor/Cargo.toml +22 -0
  213. package/supervisor/NOTICES +464 -0
  214. package/supervisor/build.rs +44 -0
  215. package/supervisor/src/command.rs +612 -0
  216. package/supervisor/src/jobs.rs +1650 -0
  217. package/supervisor/src/main.rs +71 -0
  218. package/supervisor/src/reply.rs +367 -0
@@ -51,6 +51,11 @@ export declare class FirefoxProcess {
51
51
  get exit(): ProcessExit | undefined;
52
52
  /** True once the main process has exited. */
53
53
  get mainGone(): boolean;
54
+ /**
55
+ * Whether ending this process ends `pid`: when it is this process, or, where a job holds the launch, a process in that
56
+ * job, as the browser the Windows launcher starts and waits for is.
57
+ */
58
+ ends(pid: number, deadline?: Deadline): Promise<boolean>;
54
59
  /**
55
60
  * Records the processes Firefox started since the last look, while it is still there to be their parent, from a
56
61
  * reading that does not hold the thread.
@@ -68,7 +73,10 @@ export declare class FirefoxProcess {
68
73
  * it, such as `ws://127.0.0.1:50928`. Fails when the process ends first or the deadline passes.
69
74
  */
70
75
  waitForAddress(deadline: Deadline): Promise<string>;
71
- /** Settles once the main process has exited, no recorded process is left and its output is in the log. */
76
+ /**
77
+ * Settles once the main process has exited, no recorded process is left and its output is in the log. Rejects, naming
78
+ * what still ran, when the rest is not gone `exitedGroupLimitMs` after the main process exited.
79
+ */
72
80
  gone(): Promise<void>;
73
81
  /**
74
82
  * Waits up to `graceMs` for the main process and every recorded one to end, kills the recorded ones still there after
@@ -86,6 +94,13 @@ export type ProcessTable = {
86
94
  /** Checks every recorded pid's birth and every group member without changing recorded commands. */
87
95
  endedAsync?(): Promise<boolean>;
88
96
  };
97
+ /**
98
+ * The program a process runs, by its executable's own name. On Windows a reading gives the image's own path, spaces and
99
+ * all, and `ps` gives a command line, whose first word, or its quoted first part, is the program.
100
+ *
101
+ * @example programName('C:\\Program Files\\Mozilla Firefox\\firefox.exe') // 'firefox.exe'
102
+ */
103
+ export declare function programName(command: string): string;
89
104
  /** A process a route claimed: its id, folder and profile, its ownership, its child handle on the spawn route, and its output. */
90
105
  type ClaimedFirefox = {
91
106
  pid: number;
@@ -99,6 +114,10 @@ type ClaimedFirefox = {
99
114
  /** Copies output the route left in a file of its own into the log, once the process is gone. */
100
115
  copyOutput?: () => Promise<string[]>;
101
116
  cancelOutput?: () => void;
117
+ /** Stops reading output whose launch was shown ended, keeping what was read; the spawn route's redacted output has it. */
118
+ finishOutput?: () => void;
119
+ /** Whether a process outside Retest, inside the launch or not, can still write to the output Retest reads. */
120
+ outputWritersRemain?: () => boolean;
102
121
  /** The process table the Launch Services route watches; the host's own unless a test gives one. */
103
122
  table?: ProcessTable;
104
123
  /** When the claimed process started, as the launch recorded it, which tells it from a later process with its pid. */
@@ -1 +1 @@
1
- {"version":3,"file":"process.d.ts","sourceRoot":"","sources":["../../../src/browser/firefox/process.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"process.d.ts","sourceRoot":"","sources":["../../../src/browser/firefox/process.ts"],"names":[],"mappings":"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"}
@@ -2,7 +2,7 @@ import { execFile, spawn } from 'node:child_process';
2
2
  import { once } from 'node:events';
3
3
  import { rmSync } from 'node:fs';
4
4
  import { appendFile, mkdir, open, readFile, rm } from 'node:fs/promises';
5
- import { dirname, join } from 'node:path';
5
+ import { basename, dirname, join, win32 } from 'node:path';
6
6
  import { Readable } from 'node:stream';
7
7
  import { setTimeout as sleep } from 'node:timers/promises';
8
8
  import { waitBeforeRead } from "../../assertions/wait-before-read.js";
@@ -11,13 +11,19 @@ import { Deadline } from "../../protocol/deadline.js";
11
11
  import { defaultTimeouts } from "../../protocol/timeouts.js";
12
12
  import { errorMessage } from "../../protocol/failures.js";
13
13
  import { s, parse } from "../../protocol/schema.js";
14
+ import { withoutVariables } from "../../shared/environment.js";
15
+ import { listWords } from "../../shared/list-words.js";
14
16
  import { describeExit } from "../../shared/process-exit.js";
15
17
  import { OwnedProcessGroup, unverifiedMembersProblem } from "../../shared/process-ownership.js";
18
+ import { holdLaunch, launchesHeldByJob, prepareOwnedLaunch } from "../../shared/process-system.js";
19
+ import { exitRemovalMs, groupEndsNow, outputWithin, removeOnceFree } from "../chromium-process.js";
16
20
  import { closeGraceMs, LaunchError } from "../contract.js";
17
21
  import { writeRedactedLog } from "../redacted-log.js";
18
- import { ownerIdentityAsync, writeOwnerRecord } from "./profile.js";
22
+ import { ownerIdentityAsync, ownerStartVersion, writeOwnerRecord } from "./profile.js";
19
23
  import { isRunning, readProcessTable, readProcessIdentityAsync, readProcessIdentity, readProcessLivenessAsync, readProcessTableAsync } from "./process-table.js";
20
24
  const pollMs = 20;
25
+ // How long what Firefox's main process leaves is waited for once it exited, as Chromium's group is.
26
+ const exitedGroupLimitMs = 5 * closeGraceMs;
21
27
  // How often the Launch Services route reads the process table for a Firefox whose connection still stands. The
22
28
  // connection's end is the usual sign that the browser went; this rare look, taken without holding the thread, is for a
23
29
  // Firefox that went without closing it, and a look every `pollMs * 5` follows once the connection ended.
@@ -50,6 +56,8 @@ export class FirefoxProcess {
50
56
  #lastResort;
51
57
  #outputClosed;
52
58
  #cancelOutput;
59
+ #finishOutput;
60
+ #outputWritersRemain;
53
61
  #copyOutput;
54
62
  #table;
55
63
  #startedAt;
@@ -69,6 +77,8 @@ export class FirefoxProcess {
69
77
  this.#outputClosed = fields.output;
70
78
  this.#copyOutput = fields.copyOutput;
71
79
  this.#cancelOutput = fields.cancelOutput;
80
+ this.#finishOutput = fields.finishOutput;
81
+ this.#outputWritersRemain = fields.outputWritersRemain;
72
82
  this.#table = fields.table ?? { readAsync: readProcessTableAsync };
73
83
  this.#startedAt = fields.startedAt;
74
84
  const { child } = fields;
@@ -101,7 +111,7 @@ export class FirefoxProcess {
101
111
  const started = options.route === 'spawn' ? await spawnFirefox(options) : await openFirefox(options);
102
112
  const record = started.identity;
103
113
  try {
104
- await writeOwnerRecord(options.folder, { version: 1, startTimeVersion: 1, owner, firefox: { pid: record.pid, startedAt: record.startedAt, command: record.command, route: options.route }, profile: options.profile });
114
+ await writeOwnerRecord(options.folder, { version: 1, startTimeVersion: ownerStartVersion, owner, firefox: { pid: record.pid, startedAt: record.startedAt, command: record.command, route: options.route }, profile: options.profile });
105
115
  }
106
116
  catch (error) {
107
117
  const problems = await started.stop(0);
@@ -117,6 +127,18 @@ export class FirefoxProcess {
117
127
  get mainGone() {
118
128
  return this.#mainGone;
119
129
  }
130
+ /**
131
+ * Whether ending this process ends `pid`: when it is this process, or, where a job holds the launch, a process in that
132
+ * job, as the browser the Windows launcher starts and waits for is.
133
+ */
134
+ async ends(pid, deadline) {
135
+ if (pid === this.pid)
136
+ return true;
137
+ if (!this.#ownership.heldByJob)
138
+ return false;
139
+ await this.recordDescendants(deadline);
140
+ return this.#ownership.verifiedIdentity(pid) !== undefined;
141
+ }
120
142
  /**
121
143
  * Records the processes Firefox started since the last look, while it is still there to be their parent, from a
122
144
  * reading that does not hold the thread.
@@ -139,6 +161,13 @@ export class FirefoxProcess {
139
161
  this.expectExit();
140
162
  const deadline = new Deadline(timeoutMs);
141
163
  const problems = [];
164
+ // A job ends every process at once, so none can start another meanwhile, and Windows has no stopped state to wait for.
165
+ if (this.#ownership.heldByJob) {
166
+ problems.push(...this.#ownership.signalNow('SIGKILL', deadline));
167
+ if (problems.length > 0)
168
+ throw new Error(problems.join(' '));
169
+ return;
170
+ }
142
171
  try {
143
172
  await this.#stopForCrash(deadline);
144
173
  // The Remote Agent ends before content, so a lost input cannot receive a content-process protocol error.
@@ -206,17 +235,44 @@ export class FirefoxProcess {
206
235
  await waitBeforeRead(deadline, pollMs);
207
236
  }
208
237
  }
209
- /** Settles once the main process has exited, no recorded process is left and its output is in the log. */
238
+ /**
239
+ * Settles once the main process has exited, no recorded process is left and its output is in the log. Rejects, naming
240
+ * what still ran, when the rest is not gone `exitedGroupLimitMs` after the main process exited.
241
+ */
210
242
  gone() {
211
- this.#gone ??= this.#awaitGone();
243
+ if (this.#gone === undefined) {
244
+ this.#gone = this.#awaitGone();
245
+ // Marks the rejection handled for a browser nobody waits on; every caller that awaits `gone` still receives it.
246
+ this.#gone.catch(() => undefined);
247
+ }
212
248
  return this.#gone;
213
249
  }
250
+ // The main process can run as long as its browser is used, so its exit is awaited as it comes. What it leaves is given
251
+ // the bound Chromium's processes get, so a process that never ends is a cleanup failure, never a wait without end.
214
252
  async #awaitGone() {
215
253
  await this.exited;
254
+ const deadline = new Deadline(exitedGroupLimitMs);
255
+ // Each reading keeps its own bound, as before, so the limit cuts no reading short into a failed one.
216
256
  await this.recordDescendants();
217
- while (!(await this.#helpersGone()))
218
- await sleep(pollMs * 5, undefined, { ref: false });
219
- await this.outputSettled;
257
+ // What the newest reading that succeeded found still running, and why the readings after it failed, if they did: a
258
+ // process is named as still running only once a reading showed it.
259
+ let shown;
260
+ let failed;
261
+ while (!(await this.#helpersGone())) {
262
+ const look = this.#ownership.lastLook;
263
+ if (look !== undefined && 'running' in look)
264
+ [shown, failed] = [look.running, undefined];
265
+ else if (look !== undefined)
266
+ failed = look.problem;
267
+ if (deadline.reached)
268
+ throw new Error(stillRunning(this.pid, shown, failed));
269
+ await sleep(Math.min(pollMs * 5, deadline.remainingMs), undefined, { ref: false });
270
+ }
271
+ if (await settlesWithin(this.outputSettled, deadline))
272
+ return;
273
+ // The Launch Services route copies its output into the log as it stops, which its stop reports on.
274
+ if (this.#copyOutput === undefined)
275
+ throw new Error(`Firefox's output did not close within ${exitedGroupLimitMs} ms after its main process ${this.pid} exited.`);
220
276
  }
221
277
  /**
222
278
  * Waits up to `graceMs` for the main process and every recorded one to end, kills the recorded ones still there after
@@ -245,11 +301,13 @@ export class FirefoxProcess {
245
301
  catch (error) {
246
302
  problems.push(`Could not end Firefox's process group ${this.pid}: ${errorMessage(error)}`);
247
303
  }
248
- if (!(await settlesWithin(this.#outputClosed, new Deadline(closeGraceMs)))) {
249
- problems.push("Firefox's output did not close within the cleanup bound.");
250
- this.#cancelOutput?.();
251
- }
252
- problems.push(...(await this.#outputClosed));
304
+ // The output's bound follows the launch's confirmed end, as a Chromium browser's does: once its close bound passes,
305
+ // waiting I/O is read, and once nothing can write to the output, or nothing of the launch is left, Retest's own
306
+ // writing of what it read gets a bound of its own.
307
+ const finishReading = this.#finishOutput;
308
+ const writersRemain = this.#outputWritersRemain;
309
+ const output = { closed: this.#outputClosed, cancel: () => this.#cancelOutput?.(), ...(finishReading === undefined ? {} : { finishReading }), ...(writersRemain === undefined ? {} : { writersRemain }) };
310
+ problems.push(...(await outputWithin(output, closeGraceMs, async () => this.#mainGone && !(await this.#ownership.remainsAsync(deadline)))));
253
311
  if (this.#copyOutput !== undefined)
254
312
  problems.push(...(await this.#copyOutput()));
255
313
  this.#settleCopied();
@@ -320,6 +378,32 @@ export class FirefoxProcess {
320
378
  });
321
379
  }
322
380
  }
381
+ // What still ran of a Firefox whose main process exited, by pid and the name of its program, never its arguments, as the
382
+ // newest look that succeeded showed it, or that Retest could not look: every look after the exit failed.
383
+ function stillRunning(pid, shown, failed) {
384
+ if (shown === undefined)
385
+ return `Retest could not look for what Firefox ${pid} left running after it exited.`;
386
+ const named = shown.filter((entry) => entry.pid !== pid).map((entry) => `${entry.pid} (${programName(entry.command)})`);
387
+ if (named.length === 0)
388
+ return `Firefox ${pid} still has processes running ${exitedGroupLimitMs / 1000} seconds after it exited.`;
389
+ const listed = named.length <= 3 ? listWords(named, 'and') : `${named.slice(0, 3).join(', ')} and ${named.length - 3} more`;
390
+ const subject = `Firefox process${named.length === 1 ? '' : 'es'} ${listed}`;
391
+ if (failed !== undefined)
392
+ return `${subject} ${named.length === 1 ? 'was' : 'were'} still running when Retest could last look.`;
393
+ return `${subject} ${named.length === 1 ? 'is' : 'are'} still running ${exitedGroupLimitMs / 1000} seconds after Firefox ${pid} exited.`;
394
+ }
395
+ /**
396
+ * The program a process runs, by its executable's own name. On Windows a reading gives the image's own path, spaces and
397
+ * all, and `ps` gives a command line, whose first word, or its quoted first part, is the program.
398
+ *
399
+ * @example programName('C:\\Program Files\\Mozilla Firefox\\firefox.exe') // 'firefox.exe'
400
+ */
401
+ export function programName(command) {
402
+ if (/^(?:[A-Za-z]:)?\\/.test(command))
403
+ return win32.basename(command);
404
+ const quoted = /^"([^"]+)"/.exec(command)?.[1];
405
+ return win32.basename(quoted ?? command.split(' ')[0] ?? command);
406
+ }
323
407
  // Whether `promise` settles before the deadline passes.
324
408
  async function settlesWithin(promise, deadline) {
325
409
  let timer;
@@ -342,27 +426,35 @@ async function spawnFirefox(options) {
342
426
  const log = await openLog(logFile);
343
427
  let logHandedOn = false;
344
428
  try {
345
- const hidden = new Set(options.hiddenVariables);
346
- const environment = { ...Object.fromEntries(Object.entries(process.env).filter(([name]) => !hidden.has(name))), ...crashReporterOff };
429
+ const environment = { ...withoutVariables(process.env, options.hiddenVariables), ...crashReporterOff };
347
430
  // Output to redact comes through Retest; any other goes straight to the log.
348
431
  const redacting = redact !== undefined || redactStream !== undefined;
432
+ try {
433
+ prepareOwnedLaunch();
434
+ }
435
+ catch (error) {
436
+ throw new LaunchError(`Cannot start ${executable}: ${errorMessage(error)}`, { cause: error });
437
+ }
349
438
  const child = spawn(executable, firefoxArguments(profile, options.headless), { env: environment, detached: true, stdio: ['ignore', redacting ? 'pipe' : log.fd, redacting ? 'pipe' : log.fd] });
350
439
  await once(child, 'spawn').catch((error) => {
351
440
  throw new LaunchError(`Cannot start ${executable}: ${errorMessage(error)}`, { cause: error });
352
441
  });
353
- const { ownership, table } = await claimOwnership(child.pid ?? 0, process.pid);
442
+ // Where a job holds the launch, its own process list is the table; the process table `ps` reads exists only on Unix.
443
+ // The folder's name is in the command line of the browser a Windows launcher starts, which may be in a job of its own.
444
+ const { ownership, table } = launchesHeldByJob ? { ownership: holdLaunch(child, { marker: basename(folder) }), table: undefined } : await claimOwnership(child.pid ?? 0, process.pid);
354
445
  const ownershipProblems = ownership.capture();
355
446
  // Without redaction Firefox writes to the log itself, until it exits.
356
447
  let output = child.exitCode !== null || child.signalCode !== null ? Promise.resolve([]) : new Promise((resolve) => child.once('exit', () => resolve([])));
357
- let cancelOutput;
448
+ let reading;
358
449
  if (redacting) {
359
450
  const streams = [child.stdout, child.stderr].flatMap((stream) => (stream instanceof Readable ? [stream] : []));
360
451
  const writer = writeRedactedLog(streams, log, redact ?? ((text) => text), redactStream);
361
452
  output = writer.closed;
362
- cancelOutput = writer.cancel;
453
+ // A pipe reaches its end only once every process holding its write end, inside the launch or not, has let go.
454
+ reading = { cancelOutput: writer.cancel, finishOutput: writer.finishReading, outputWritersRemain: () => streams.some((stream) => !stream.readableEnded && !stream.destroyed) };
363
455
  logHandedOn = true;
364
456
  }
365
- const firefox = new FirefoxProcess({ pid: child.pid ?? 0, route: 'spawn', folder, profile, ownership, table, child, output, ...(cancelOutput === undefined ? {} : { cancelOutput }) });
457
+ const firefox = new FirefoxProcess({ pid: child.pid ?? 0, route: 'spawn', folder, profile, ownership, child, output, ...(table === undefined ? {} : { table }), ...reading });
366
458
  if (!redacting) {
367
459
  logHandedOn = true;
368
460
  await log.close().catch(() => undefined);
@@ -511,9 +603,19 @@ function parseJson(text) {
511
603
  // is why every launch first ends the Firefoxes of launchers that are gone.
512
604
  function killAtExit(ownership, folder) {
513
605
  try {
514
- ownership.signalNow('SIGKILL');
515
- if (!ownership.remains())
516
- rmSync(folder, { recursive: true, force: true, maxRetries: 3 });
606
+ if (!launchesHeldByJob) {
607
+ ownership.signalNow('SIGKILL');
608
+ if (!ownership.remains())
609
+ rmSync(folder, { recursive: true, force: true, maxRetries: 3 });
610
+ return;
611
+ }
612
+ // On Windows the end also reads and ends what the launch's parent holds, each step a run of retest-supervisor that
613
+ // holds the exiting thread, so the end and its confirmation get the bound Chromium's exit gets, and the removal,
614
+ // which the folder refuses for a moment after its processes left their job, gets one of its own.
615
+ const deadline = new Deadline(closeGraceMs);
616
+ ownership.signalNow('SIGKILL', deadline);
617
+ if (groupEndsNow(ownership, deadline))
618
+ removeOnceFree(folder, new Deadline(exitRemovalMs));
517
619
  }
518
620
  catch {
519
621
  // Throwing here would replace the exit code Retest chose.
@@ -30,6 +30,11 @@ export type OwnerRecord = {
30
30
  };
31
31
  profile: string;
32
32
  };
33
+ /**
34
+ * How the starts in a record this platform writes are read: 1 is `ps` in the C locale under UTC0, 2 the creation time
35
+ * Windows keeps, in 100 ns ticks since 1601. A record of another version is never compared.
36
+ */
37
+ export declare const ownerStartVersion: number;
33
38
  /**
34
39
  * This process as `ps` reads it, read once: the owner every record of this process names.
35
40
  *
@@ -1 +1 @@
1
- {"version":3,"file":"profile.d.ts","sourceRoot":"","sources":["../../../src/browser/firefox/profile.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,mCAAmC,CAAA;AAC7E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAM9C,qHAAqH;AACrH,eAAO,MAAM,mBAAmB,oBAAoB,CAAA;AAEpD,iGAAiG;AACjG,eAAO,MAAM,eAAe,sBAAsB,CAAA;AAElD;;;GAGG;AACH,eAAO,MAAM,kBAAkB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,CAuBlF,CAAA;AAED,2FAA2F;AAC3F,MAAM,MAAM,aAAa,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAA;AAE/E;;;;;GAKG;AACH,MAAM,MAAM,WAAW,GAAG;IAAE,OAAO,EAAE,CAAC,CAAC;IAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,aAAa,CAAC;IAAC,OAAO,EAAE,aAAa,GAAG;QAAE,KAAK,EAAE,YAAY,CAAA;KAAE,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAA;AAa5J;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,KAAK,GAAE,MAAM,SAAS,oBAAoB,EAAqB,GAAG,aAAa,CAM5G;AAED,0FAA0F;AAC1F,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,aAAa,CAAC,CAIjE;AAED;;;GAGG;AACH,wBAAsB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAepG;AAED,iHAAiH;AACjH,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAIzF;AAED,4FAA4F;AAC5F,wBAAsB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC,CAetF"}
1
+ {"version":3,"file":"profile.d.ts","sourceRoot":"","sources":["../../../src/browser/firefox/profile.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,mCAAmC,CAAA;AAC7E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAO9C,qHAAqH;AACrH,eAAO,MAAM,mBAAmB,oBAAoB,CAAA;AAEpD,iGAAiG;AACjG,eAAO,MAAM,eAAe,sBAAsB,CAAA;AAElD;;;GAGG;AACH,eAAO,MAAM,kBAAkB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,CA0BlF,CAAA;AAED,2FAA2F;AAC3F,MAAM,MAAM,aAAa,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAA;AAE/E;;;;;GAKG;AACH,MAAM,MAAM,WAAW,GAAG;IAAE,OAAO,EAAE,CAAC,CAAC;IAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,aAAa,CAAC;IAAC,OAAO,EAAE,aAAa,GAAG;QAAE,KAAK,EAAE,YAAY,CAAA;KAAE,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAA;AAW5J;;;GAGG;AACH,eAAO,MAAM,iBAAiB,EAAE,MAAkC,CAAA;AAIlE;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,KAAK,GAAE,MAAM,SAAS,oBAAoB,EAAqB,GAAG,aAAa,CAM5G;AAED,0FAA0F;AAC1F,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,aAAa,CAAC,CAUjE;AAED;;;GAGG;AACH,wBAAsB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAepG;AAED,iHAAiH;AACjH,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAIzF;AAED,4FAA4F;AAC5F,wBAAsB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC,CAetF"}
@@ -1,6 +1,7 @@
1
1
  import { mkdir, mkdtemp, readFile, realpath, rename, rm, writeFile } from 'node:fs/promises';
2
2
  import { join } from 'node:path';
3
3
  import { s, parse } from "../../protocol/schema.js";
4
+ import { launchesHeldByJob, readHeldProcess } from "../../shared/process-system.js";
4
5
  import { readProcessTable, readProcessTableAsync } from "./process-table.js";
5
6
  /** How the temporary folders of Firefox launches begin: `retest-firefox-<pid of the launching process>-<random>`. */
6
7
  export const firefoxFolderPrefix = 'retest-firefox-';
@@ -11,7 +12,7 @@ export const ownerRecordFile = 'retest-owner.json';
11
12
  * since `remote.prefs.recommended` is on by default; these name what Retest relies on instead of leaving it to that list.
12
13
  */
13
14
  export const firefoxPreferences = {
14
- // WebDriver BiDi only. Firefox 133 still offers its deprecated CDP endpoint under other values.
15
+ // WebDriver BiDi only, the one protocol Retest speaks to Firefox.
15
16
  'remote.active-protocols': 1,
16
17
  // No update is downloaded or applied, and no restart is asked for, while a test runs.
17
18
  'app.update.disabledForTesting': true,
@@ -33,6 +34,9 @@ export const firefoxPreferences = {
33
34
  // No page is kept in the back-forward cache, so a move through the history always loads its document again and
34
35
  // Firefox tells of it as a navigation.
35
36
  'browser.sessionhistory.max_total_viewers': 0,
37
+ // Firefox 158 writes each error of a page's script to its output, with the script's address and query, which the
38
+ // browser log keeps; Firefox 133 wrote none.
39
+ 'browser.dom.window.dump.enabled': false,
36
40
  };
37
41
  const identitySchema = s.object({ pid: s.number({ integer: true, min: 1 }), startedAt: s.string(), command: s.string() });
38
42
  const recordSchema = s.object({
@@ -42,6 +46,11 @@ const recordSchema = s.object({
42
46
  firefox: s.object({ pid: s.number({ integer: true, min: 2 }), startedAt: s.string(), command: s.string(), route: s.enum(['spawn', 'launch-services']) }),
43
47
  profile: s.string(),
44
48
  });
49
+ /**
50
+ * How the starts in a record this platform writes are read: 1 is `ps` in the C locale under UTC0, 2 the creation time
51
+ * Windows keeps, in 100 ns ticks since 1601. A record of another version is never compared.
52
+ */
53
+ export const ownerStartVersion = launchesHeldByJob ? 2 : 1;
45
54
  let ownIdentity;
46
55
  /**
47
56
  * This process as `ps` reads it, read once: the owner every record of this process names.
@@ -61,6 +70,13 @@ export function ownerIdentity(table = readProcessTable) {
61
70
  export async function ownerIdentityAsync() {
62
71
  if (ownIdentity !== undefined)
63
72
  return ownIdentity;
73
+ if (launchesHeldByJob) {
74
+ const own = readHeldProcess(process.pid);
75
+ if (own === undefined)
76
+ throw new Error(`Windows does not show this process, ${process.pid}, so no Firefox it launches could be recorded as its own.`);
77
+ ownIdentity = { pid: own.pid, startedAt: own.startedAt, command: own.image ?? '' };
78
+ return ownIdentity;
79
+ }
64
80
  const table = await readProcessTableAsync();
65
81
  return ownerIdentity(() => table);
66
82
  }
@@ -1 +1 @@
1
- {"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../../src/browser/firefox/storage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAClD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,4BAA4B,CAAA;AAC1D,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,iCAAiC,CAAA;AAS/F,2FAA2F;AAC3F,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAA;IACtC,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,EAAE,OAAO,CAAA;IACjB,MAAM,EAAE,OAAO,CAAA;IACf,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB,CAAA;AAkCD;;;GAGG;AACH,wBAAsB,kBAAkB,CAAC,MAAM,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAI7H;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,aAAa,GAAG,YAAY,CAMvE;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAYhF;AAED;;;;GAIG;AACH,wBAAsB,mBAAmB,CAAC,MAAM,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAa7I;AAED;;;GAGG;AACH,wBAAsB,wBAAwB,CAAC,MAAM,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,MAAM,EAAE,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAG/J;AAED;;;;GAIG;AACH,wBAAsB,wBAAwB,CAAC,CAAC,EAC9C,MAAM,EAAE,UAAU,EAClB,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,SAAS,MAAM,EAAE,EAC1B,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,EACrD,QAAQ,EAAE,QAAQ,GACjB,OAAO,CAAC,CAAC,EAAE,CAAC,CAwBd"}
1
+ {"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../../src/browser/firefox/storage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAClD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,4BAA4B,CAAA;AAC1D,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,iCAAiC,CAAA;AAU/F,2FAA2F;AAC3F,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAA;IACtC,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,EAAE,OAAO,CAAA;IACjB,MAAM,EAAE,OAAO,CAAA;IACf,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB,CAAA;AAkCD;;;GAGG;AACH,wBAAsB,kBAAkB,CAAC,MAAM,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAI7H;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,aAAa,GAAG,YAAY,CAMvE;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAYhF;AAED;;;;GAIG;AACH,wBAAsB,mBAAmB,CAAC,MAAM,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAa7I;AAED;;;GAGG;AACH,wBAAsB,wBAAwB,CAAC,MAAM,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,MAAM,EAAE,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAG/J;AAED;;;;GAIG;AACH,wBAAsB,wBAAwB,CAAC,CAAC,EAC9C,MAAM,EAAE,UAAU,EAClB,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,SAAS,MAAM,EAAE,EAC1B,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,EACrD,QAAQ,EAAE,QAAQ,GACjB,OAAO,CAAC,CAAC,EAAE,CAAC,CAyBd"}
@@ -4,6 +4,7 @@ import { readStorageFunction, writeStorageFunction } from "../page-scripts.js";
4
4
  import { webOrigin } from "../storage-state.js";
5
5
  import { readBidi } from "./bidi-client.js";
6
6
  import { callInSandbox } from "./sandbox.js";
7
+ import { subscribedSchema, unsubscribeParameters } from "./subscriptions.js";
7
8
  import { inWindowOrder } from "./window-order.js";
8
9
  const cookiesSchema = s.object({
9
10
  cookies: s.array(s.object({
@@ -117,8 +118,9 @@ export async function inFirefoxOriginDocuments(client, userContext, origins, wor
117
118
  const { context } = await inWindowOrder(client, deadline, () => client.request('browsingContext.create', { type: 'tab', userContext, background: true }, contextSchema, { ...options(), keepErrorMessage: true }));
118
119
  const stop = client.on('network.beforeRequestSent', (params) => answerPaused(client, context, params, deadline));
119
120
  let intercept;
121
+ let subscription;
120
122
  try {
121
- await client.request('session.subscribe', { events: ['network.beforeRequestSent'], contexts: [context] }, s.object({}), options());
123
+ subscription = (await client.request('session.subscribe', { events: ['network.beforeRequestSent'], contexts: [context] }, subscribedSchema, options())).subscription;
122
124
  intercept = (await client.request('network.addIntercept', { phases: ['beforeRequestSent'], contexts: [context] }, interceptSchema, options())).intercept;
123
125
  const results = [];
124
126
  for (const origin of origins) {
@@ -131,7 +133,7 @@ export async function inFirefoxOriginDocuments(client, userContext, origins, wor
131
133
  stop();
132
134
  if (intercept !== undefined)
133
135
  await client.send('network.removeIntercept', { intercept }, options()).catch(() => undefined);
134
- await client.send('session.unsubscribe', { events: ['network.beforeRequestSent'], contexts: [context] }, options()).catch(() => undefined);
136
+ await client.send('session.unsubscribe', unsubscribeParameters({ id: subscription, events: ['network.beforeRequestSent'], contexts: [context] }), options()).catch(() => undefined);
135
137
  await inWindowOrder(client, deadline, () => client.send('browsingContext.close', { context }, options())).catch(() => {
136
138
  // Removing the user context closes the tab with it, and the error that matters is the work's own.
137
139
  });
@@ -0,0 +1,26 @@
1
+ import type { Schema } from '../../protocol/schema.ts';
2
+ /** What `session.subscribe` answers: the subscription's id, from a Firefox later than 133, or nothing. */
3
+ export type Subscribed = {
4
+ subscription?: string;
5
+ };
6
+ export declare const subscribedSchema: Schema<Subscribed>;
7
+ /** One subscription as it was made: the id the browser gave it, if any, and the events and contexts it named. */
8
+ export type Subscription = {
9
+ readonly id: string | undefined;
10
+ readonly events: readonly string[];
11
+ readonly contexts: readonly string[];
12
+ };
13
+ /** What `session.unsubscribe` is sent to end a subscription: its id, or its events and contexts. */
14
+ export type UnsubscribeParameters = {
15
+ readonly subscriptions: readonly string[];
16
+ } | {
17
+ readonly events: readonly string[];
18
+ readonly contexts: readonly string[];
19
+ };
20
+ /**
21
+ * The parameters that end `subscription`, by its id when the browser gave one.
22
+ *
23
+ * @example unsubscribeParameters({ id: undefined, events: ['script'], contexts: [context] }) // { events: ['script'], contexts: [context] }
24
+ */
25
+ export declare function unsubscribeParameters(subscription: Subscription): UnsubscribeParameters;
26
+ //# sourceMappingURL=subscriptions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"subscriptions.d.ts","sourceRoot":"","sources":["../../../src/browser/firefox/subscriptions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,0BAA0B,CAAA;AAWtD,0GAA0G;AAC1G,MAAM,MAAM,UAAU,GAAG;IAAE,YAAY,CAAC,EAAE,MAAM,CAAA;CAAE,CAAA;AAElD,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,UAAU,CAAsD,CAAA;AAEtG,iHAAiH;AACjH,MAAM,MAAM,YAAY,GAAG;IAAE,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,SAAS,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,CAAA;AAExI,oGAAoG;AACpG,MAAM,MAAM,qBAAqB,GAAG;IAAE,QAAQ,CAAC,aAAa,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,GAAG;IAAE,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,CAAA;AAEhK;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,YAAY,EAAE,YAAY,GAAG,qBAAqB,CAEvF"}
@@ -0,0 +1,10 @@
1
+ import { s } from "../../protocol/schema.js";
2
+ export const subscribedSchema = s.object({ subscription: s.optional(s.string()) });
3
+ /**
4
+ * The parameters that end `subscription`, by its id when the browser gave one.
5
+ *
6
+ * @example unsubscribeParameters({ id: undefined, events: ['script'], contexts: [context] }) // { events: ['script'], contexts: [context] }
7
+ */
8
+ export function unsubscribeParameters(subscription) {
9
+ return subscription.id === undefined ? { events: subscription.events, contexts: subscription.contexts } : { subscriptions: [subscription.id] };
10
+ }
@@ -13,6 +13,12 @@ import { CdpConnection } from './cdp/connection.ts';
13
13
  * @example const browser = await launchBrowser({ executablePath, logFile: 'logs/browser.log', headless: true })
14
14
  */
15
15
  export declare function launchBrowser(options: LaunchOptions, timeoutMs?: number, transport?: (pipe: PipeStreams) => Transport): Promise<WebRuntime>;
16
+ /**
17
+ * The command line Retest starts Chromium with, the start page last.
18
+ *
19
+ * @example chromiumArguments(profile, true).at(-1) // 'about:blank'
20
+ */
21
+ export declare function chromiumArguments(profile: string, headless: boolean): string[];
16
22
  /**
17
23
  * Asks the browser what it is, and splits its product into a name and a version.
18
24
  *
@@ -1 +1 @@
1
- {"version":3,"file":"launch.d.ts","sourceRoot":"","sources":["../../src/browser/launch.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AACxD,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAChE,OAAO,KAAK,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;AAC9D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAIlD,OAAO,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAA;AAKlD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAgBnD;;;;;;;GAOG;AACH,wBAAsB,aAAa,CACjC,OAAO,EAAE,aAAa,EACtB,SAAS,GAAE,MAAwB,EACnC,SAAS,GAAE,CAAC,IAAI,EAAE,WAAW,KAAK,SAA6C,GAC9E,OAAO,CAAC,UAAU,CAAC,CAsCrB;AAgBD;;;;GAIG;AACH,wBAAsB,SAAS,CAAC,UAAU,EAAE,aAAa,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,cAAc,CAAC,CAOtG;AAcD,+EAA+E;AAC/E,wBAAsB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAOhE;AAED;;;GAGG;AACH,wBAAsB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAejF;AAED,8DAA8D;AAC9D,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,aAAa,GAAG,MAAM,CAWpE;AAED,gHAAgH;AAChH,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAQjE"}
1
+ {"version":3,"file":"launch.d.ts","sourceRoot":"","sources":["../../src/browser/launch.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AACxD,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAChE,OAAO,KAAK,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;AAC9D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAIlD,OAAO,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAA;AAKlD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAgBnD;;;;;;;GAOG;AACH,wBAAsB,aAAa,CACjC,OAAO,EAAE,aAAa,EACtB,SAAS,GAAE,MAAwB,EACnC,SAAS,GAAE,CAAC,IAAI,EAAE,WAAW,KAAK,SAA6C,GAC9E,OAAO,CAAC,UAAU,CAAC,CAsCrB;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,GAAG,MAAM,EAAE,CAoB9E;AAED;;;;GAIG;AACH,wBAAsB,SAAS,CAAC,UAAU,EAAE,aAAa,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,cAAc,CAAC,CAOtG;AAcD,+EAA+E;AAC/E,wBAAsB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAOhE;AAED;;;GAGG;AACH,wBAAsB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAejF;AAED,8DAA8D;AAC9D,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,aAAa,GAAG,MAAM,CAWpE;AAED,gHAAgH;AAChH,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAQjE"}
@@ -73,7 +73,12 @@ export async function launchBrowser(options, timeoutMs = launchTimeoutMs, transp
73
73
  throw new ProcessLaunchError(`${message}${cleanup}`, chromium.gone(), { cause: error, failureClass: problems.length > 0 ? 'cleanup_failed' : 'setup_failed' }, chromium.outputSettled);
74
74
  }
75
75
  }
76
- function chromiumArguments(profile, headless) {
76
+ /**
77
+ * The command line Retest starts Chromium with, the start page last.
78
+ *
79
+ * @example chromiumArguments(profile, true).at(-1) // 'about:blank'
80
+ */
81
+ export function chromiumArguments(profile, headless) {
77
82
  return [
78
83
  '--remote-debugging-pipe',
79
84
  `--user-data-dir=${profile}`,
@@ -81,6 +86,14 @@ function chromiumArguments(profile, headless) {
81
86
  '--no-first-run',
82
87
  '--no-default-browser-check',
83
88
  '--disable-background-networking',
89
+ // A page whose window another window covers, or that is not in front, keeps its renderer's priority and its
90
+ // timers: Chromium otherwise backgrounds an occluded window's renderer and throttles a background page's timers,
91
+ // and a step that waits for the page's next frame or its answer waits on them. These are Chromium's switches
92
+ // (content/public/common/content_switches.cc); Playwright's default Chromium switches pass the same three on every
93
+ // platform (packages/playwright-core/src/server/chromium/chromiumSwitches.ts, Apache-2.0, read 23 October 2026).
94
+ '--disable-backgrounding-occluded-windows',
95
+ '--disable-renderer-backgrounding',
96
+ '--disable-background-timer-throttling',
84
97
  // Keeps a fresh profile out of the login keychain that the person's own Chrome uses.
85
98
  ...(process.platform === 'darwin' ? ['--use-mock-keychain'] : []),
86
99
  'about:blank',
@@ -10,7 +10,7 @@ import { parseArguments, value } from "../arguments.js";
10
10
  import { defaultConfigFile, findConfig, missingConfig } from "../config-file.js";
11
11
  import { runChecks } from "../doctor/checks.js";
12
12
  import { UsageError } from "../errors.js";
13
- import { shouldUseColor } from "../terminal.js";
13
+ import { shouldUseColor, terminalMarks } from "../terminal.js";
14
14
  const options = {
15
15
  config: value({ placeholder: '<path>', description: `Config file. Default: ${defaultConfigFile}` }),
16
16
  };
@@ -43,7 +43,7 @@ export const doctorCommand = {
43
43
  removeLogs(logFolder);
44
44
  if (dependencies.signal.aborted)
45
45
  return interruptedExitCode(dependencies.signal);
46
- const style = createStyle(shouldUseColor(dependencies.stdout, dependencies.env));
46
+ const style = createStyle(shouldUseColor(dependencies.stdout, dependencies.env), terminalMarks(dependencies.stdout, dependencies.env));
47
47
  dependencies.stdout.write(renderChecks(checks, config, style));
48
48
  return problems === 0 ? 0 : 2;
49
49
  },
@@ -71,7 +71,7 @@ export function renderChecks(checks, config, style) {
71
71
  for (const check of shown) {
72
72
  const shownGroup = check.group === group ? '' : check.group;
73
73
  group = check.group;
74
- const mark = check.ok ? style.green('✓') : style.red('✗');
74
+ const mark = check.ok ? style.green(style.marks.passed) : style.red(style.marks.failed);
75
75
  const text = check.detail === undefined ? check.text : `${check.text.padEnd(textWidth)}${style.dim(check.detail)}`;
76
76
  lines.push(` ${style.cyan(shownGroup.padEnd(groupWidth))}${check.subject.padEnd(subjectWidth)}${mark} ${text}`);
77
77
  if (check.fix !== undefined)
@@ -14,7 +14,7 @@ import { CliError, UsageError } from "../errors.js";
14
14
  import { readTestDiagnostics, renderTestDiagnostics } from "../inspect/diagnostics.js";
15
15
  import { renderTimeline } from "../inspect/test-timeline.js";
16
16
  import { suggest } from "../suggest.js";
17
- import { shouldUseColor } from "../terminal.js";
17
+ import { shouldUseColor, terminalMarks } from "../terminal.js";
18
18
  const listedTests = 10;
19
19
  const options = {
20
20
  json: flag('Print one JSON document: the run result, or the test with --test'),
@@ -82,7 +82,7 @@ function writeTest(request, dependencies) {
82
82
  stdout.write(`${JSON.stringify(report, null, 2)}\n`);
83
83
  return;
84
84
  }
85
- const style = createStyle(shouldUseColor(stdout, dependencies.env));
85
+ const style = createStyle(shouldUseColor(stdout, dependencies.env), terminalMarks(stdout, dependencies.env));
86
86
  const targets = runTargets(record, folder.result);
87
87
  stdout.write(`\n${renderTimeline(test, events, { style, runFolder: shown, targets })}`);
88
88
  stdout.write(renderTestDiagnostics(diagnostics, events, { style, runFolder: shown }));
@@ -97,6 +97,7 @@ function writeRun(folder, shown, dependencies) {
97
97
  stdout: dependencies.stdout,
98
98
  stderr: dependencies.stderr,
99
99
  color: shouldUseColor(dependencies.stdout, dependencies.env),
100
+ marks: terminalMarks(dependencies.stdout, dependencies.env),
100
101
  runFolder: shown,
101
102
  });
102
103
  for (const event of folder.events)
@@ -1 +1 @@
1
- {"version":3,"file":"install.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/install.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AA4B5C,eAAO,MAAM,cAAc,EAAE,OAsE5B,CAAA"}
1
+ {"version":3,"file":"install.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/install.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AA+B5C,eAAO,MAAM,cAAc,EAAE,OA8E5B,CAAA"}