Three review findings, no redesign.
seal() was installed after session.close(), so onLate could never fire:
once the context is destroyed no route handler and no console listener
exists to record anything. It was dead code that the attribution table,
run.js and harness.js all described as the final safety net. Deleted
rather than moved — take() already drains everything the collector holds
before teardown, so it covered nothing, and this harness must not ship a
mechanism it cannot demonstrate. The now-unreachable `late > 0` term in
the failure condition goes with it.
handleRpc() parsed `postData || "null"` and then dereferenced the result,
so a POST whose body Playwright reports as null — a bodyless request, or
any payload it cannot decode as UTF-8, e.g. sendBeacon with a Blob —
threw a TypeError inside the route handler and killed node mid-suite:
truncated TAP, no summary, no failure line. Non-object payloads now take
the same path as unparseable ones and are reported as unstubbed traffic,
which is the entire point of that branch.
README claimed unrecognised outbound requests are reported as failures,
unqualified, while observation in fact ends TRAILING_WATCH_MS after the
last test returns. The limit is now stated where it lands.
The error collector had a window API (mark/since) and twice a record fell
outside somebody's window and was silently dropped, producing a green run
that proved nothing: first the mark started after test 1, discarding
everything recorded during launch; then the tail after the final test was
never read at all, so a request escaping the fixtures at the end of the
last test reported 5/5 passed and exit 0.
Rather than patch a second boundary and invite a third, the window
concept is gone. ErrorCollector exposes only take(), which always drains
everything outstanding, so successive takes partition the whole record
stream with no gaps, and seal(), which closes the stream at the end of
the run and routes stragglers straight to a failure. Attribution is
total by construction: launch through test 1 goes to test 1, each
subsequent interval to the test that ends it, the tail to the suite.
The tail also needs to exist before it can be drained. A request a test
fires without awaiting reaches the route handler about 10ms after that
test's function resolves, and closing the context does not wait for it,
so with no window at all it died unobserved. The run now keeps
collecting for a bounded 1.5s after the last test before teardown.
Also:
- README described a canary that was built, found to kill the service
worker, and deleted. Replaced with what actually runs: the harness
waits for the background worker's own startup blocklist fetch to reach
the route handler and aborts if it does not. Documents
--host-resolver-rules=MAP * ~NOTFOUND as defence in depth.
- The canary's failure message asserted traffic was escaping to the real
internet and blamed the -e flag. It cannot distinguish that from a lost
startup race, so it now states what was observed and lists both causes.
- E2E_TRACE_NETWORK was compared strictly to "1", so E2E_TRACE_NETWORK=true
silently did nothing. Recognised on/off values are accepted and anything
else is a hard error rather than a quiet default.
- The measured margin that makes the canary sound is route install at
11-23ms against the worker fetch at 525-883ms, not the 30s timeout
slack the comment cited.
ctx.route() does not see requests made by the background service worker
unless Playwright is run with PW_EXPERIMENTAL_SERVICE_WORKER_NETWORK_EVENTS=1,
so the phishing blocklist fetch that src/background/index.js issues at
worker startup was reaching raw.githubusercontent.com on the real
internet on every run. phishingDomains.js swallows fetch failures, so
nothing surfaced it, and the raw.githubusercontent.com stub in
tests/e2e/network.js was unreachable code that made the gap look covered.
script/test-e2e now sets the flag, with a comment recording what to do if
a future Playwright drops it. The flag being experimental is not taken on
trust: launch() waits for the worker's own startup request to arrive in
the route handler and refuses to run the suite if it never does, so
escaping traffic fails the run instead of passing unnoticed. Chrome is
additionally started with --host-resolver-rules=MAP * ~NOTFOUND, so
anything that does slip past interception cannot reach a real host.
Also: errors and unstubbed requests recorded during launch are attributed
to the first test rather than discarded, a suite that registers no tests
now fails instead of exiting 0, a failure after the browser is up tears
the context down instead of hanging the process, E2E_TRACE_NETWORK=1
prints every routed request tagged [sw] or [page], and the dead exports in
harness.js and network.js are gone.
`make check` was green while the AddToken screen crashed on every open.
`script/lint` is only `prettier --check`, so a used-but-not-imported
identifier is invisible until a browser evaluates it. This adds a suite
that runs the real popup in a real Chrome and treats any uncaught page
error or console.error as a failure.
- `script/test-e2e` (with `make test-e2e` as a thin shim) builds
`dist/chrome/` and runs `tests/e2e/run.js` inside the Playwright image,
pinned by digest. `playwright-core` is pinned to the matching 1.56.0
through `yarn.lock`; the two must be bumped together because the
browsers ship inside the image.
- Deliberately outside `script/test` and `script/check`: REPO_POLICIES
caps `make test` at 20 seconds. Nothing under `tests/e2e/` is named
`*.test.js`, so jest cannot pick it up either.
- Launches with `channel: "chromium"`; the default headless shell
silently refuses to load extensions with no error at all. The extension
id is read from the service worker URL, never hardcoded.
- All http(s) traffic is intercepted at the browser level and served from
fixtures, so the run is deterministic and offline. Unrecognised
outbound requests are reported as failures rather than allowed.
- A missing build or an unavailable container fails loudly; a skip that
looks like a pass is the failure mode this is meant to prevent.
- One allowlisted page error, for the libsodium WASM CSP fallback tracked
as #182, which is otherwise untouched here.
The suite was demonstrated failing against the unfixed tree with
`pageerror: showView is not defined` and `pageerror: addressDotHtml is
not defined`, so it carries the two one-line import fixes it caught:
closes#150 — `showView` restored to the destructure in
`src/popup/views/addToken.js`, dropped by a22f33d, which made the
AddToken screen unreachable and corrupted the navigation stack.
closes#151 — `addressDotHtml` restored in
`src/popup/views/transactionDetail.js`, dropped by df031fd, which threw
before `showView("transaction")` for every ERC-20 transfer. The shared
`renderAddressHtml` helper is not used here on purpose: it hardcodes the
`/address/` explorer URL, and this row needs the token-specific `/token/`
link.