1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
//! Shared test harness.
//!
//! Every browser test in this suite used to open with the same twelve lines: find Chrome,
//! `eprintln!("SKIP: …")`, `return`. A bare `return` inside `#[test]` is a pass, so a machine
//! without Chrome — or a fixture deleted by mistake — turned ~57 of 68 tests into green
//! no-ops. This module makes that failure loud when it matters and quiet when it doesn't:
//! locally a missing Chrome still skips, but with `CHROME_AGENT_REQUIRE_CHROME=1` (set in CI)
//! the same condition panics.
use PathBuf;
use Command;
/// Environment variable CI sets to turn every skip into a failure.
pub const REQUIRE_ENV: &str = "CHROME_AGENT_REQUIRE_CHROME";
/// Whether a raw `REQUIRE_ENV` value demands a real browser run. Split from the lookup so
/// the decision is testable without mutating the process environment (`set_var` is unsafe
/// in edition 2024 and this crate forbids `unsafe`).
/// Whether the caller demands a real browser run (CI) or tolerates a skip (a laptop
/// without Chrome).
/// Report a precondition the test cannot meet. Returns `false` (the caller then returns and
/// the test passes as a skip) unless a browser run was required, in which case it panics.
///
/// The pure form is `unavailable_with`; this is the environment-reading wrapper.
/// `unavailable` with the policy passed in.
///
/// # Panics
/// When `require` is set — that is the point: a CI run that silently skips every browser
/// test reports the same green as one that ran them.
/// True when a Chrome binary exists on this machine.
/// `true` when the test may proceed. Skips (or fails, under `REQUIRE_ENV`) otherwise.
/// Absolute path of a fixture, asserted to exist.
///
/// # Panics
/// When the fixture is missing. Deleting a fixture used to leave the tests that load it
/// green: `file://…/gone.html` navigates to an error page and every later assertion was
/// guarded by an early return.
/// `file://` URL of a fixture, asserted to exist.
// ---------------------------------------------------------------------------
// Isolation between concurrent test processes
// ---------------------------------------------------------------------------
/// The binary under test, resolved from the test executable's own location.
///
/// Every suite had its own copy of this, and every copy was identical. It lives here now for
/// the same reason [`TestBrowser`] does: the thing that must not drift is the thing that
/// several files spell the same way.
///
/// One trap it cannot remove, so it is written down instead: `cargo test --test X` does not
/// always rebuild this binary. An A/B that edits `src/` and re-runs one suite can measure the
/// PREVIOUS build and read as a regression that is not there. Run `cargo build` between the
/// two states.
/// A name no other process is using, and no later run of this one will reuse.
///
/// Two ingredients, and both are needed. The pid separates concurrent processes — two
/// `cargo test` runs, which is the normal regime on a machine with several worktrees. The
/// counter separates tests INSIDE one process: the harness runs them on parallel threads, so
/// two tests that happen to pass the same label would otherwise drive one browser, and the
/// first to finish would `close --purge` it under the second.
///
/// Not a random number: a name that appears in a failure message is worth being able to find
/// again in `chrome-agent status` while the run is still going.
/// A browser this test owns, closed and purged when the test ends — including on panic.
///
/// This is the ONE mechanism. Twenty-four suites carried a byte-identical copy of it and five
/// did not, and those five were the ones that failed: a fixed `--browser` name means two
/// concurrent runs drive ONE browser, and the first to finish closes it under the second.
/// Measured, before this existed, by running the whole suite twice at once from two
/// directories: `action_report_tests` died with `transport: transport closed` on the browser
/// named `pipe-bootstrap`, and `proxy_tests` timed out on `test-managed-proxy`. Both had
/// hard-coded their name; neither had a bug.
///
/// RAII, and that matters as much as the name: a plain `close` statement at the end of a
/// helper is skipped when an assertion panics, which leaks a Chrome and a ~14 MB profile
/// directory per failure. `Drop` runs on the unwind.
;
/// A temporary file path this test owns.
///
/// The same rule as the browser name, for the same reason: two suites wrote
/// `/tmp/chrome-agent-<fixed>.jsonl` and `/tmp/chrome-agent-dblclick-selector-test.html`, so a
/// concurrent run could rewrite or unlink the file between another run's write and its read.