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.
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.