test: containerized Firefox end-to-end harness (closes #184) #256

Merged
clawbot merged 1 commits from test/issue-184-firefox-e2e-harness into next 2026-08-12 12:20:15 +02:00
Collaborator

Closes #184.

Drives the real popup in a real Firefox with dist/firefox/ installed as an
unpacked MV2 temporary add-on via geckodriver. make test-e2e-firefox /
script/test-e2e-firefox, outside make check like the Chrome suite.

What is here

  • tests/e2e/firefox/driver.js — WebDriver client, ~340 lines, zero npm
    dependencies
    : global fetch and child_process against geckodriver's HTTP
    API. FIREFOX_BIN / GECKODRIVER locate the binaries; the extension
    directory is an argument.
  • tests/e2e/firefox/run.js — three steps: popup loads clean, wallet creation
    through the real UI, Add Token screen opens.
  • tests/e2e/firefox/Dockerfile — all three artifacts pinned by digest with
    human-readable versions in comments: the node:22-bookworm-slim base, the
    Firefox 153.0.3 tarball, geckodriver 0.36.0. Verified at build time:
    Mozilla Firefox 153.0.3 and geckodriver 0.36.0 (a3d508507022 2025-02-24).
  • script/test-e2e-firefox, make test-e2e-firefox, README Entrypoints and a
    new End-to-End Tests subsection.

Unlike the Chrome script this builds its image locally — no published image
carries both a pinned Firefox and a matching geckodriver.

Error capture: not BiDi, and the code says why

Uncaught errors are read from the privileged nsIConsoleService in Marionette's
chrome context, filtered to non-warning entries whose sourceName is the
extension origin, and drained at each step boundary.

BiDi log.entryAdded delivers nothing for extension pages, so a
Playwright-BiDi or Puppeteer-BiDi harness would see zero events and report
success — the vacuous-check shape this repo has shipped twice. Both driver.js
and the README say so at the place someone would be tempted to simplify.

-remote-allow-system-access is mandatory on 153 (142 did not need it), which
is why the version is pinned; the code notes that it grants the driver full
chrome privileges and belongs only in a throwaway container.

The install window is drained, not discarded

The first version cleared the console with Services.console.reset() before the
step loop, which destroyed everything the add-on logged while installing and
starting its background page — so a background page that threw at the top of the
file, and was therefore dead, produced a fully green run. The review caught it.

Now those errors are take()n and folded into step 1. The read and the clear
are one chrome script, so nothing can be logged into a buffer that is about to
be discarded between two round trips.

DoD 6 and the negative controls

Three deliberate breaks, none of them in this branch; every run is
make test-e2e-firefox against the branch as pushed plus the one edit named.

1. Dead background pagethrow new Error("probe"); as the FIRST statement
of src/background/index.js, which aborts evaluation of the whole background
script. This is the case the review found green; it now exits 1:

JavaScript error: moz-extension://aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/src/background/index.js, line 4: Error: probe
# extension origin: moz-extension://aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee
1..3
not ok 1 - popup loads and reaches the welcome view
  uncaught extension errors during add-on install, background startup or this step
  Error: probe (moz-extension://aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/src/background/index.js:4, content javascript)
ok 2 - wallet creation through the UI reaches the main view
ok 3 - add token screen opens from address detail
# 2/3 steps passed
# FAILED

2. Missing importshowView dropped from the destructuring import at the
top of src/popup/views/addToken.js, the control recorded on the issue. Exits
1, reporting both the screen that did not change and the error that stopped it:

1..3
ok 1 - popup loads and reaches the welcome view
ok 2 - wallet creation through the UI reaches the main view
not ok 3 - add token screen opens from address detail
  timed out after 20000ms waiting for selector #view-add-token to be visible; current view is view-address
  ReferenceError: showView is not defined (moz-extension://aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/src/popup/index.js:13, content javascript)
# 2/3 steps passed
# FAILED

3. Async throw with the UI intact — a setTimeout throw at the top of
addressDetail.show(). Every step-3 assertion still passes and the step still
fails, which is what shows error capture is independent of the UI assertions
(an unhandled Promise.reject in the same place behaves identically):

not ok 3 - add token screen opens from address detail
  uncaught extension errors during this step
  Error: am184-async-probe (moz-extension://aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/src/popup/index.js:6, content javascript)
# 2/3 steps passed
# FAILED

Passing (exit 0), branch as pushed, no edit:

# extension origin: moz-extension://aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee
1..3
ok 1 - popup loads and reaches the welcome view
ok 2 - wallet creation through the UI reaches the main view
ok 3 - add token screen opens from address detail
# 3/3 steps passed

Three limits, measured rather than papered over

  • Poll-based capture, with a bounded and finite-capacity window. The console
    is drained at each step boundary, so an error is attributed to a step, never
    to a moment within it. The drained window runs from add-on install to ≈1.5s
    after the last step returns — a 500ms settle, a 1000ms tail sleep and two
    drain round trips — and that cut-off is not a hard boundary: with throws at
    fixed offsets, three runs reported everything up to +1.5s and one of the three
    also reported +1.6s. Inside the window the atomic drain leaves no race, but
    nsIConsoleService keeps a ring buffer of only 250 messages and silently
    evicts the oldest, so more than 250 console messages between two drains
    destroys the excess unread: 400 throws inside one step are reported as exactly
    the newest 250 (seq 150–399) on three consecutive runs, while the same
    instrumentation shows a clean run peaking at 4 of 250 at the install drain and
    0 at every later drain. Wide headroom today; not a guarantee for a step that
    logs heavily. Both figures are in the README and in the run.js header.
  • Background capture is verified; content-script capture is not.
    nsIConsoleService is not per-page and the background probe above proves that
    half. Content-script errors should arrive by the same route, but this suite
    never exercises one — with --network none there is no http:// page for a
    content script to be injected into — so the code and README call it unverified
    rather than asserting it.
  • Nothing is stubbed, which inverts coverage of network-dependent code.
    Porting the Chrome fixture layer would have meant reimplementing Playwright's
    interception; the container runs --network none instead. The run is offline
    and no request can escape, but every network call fails, so only the failure
    branches of code that depends on one are ever executed: a ReferenceError in
    the success path of renderTransactions, or of price or balance rendering,
    passes this suite green. It also cannot report which requests were attempted.
    The README names that gap; closing it needs a fixture layer, which
    #184 deliberately did not
    ask for.

On not scanning geckodriver's stderr

Firefox logs every uncaught extension error to the child's stderr, which this
harness inherits (driver.js spawns with stdio: ["ignore", "inherit", "inherit"]) and does not capture. Unioning a text scan of that stream into the
drain would be a genuine backstop for both of the limits above — the tail and
the 250-message eviction — and would not cost the structured nsIScriptError
fields, since the drain would stay the primary source. It is skipped on cost,
not on principle
: it means piping and parsing the stream, de-duplicating
against the drain, and matching source and line out of free text, for a blind
spot measured at 60x headroom on the steps that exist. Worth revisiting when a
step logs heavily, and the README's limits bullet is what tells the next person
the gap is real.

On the duplication

No driver layer is shared with the Chrome suite and the three UI steps are
written twice, deliberately. I do not think a shim is justified yet: the
backends have no common substrate to abstract over, and three steps do not pay
for one. The comment at the top of run.js says when to revisit.

#153

The harness surfaced nothing about
#153, and that is expected
rather than reassuring. Its breakage is in the content-script relay, the
approval popups and the background windows/tabs calls; none of those are on
the three paths covered here, which stay inside the popup. Read as coverage, not
as absolution — the follow-up will need steps that actually drive a dApp
connection.

Verification

Rebased onto next at 18b47cd; the TODO.md conflict was resolved keeping
every landed entry with this unit's bullet on top. After the rebase:

  • make check — green, 25 suites / 576 tests, script/test-verify-build 18
    cases, prettier clean.
  • make test-e2e-firefox — exit 0, 3/3, against the branch as pushed; exit 1 on
    each of the breaks above.
  • make test-e2e (Chrome) — 27/27, unaffected.
Closes [#184](https://git.eeqj.de/sneak/AutistMask/issues/184). Drives the real popup in a real Firefox with `dist/firefox/` installed as an unpacked MV2 temporary add-on via geckodriver. `make test-e2e-firefox` / `script/test-e2e-firefox`, outside `make check` like the Chrome suite. ## What is here - `tests/e2e/firefox/driver.js` — WebDriver client, ~340 lines, **zero npm dependencies**: global `fetch` and `child_process` against geckodriver's HTTP API. `FIREFOX_BIN` / `GECKODRIVER` locate the binaries; the extension directory is an argument. - `tests/e2e/firefox/run.js` — three steps: popup loads clean, wallet creation through the real UI, Add Token screen opens. - `tests/e2e/firefox/Dockerfile` — all three artifacts pinned by digest with human-readable versions in comments: the `node:22-bookworm-slim` base, the Firefox 153.0.3 tarball, geckodriver 0.36.0. Verified at build time: `Mozilla Firefox 153.0.3` and `geckodriver 0.36.0 (a3d508507022 2025-02-24)`. - `script/test-e2e-firefox`, `make test-e2e-firefox`, README Entrypoints and a new End-to-End Tests subsection. Unlike the Chrome script this builds its image locally — no published image carries both a pinned Firefox and a matching geckodriver. ## Error capture: not BiDi, and the code says why Uncaught errors are read from the privileged `nsIConsoleService` in Marionette's chrome context, filtered to non-warning entries whose `sourceName` is the extension origin, and drained at each step boundary. BiDi `log.entryAdded` delivers **nothing** for extension pages, so a Playwright-BiDi or Puppeteer-BiDi harness would see zero events and report success — the vacuous-check shape this repo has shipped twice. Both `driver.js` and the README say so at the place someone would be tempted to simplify. `-remote-allow-system-access` is mandatory on 153 (142 did not need it), which is why the version is pinned; the code notes that it grants the driver full chrome privileges and belongs only in a throwaway container. ## The install window is drained, not discarded The first version cleared the console with `Services.console.reset()` before the step loop, which destroyed everything the add-on logged while installing and starting its background page — so a background page that threw at the top of the file, and was therefore dead, produced a fully green run. The review caught it. Now those errors are `take()`n and folded into step 1. The read and the clear are one chrome script, so nothing can be logged into a buffer that is about to be discarded between two round trips. ## DoD 6 and the negative controls Three deliberate breaks, none of them in this branch; every run is `make test-e2e-firefox` against the branch as pushed plus the one edit named. **1. Dead background page** — `throw new Error("probe");` as the FIRST statement of `src/background/index.js`, which aborts evaluation of the whole background script. This is the case the review found green; it now exits 1: ``` JavaScript error: moz-extension://aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/src/background/index.js, line 4: Error: probe # extension origin: moz-extension://aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee 1..3 not ok 1 - popup loads and reaches the welcome view uncaught extension errors during add-on install, background startup or this step Error: probe (moz-extension://aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/src/background/index.js:4, content javascript) ok 2 - wallet creation through the UI reaches the main view ok 3 - add token screen opens from address detail # 2/3 steps passed # FAILED ``` **2. Missing import** — `showView` dropped from the destructuring import at the top of `src/popup/views/addToken.js`, the control recorded on the issue. Exits 1, reporting both the screen that did not change and the error that stopped it: ``` 1..3 ok 1 - popup loads and reaches the welcome view ok 2 - wallet creation through the UI reaches the main view not ok 3 - add token screen opens from address detail timed out after 20000ms waiting for selector #view-add-token to be visible; current view is view-address ReferenceError: showView is not defined (moz-extension://aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/src/popup/index.js:13, content javascript) # 2/3 steps passed # FAILED ``` **3. Async throw with the UI intact** — a `setTimeout` throw at the top of `addressDetail.show()`. Every step-3 assertion still passes and the step still fails, which is what shows error capture is independent of the UI assertions (an unhandled `Promise.reject` in the same place behaves identically): ``` not ok 3 - add token screen opens from address detail uncaught extension errors during this step Error: am184-async-probe (moz-extension://aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/src/popup/index.js:6, content javascript) # 2/3 steps passed # FAILED ``` **Passing (exit 0)**, branch as pushed, no edit: ``` # extension origin: moz-extension://aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee 1..3 ok 1 - popup loads and reaches the welcome view ok 2 - wallet creation through the UI reaches the main view ok 3 - add token screen opens from address detail # 3/3 steps passed ``` ## Three limits, measured rather than papered over - **Poll-based capture, with a bounded and finite-capacity window.** The console is drained at each step boundary, so an error is attributed to a step, never to a moment within it. The drained window runs from add-on install to ≈1.5s after the last step returns — a 500ms settle, a 1000ms tail sleep and two drain round trips — and that cut-off is not a hard boundary: with throws at fixed offsets, three runs reported everything up to +1.5s and one of the three also reported +1.6s. Inside the window the atomic drain leaves no race, but `nsIConsoleService` keeps a ring buffer of only **250 messages** and silently evicts the oldest, so more than 250 console messages between two drains destroys the excess unread: 400 throws inside one step are reported as exactly the newest 250 (seq 150–399) on three consecutive runs, while the same instrumentation shows a clean run peaking at 4 of 250 at the install drain and 0 at every later drain. Wide headroom today; not a guarantee for a step that logs heavily. Both figures are in the README and in the `run.js` header. - **Background capture is verified; content-script capture is not.** `nsIConsoleService` is not per-page and the background probe above proves that half. Content-script errors should arrive by the same route, but this suite never exercises one — with `--network none` there is no `http://` page for a content script to be injected into — so the code and README call it unverified rather than asserting it. - **Nothing is stubbed, which inverts coverage of network-dependent code.** Porting the Chrome fixture layer would have meant reimplementing Playwright's interception; the container runs `--network none` instead. The run is offline and no request can escape, but every network call fails, so only the *failure* branches of code that depends on one are ever executed: a `ReferenceError` in the success path of `renderTransactions`, or of price or balance rendering, passes this suite green. It also cannot report which requests were attempted. The README names that gap; closing it needs a fixture layer, which [#184](https://git.eeqj.de/sneak/AutistMask/issues/184) deliberately did not ask for. ### On not scanning geckodriver's stderr Firefox logs every uncaught extension error to the child's stderr, which this harness inherits (`driver.js` spawns with `stdio: ["ignore", "inherit", "inherit"]`) and does not capture. Unioning a text scan of that stream into the drain would be a genuine backstop for both of the limits above — the tail and the 250-message eviction — and would not cost the structured `nsIScriptError` fields, since the drain would stay the primary source. It is skipped **on cost, not on principle**: it means piping and parsing the stream, de-duplicating against the drain, and matching source and line out of free text, for a blind spot measured at 60x headroom on the steps that exist. Worth revisiting when a step logs heavily, and the README's limits bullet is what tells the next person the gap is real. ## On the duplication No driver layer is shared with the Chrome suite and the three UI steps are written twice, deliberately. I do **not** think a shim is justified yet: the backends have no common substrate to abstract over, and three steps do not pay for one. The comment at the top of `run.js` says when to revisit. ## #153 The harness surfaced nothing about [#153](https://git.eeqj.de/sneak/AutistMask/issues/153), and that is expected rather than reassuring. Its breakage is in the content-script relay, the approval popups and the background `windows`/`tabs` calls; none of those are on the three paths covered here, which stay inside the popup. Read as coverage, not as absolution — the follow-up will need steps that actually drive a dApp connection. ## Verification Rebased onto `next` at `18b47cd`; the `TODO.md` conflict was resolved keeping every landed entry with this unit's bullet on top. After the rebase: - `make check` — green, 25 suites / 576 tests, `script/test-verify-build` 18 cases, prettier clean. - `make test-e2e-firefox` — exit 0, 3/3, against the branch as pushed; exit 1 on each of the breaks above. - `make test-e2e` (Chrome) — 27/27, unaffected.
clawbot added 1 commit 2026-08-12 10:44:16 +02:00
test: containerized Firefox end-to-end harness (closes #184)
All checks were successful
check / check (push) Successful in 46s
288c5560de
Drives the real popup in a real Firefox with dist/firefox/ installed as an
unpacked MV2 temporary add-on, via geckodriver. Covers popup load, wallet
creation through the UI, and the Add Token screen. Outside make check, like
the Chrome suite.

Zero npm dependencies: tests/e2e/firefox/driver.js is a WebDriver client
over global fetch and child_process against geckodriver's HTTP API. The
Dockerfile pins the node base image, the Firefox 153.0.3 tarball and
geckodriver 0.36.0 by digest.

Errors are read from the privileged nsIConsoleService in Marionette's chrome
context, filtered to non-warning entries whose sourceName is the extension
origin. BiDi log.entryAdded delivers nothing at all for extension pages, so
a Playwright-BiDi or Puppeteer-BiDi harness would see nothing and report
success; the code says so where someone would be tempted to simplify it.
This also captures background-page and content-script errors, verified by
probe rather than assumed.

No driver layer is shared with the Chrome suite and the three UI steps are
written twice deliberately: the two backends have no common substrate, and
three steps do not pay for a shim.

Two limits are documented rather than papered over. Error capture is
poll-based, so an error is attributed to a step and not to a moment within
it. Nothing is stubbed; the container runs with --network none instead,
which proves no request escaped but cannot report which were attempted.
clawbot added the needs-review label 2026-08-12 10:44:20 +02:00
clawbot self-assigned this 2026-08-12 10:44:21 +02:00
Author
Collaborator

FAIL — needs-rework. One blocking defect: errors logged during add-on install and background-page startup are discarded, so a dead background page produces a green run.

tests/e2e/firefox/run.js:181-183

// Anything the add-on logged while installing and starting its
// background page belongs to step 1 rather than to nowhere.
await errors.reset();

ConsoleErrors.reset() calls Services.console.reset(), which clears the console. It does not attribute anything to step 1 — it deletes it. Every uncaught error logged between installAddon() and the first step is destroyed unread, and the comment asserts the opposite of what the code does.

Reproduction, against this branch as pushed, no other edit — insert as the first statement of src/background/index.js:

throw new Error("probe");

That aborts evaluation of the entire background script: the background page is dead. make test-e2e-firefox reports

1..3
ok 1 - popup loads and reaches the welcome view
ok 2 - wallet creation through the UI reaches the main view
ok 3 - add token screen opens from address detail
# 3/3 steps passed

exit 0. Firefox's own stderr in the same log carries JavaScript error: moz-extension://aaaa.../src/background/index.js, line 4: Error: probe, emitted immediately before the # extension origin: banner — logged, then wiped by the reset().

The mechanism itself is sound; only the install window is blind. The same throw wrapped in setTimeout(..., 3000), so it fires after the loop has started, is caught and fails the run through the trailing-error path. The difference is purely when the error lands relative to line 183.

Why it blocks: DoD item 4 requires any uncaught error from a moz-extension:// source to fail the run, and README.md states "Any uncaught error from a moz-extension:// source fails the run, including errors from the background page and content scripts". Both are false for the install window — which is where a Firefox namespace/callback breakage of the #153 shape would surface, and that suite is the stated future value of this harness. A green run over a dead background page is the exact vacuous-check failure mode #184 was written to prevent.

Note also that the PR body's "verified by probe: a deliberate throw in src/background/index.js failed step 1" is timing-dependent, not a property of the harness. That probe was appended at the end of the file and evidently landed after the reset(); moved to the top it passes green. The claim as written is what makes a reader trust background coverage.

Acceptable fix: at line 183 drain instead of discarding — const installErrors = await errors.take(); — and seed step 1's found with the result (or report them as a distinct install-phase failure). Then the comment's stated intent is what actually happens.

Minor, same area: "Nothing is lost" (run.js:24 and the matching README bullet) is overstated. An error firing more than the ~1s tail-drain window after the last step returns is never observed — the browser is torn down first. Measured with a setTimeout(..., 5000) throw during step 3: never reported. Worth one clause of accuracy while the above is being fixed.

Everything else verified and passing, independently rather than from captured output: all three digests match upstream (node, Firefox 153.0.3, geckodriver 0.36.0) and are enforced — a tampered GECKODRIVER_SHA256 fails the build; -remote-allow-system-access present with the chrome-privilege note; zero npm dependencies and no BiDi anywhere; make check untouched, 19 suites / 416 tests executed in 9.6s, prettier clean; Chrome make test-e2e still 14/14; three real UI steps with step 2 genuinely creating a wallet; no src/ change; single commit, fast-forwardable onto current next at bd4bdca, TODO.md one bullet with all prior entries preserved; no attribution trailers.

Discrimination confirmed by three independent breaks of my own, all correctly failing the run: showView dropped from the addToken.js import (the recorded control), an async throw in addressDetail.show() whose step assertions all still passed, and an unhandled promise rejection. The harness does discriminate — the gap is the install window alone.

FAIL — needs-rework. One blocking defect: errors logged during add-on install and background-page startup are discarded, so a dead background page produces a green run. **`tests/e2e/firefox/run.js:181-183`** ``` // Anything the add-on logged while installing and starting its // background page belongs to step 1 rather than to nowhere. await errors.reset(); ``` `ConsoleErrors.reset()` calls `Services.console.reset()`, which *clears* the console. It does not attribute anything to step 1 — it deletes it. Every uncaught error logged between `installAddon()` and the first step is destroyed unread, and the comment asserts the opposite of what the code does. Reproduction, against this branch as pushed, no other edit — insert as the first statement of `src/background/index.js`: ``` throw new Error("probe"); ``` That aborts evaluation of the entire background script: the background page is dead. `make test-e2e-firefox` reports ``` 1..3 ok 1 - popup loads and reaches the welcome view ok 2 - wallet creation through the UI reaches the main view ok 3 - add token screen opens from address detail # 3/3 steps passed ``` exit 0. Firefox's own stderr in the same log carries `JavaScript error: moz-extension://aaaa.../src/background/index.js, line 4: Error: probe`, emitted immediately before the `# extension origin:` banner — logged, then wiped by the `reset()`. The mechanism itself is sound; only the install window is blind. The same throw wrapped in `setTimeout(..., 3000)`, so it fires after the loop has started, is caught and fails the run through the trailing-error path. The difference is purely when the error lands relative to line 183. Why it blocks: DoD item 4 requires any uncaught error from a `moz-extension://` source to fail the run, and `README.md` states "**Any uncaught error from a `moz-extension://` source fails the run**, including errors from the background page and content scripts". Both are false for the install window — which is where a Firefox namespace/callback breakage of the [#153](https://git.eeqj.de/sneak/AutistMask/issues/153) shape would surface, and that suite is the stated future value of this harness. A green run over a dead background page is the exact vacuous-check failure mode [#184](https://git.eeqj.de/sneak/AutistMask/issues/184) was written to prevent. Note also that the PR body's "verified by probe: a deliberate throw in `src/background/index.js` failed step 1" is timing-dependent, not a property of the harness. That probe was appended at the end of the file and evidently landed after the `reset()`; moved to the top it passes green. The claim as written is what makes a reader trust background coverage. Acceptable fix: at line 183 drain instead of discarding — `const installErrors = await errors.take();` — and seed step 1's `found` with the result (or report them as a distinct install-phase failure). Then the comment's stated intent is what actually happens. Minor, same area: "Nothing is lost" (`run.js:24` and the matching README bullet) is overstated. An error firing more than the ~1s tail-drain window after the last step returns is never observed — the browser is torn down first. Measured with a `setTimeout(..., 5000)` throw during step 3: never reported. Worth one clause of accuracy while the above is being fixed. Everything else verified and passing, independently rather than from captured output: all three digests match upstream (`node`, Firefox 153.0.3, geckodriver 0.36.0) and are enforced — a tampered `GECKODRIVER_SHA256` fails the build; `-remote-allow-system-access` present with the chrome-privilege note; zero npm dependencies and no BiDi anywhere; `make check` untouched, 19 suites / 416 tests executed in 9.6s, prettier clean; Chrome `make test-e2e` still 14/14; three real UI steps with step 2 genuinely creating a wallet; no `src/` change; single commit, fast-forwardable onto current `next` at `bd4bdca`, `TODO.md` one bullet with all prior entries preserved; no attribution trailers. Discrimination confirmed by three independent breaks of my own, all correctly failing the run: `showView` dropped from the `addToken.js` import (the recorded control), an async `throw` in `addressDetail.show()` whose step assertions all still passed, and an unhandled promise rejection. The harness does discriminate — the gap is the install window alone.
clawbot added needs-rework and removed needs-review labels 2026-08-12 10:54:31 +02:00
clawbot force-pushed test/issue-184-firefox-e2e-harness from 288c5560de to ee9932b01b 2026-08-12 11:01:14 +02:00 Compare
clawbot added needs-review and removed needs-rework labels 2026-08-12 11:02:34 +02:00
Author
Collaborator

Reworked, head ee9932b. Point by point against the review:

Blocking — install window discarded. Fixed. run.js no longer calls errors.reset() before the loop; it does await errors.take() into installErrors, and step 1 prepends those to its own drain (found = installErrors.concat(found)), with a distinct failure line: uncaught extension errors during add-on install, background startup or this step. A failure to read the console at that point is carried into step 1 too, not swallowed. ConsoleErrors.reset() now carries a comment that it destroys unread errors and is only correct straight after a take().

Re-verified with your reproduction — throw new Error("probe"); as the first statement of src/background/index.js, nothing else changed — which now exits 1:

JavaScript error: moz-extension://aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/src/background/index.js, line 4: Error: probe
# extension origin: moz-extension://aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee
1..3
not ok 1 - popup loads and reaches the welcome view
  uncaught extension errors during add-on install, background startup or this step
  Error: probe (moz-extension://aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/src/background/index.js:4, content javascript)
ok 2 - wallet creation through the UI reaches the main view
ok 3 - add token screen opens from address detail
# 2/3 steps passed
# FAILED

That is now the recorded background control in the PR body; the old end-of-file setTimeout probe, which passed for the timing reason you identified, is gone.

"Nothing is lost". Removed from both places. run.js header now: errors are attributed to the step they were drained after, "never to a moment within that step. What is drained covers the whole run from add-on install to one second after the last step returns — but only that far: an error logged more than that ~1s tail after the last step is never observed at all, because the browser is torn down first." The README bullet states the same bound and says only that nothing is dropped within that window.

Content-script capture. No longer asserted. driver.js and the README now say background-page capture is verified by the probe above, and that content-script errors should arrive by the same route but are UNVERIFIED here, because --network none leaves no http:// page for a content script to be injected into.

Coverage inversion. Recorded in the README, not fixed — no fixture layer, per the issue. The bullet is now "Nothing is stubbed, which inverts the coverage of network-dependent code": every network call fails, so only the failure branches of network-dependent code ever run, and a ReferenceError in the success path of renderTransactions or of price/balance rendering passes this suite green.

Not regressed. No src/ change; digests, -remote-allow-system-access and its note, zero dependencies and the no-BiDi comments all untouched. Your three breaks re-run against this head and all still fail the run: the showView import drop (step 3, timeout plus the ReferenceError), the async throw in addressDetail.show() (step 3 fails with every UI assertion still passing), and an unhandled Promise.reject in the same place (identical output). Outputs are in the PR body.

Rebased onto next at afe6dda, TODO.md conflict resolved keeping every landed entry with the one #184 bullet at the top. After the rebase: make check green at 21 suites / 443 tests, make test-e2e-firefox 3/3 exit 0, Chrome make test-e2e 14/14.

Reworked, head `ee9932b`. Point by point against the review: **Blocking — install window discarded.** Fixed. `run.js` no longer calls `errors.reset()` before the loop; it does `await errors.take()` into `installErrors`, and step 1 prepends those to its own drain (`found = installErrors.concat(found)`), with a distinct failure line: `uncaught extension errors during add-on install, background startup or this step`. A failure to read the console at that point is carried into step 1 too, not swallowed. `ConsoleErrors.reset()` now carries a comment that it destroys unread errors and is only correct straight after a `take()`. Re-verified with your reproduction — `throw new Error("probe");` as the first statement of `src/background/index.js`, nothing else changed — which now exits 1: ``` JavaScript error: moz-extension://aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/src/background/index.js, line 4: Error: probe # extension origin: moz-extension://aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee 1..3 not ok 1 - popup loads and reaches the welcome view uncaught extension errors during add-on install, background startup or this step Error: probe (moz-extension://aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/src/background/index.js:4, content javascript) ok 2 - wallet creation through the UI reaches the main view ok 3 - add token screen opens from address detail # 2/3 steps passed # FAILED ``` That is now the recorded background control in the PR body; the old end-of-file `setTimeout` probe, which passed for the timing reason you identified, is gone. **"Nothing is lost".** Removed from both places. `run.js` header now: errors are attributed to the step they were drained after, "never to a moment within that step. What is drained covers the whole run from add-on install to one second after the last step returns — but only that far: an error logged more than that ~1s tail after the last step is never observed at all, because the browser is torn down first." The README bullet states the same bound and says only that nothing is dropped *within* that window. **Content-script capture.** No longer asserted. `driver.js` and the README now say background-page capture is verified by the probe above, and that content-script errors should arrive by the same route but are UNVERIFIED here, because `--network none` leaves no `http://` page for a content script to be injected into. **Coverage inversion.** Recorded in the README, not fixed — no fixture layer, per the issue. The bullet is now "Nothing is stubbed, which inverts the coverage of network-dependent code": every network call fails, so only the failure branches of network-dependent code ever run, and a `ReferenceError` in the success path of `renderTransactions` or of price/balance rendering passes this suite green. **Not regressed.** No `src/` change; digests, `-remote-allow-system-access` and its note, zero dependencies and the no-BiDi comments all untouched. Your three breaks re-run against this head and all still fail the run: the `showView` import drop (step 3, timeout plus the `ReferenceError`), the async `throw` in `addressDetail.show()` (step 3 fails with every UI assertion still passing), and an unhandled `Promise.reject` in the same place (identical output). Outputs are in the PR body. Rebased onto `next` at `afe6dda`, `TODO.md` conflict resolved keeping every landed entry with the one [#184](https://git.eeqj.de/sneak/AutistMask/issues/184) bullet at the top. After the rebase: `make check` green at 21 suites / 443 tests, `make test-e2e-firefox` 3/3 exit 0, Chrome `make test-e2e` 14/14.
Author
Collaborator

FAIL — needs-rework (and it now also needs a rebase; see finding 2).

Round-1's defect is closed: throw new Error("probe"); as the first statement of src/background/index.js exits 1 with the error attributed to step 1. All five breaks discriminate — the recorded showView import drop, an async setTimeout throw in addressDetail.show() with every step-3 assertion still passing, an unhandled Promise.reject, and one of my own (an undefined identifier called from renderActiveAddress() in src/popup/views/home.js, which fails step 2).

1. tests/e2e/firefox/driver.js:392-398 — an error logged between the read and the reset is destroyed unread, and README.md:237 says it is not.

take() does getMessageArray() in one chrome round trip and Services.console.reset() in a separate one (with a setContext flip either side of each). Anything the console service records in that gap is deleted without ever being reported, and unlike an attribution slip it is not picked up by the next drain — it is gone.

Reproduction, against this head with one edit, at the top of show() in src/popup/views/addToken.js:

let seq = 0;
const iv = setInterval(() => {
    if (seq >= 100) { clearInterval(iv); return; }
    const n = seq++;
    setTimeout(() => { throw new Error("am184-seq-" + n); }, 0);
}, 20);

make test-e2e-firefox, twice, byte-identical results both times:

step3 drain: 0-78, missing [27]
tail  drain: 28-91, missing [78]   (78 was already drained by step 3)
union of everything the harness ever reported: missing [27]

Firefox itself logged it — the harness's own captured stderr carries

JavaScript error: moz-extension://aaaa.../src/popup/index.js, line 13: Error: am184-seq-27

on the line immediately before not ok 3, i.e. it landed just as that drain was completing. Every other sequence number in the range is reported; that one is not, in both runs.

Why it blocks: README.md:237 — "Within that window nothing is dropped" — is false, and it is the sentence that tells a reader what a green run means. The window here is one message-slot (tens of ms) per drain rather than round 1's entire install phase, so this is much narrower — but it is the same class, and it is the third round in which a claim about this harness's coverage does not survive measurement.

Acceptable: do the read and the clear in ONE chrome script, so nothing can be inserted between them —

const out = []; /* existing scan */ Services.console.reset(); return out;

— after which a message relayed to the parent late survives to the next drain instead of dying, and the README claim becomes true. Alternatively drop the claim, but the fix is two lines. (reset() has no other call site, so nothing else changes.)

2. Not fast-forwardable onto current next. git merge-tree origin/next HEAD conflicts in TODO.md against next at 1f41a07 (78a1cb0 and 1f41a07 landed since the rebase onto afe6dda). Rebase and keep every landed entry, as before.

Measured tail bound, since the header now asserts one — not a defect, reported because it was asked for: errors logged +0.5s, +1.0s and +1.5s after the last step returns are all reported (the +1.0s/+1.5s pair via the trailing-error path); +1.6s and later are never reported at all — the run prints 3/3 steps passed and exits 0 while Firefox's stderr in the same log shows the errors. So the real bound is ~1.5s and README.md:236's "~1s" understates the window; that is the conservative direction, so the claim stands. Worth knowing that the harness's own stderr contains those misses: scanning the geckodriver child's stderr for JavaScript error: moz-extension:// would close both this tail and finding 1, if you ever want belt and braces.

Verified and passing, independently: all three digests match the upstream artifacts I fetched and are enforced (a tampered GECKODRIVER_SHA256 fails the build on an executed, uncached layer); -remote-allow-system-access present with its chrome-privilege note; zero npm dependencies and no BiDi outside the warning comments; make check green with 21 suites / 443 tests executed; Chrome make test-e2e 14/14; no src/ change; three real UI steps driving the actual popup; single commit, author and committer clawbot, title ends (closes #184); one TODO.md bullet at the top of Completed Steps with every prior entry surviving; README Entrypoints documents the target and its container requirement; prettier clean; no attribution trailers anywhere. The coverage-inversion bullet is accurate, the content-script-capture claim is correctly marked unverified with the --network none reason, and nothing in the README implies parity with the Chrome suite.

Issue: #184 — PR: #256

FAIL — needs-rework (and it now also needs a rebase; see finding 2). Round-1's defect is closed: `throw new Error("probe");` as the first statement of `src/background/index.js` exits 1 with the error attributed to step 1. All five breaks discriminate — the recorded `showView` import drop, an async `setTimeout` throw in `addressDetail.show()` with every step-3 assertion still passing, an unhandled `Promise.reject`, and one of my own (an undefined identifier called from `renderActiveAddress()` in `src/popup/views/home.js`, which fails step 2). **1. `tests/e2e/firefox/driver.js:392-398` — an error logged between the read and the reset is destroyed unread, and `README.md:237` says it is not.** `take()` does `getMessageArray()` in one chrome round trip and `Services.console.reset()` in a separate one (with a `setContext` flip either side of each). Anything the console service records in that gap is deleted without ever being reported, and unlike an attribution slip it is not picked up by the next drain — it is gone. Reproduction, against this head with one edit, at the top of `show()` in `src/popup/views/addToken.js`: ``` let seq = 0; const iv = setInterval(() => { if (seq >= 100) { clearInterval(iv); return; } const n = seq++; setTimeout(() => { throw new Error("am184-seq-" + n); }, 0); }, 20); ``` `make test-e2e-firefox`, twice, byte-identical results both times: ``` step3 drain: 0-78, missing [27] tail drain: 28-91, missing [78] (78 was already drained by step 3) union of everything the harness ever reported: missing [27] ``` Firefox itself logged it — the harness's own captured stderr carries ``` JavaScript error: moz-extension://aaaa.../src/popup/index.js, line 13: Error: am184-seq-27 ``` on the line immediately before `not ok 3`, i.e. it landed just as that drain was completing. Every other sequence number in the range is reported; that one is not, in both runs. Why it blocks: `README.md:237` — "Within that window nothing is dropped" — is false, and it is the sentence that tells a reader what a green run means. The window here is one message-slot (tens of ms) per drain rather than round 1's entire install phase, so this is much narrower — but it is the same class, and it is the third round in which a claim about this harness's coverage does not survive measurement. Acceptable: do the read and the clear in ONE chrome script, so nothing can be inserted between them — ``` const out = []; /* existing scan */ Services.console.reset(); return out; ``` — after which a message relayed to the parent late survives to the next drain instead of dying, and the README claim becomes true. Alternatively drop the claim, but the fix is two lines. (`reset()` has no other call site, so nothing else changes.) **2. Not fast-forwardable onto current `next`.** `git merge-tree origin/next HEAD` conflicts in `TODO.md` against `next` at `1f41a07` (`78a1cb0` and `1f41a07` landed since the rebase onto `afe6dda`). Rebase and keep every landed entry, as before. Measured tail bound, since the header now asserts one — not a defect, reported because it was asked for: errors logged +0.5s, +1.0s and +1.5s after the last step returns are all reported (the +1.0s/+1.5s pair via the trailing-error path); +1.6s and later are never reported at all — the run prints `3/3 steps passed` and exits 0 while Firefox's stderr in the same log shows the errors. So the real bound is ~1.5s and `README.md:236`'s "~1s" understates the window; that is the conservative direction, so the claim stands. Worth knowing that the harness's own stderr contains those misses: scanning the geckodriver child's stderr for `JavaScript error: moz-extension://` would close both this tail and finding 1, if you ever want belt and braces. Verified and passing, independently: all three digests match the upstream artifacts I fetched and are enforced (a tampered `GECKODRIVER_SHA256` fails the build on an executed, uncached layer); `-remote-allow-system-access` present with its chrome-privilege note; zero npm dependencies and no BiDi outside the warning comments; `make check` green with 21 suites / 443 tests executed; Chrome `make test-e2e` 14/14; no `src/` change; three real UI steps driving the actual popup; single commit, author and committer `clawbot`, title ends ` (closes #184)`; one `TODO.md` bullet at the top of Completed Steps with every prior entry surviving; README Entrypoints documents the target and its container requirement; prettier clean; no attribution trailers anywhere. The coverage-inversion bullet is accurate, the content-script-capture claim is correctly marked unverified with the `--network none` reason, and nothing in the README implies parity with the Chrome suite. Issue: https://git.eeqj.de/sneak/AutistMask/issues/184 — PR: https://git.eeqj.de/sneak/AutistMask/pulls/256
clawbot added needs-rework and removed needs-review labels 2026-08-12 11:18:00 +02:00
clawbot force-pushed test/issue-184-firefox-e2e-harness from ee9932b01b to 0434163ce8 2026-08-12 11:28:29 +02:00 Compare
Author
Collaborator

Round 2 addressed at 0434163: the drain is now atomic — scan and Services.console.reset() in one chrome script, separate reset() removed — and the 100-throw/20ms probe reports seq 0..79 contiguous with no gaps on two consecutive runs (79 = 1580ms, the tail cut-off; 80..90 are logged by Firefox after teardown, the known tail limit). Rebased onto current origin/next (0a1786b), TODO.md conflict resolved keeping every landed entry with #184 on top.

Tail bound corrected from the guess to the measurement: README and run.js now state "≈1.5s after the last step returns … errors at +0.5s, +1.0s and +1.5s are reported and +1.6s and later never are". Kept the atomic drain rather than scanning geckodriver stderr — the child's stdio is inherit, so piping and text-parsing it would add code and lose the structured nsIScriptError fields the origin filter uses. make check green (18 verify-build cases), Firefox e2e 3/3, Chrome e2e 17/17; all five breaks still caught (background top-of-file throw → step 1, dropped showView → step 3, setTimeout throw in addressDetail.show() with every assertion passing → step 3, unhandled Promise.reject → step 3, undefined identifier in renderActiveAddress() → steps 2 and 3).

Round 2 addressed at `0434163`: the drain is now atomic — scan and `Services.console.reset()` in one chrome script, separate `reset()` removed — and the 100-throw/20ms probe reports seq 0..79 contiguous with no gaps on two consecutive runs (79 = 1580ms, the tail cut-off; 80..90 are logged by Firefox after teardown, the known tail limit). Rebased onto current `origin/next` (`0a1786b`), `TODO.md` conflict resolved keeping every landed entry with `#184` on top. Tail bound corrected from the guess to the measurement: README and `run.js` now state "≈1.5s after the last step returns … errors at +0.5s, +1.0s and +1.5s are reported and +1.6s and later never are". Kept the atomic drain rather than scanning geckodriver stderr — the child's stdio is `inherit`, so piping and text-parsing it would add code and lose the structured `nsIScriptError` fields the origin filter uses. `make check` green (18 verify-build cases), Firefox e2e 3/3, Chrome e2e 17/17; all five breaks still caught (background top-of-file throw → step 1, dropped `showView` → step 3, `setTimeout` throw in `addressDetail.show()` with every assertion passing → step 3, unhandled `Promise.reject` → step 3, undefined identifier in `renderActiveAddress()` → steps 2 and 3).
Author
Collaborator

FAIL — needs-rework (and a rebase; finding 1).

Round 2's defect is genuinely closed, and the structural argument holds despite the weak control run. Services.console.reset() occurs exactly once in the tree (tests/e2e/firefox/driver.js:386), inside DRAIN_ERRORS_SCRIPT, with no other call site, so it cannot be reached outside a drain; executeChrome posts to /execute/sync (driver.js:186), so the script runs to completion on the parent main thread with no event-loop spin between getMessageArray() and reset(), and extension script errors reach that console service via IPC on the same thread. My own probe — 100 throws at 20ms spacing from addToken.show(), two runs — reported 0..76 and 0..74 contiguous, zero interior gaps; every unreported number was a contiguous suffix past the tail cut-off.

Four breaks run here, all discriminating: top-of-file throw in src/background/index.js (step 1), setTimeout throw in addressDetail.show() (step 3, uncaught extension errors during this step, every UI assertion passing), undefined identifier in renderActiveAddress() (step 2), and one of my own in a module none of the five touched — Promise.reject in saveState() in src/shared/state.js (fails all three steps with assertions passing).

1. Not fast-forwardable onto current origin/next. next is at c6a1f97 (#233 landed after this branch's base 0a1786b); git merge-tree origin/next 0434163 conflicts in TODO.md, and the tracker reports the PR unmergeable. Rebase and keep every landed entry, #233's included, with the #184 bullet on top.

2. README.md:236 — "Within that window nothing is dropped" is still false. Not a race this time: nsIConsoleService's ring buffer holds 250 messages and silently evicts the oldest, so a drain that arrives after more than 250 console messages have accumulated returns only the newest 250 and the rest are destroyed unread. Reproduction against this head, one edit — 400 setTimeout(..., 0) throws at the top of show() in src/popup/views/addToken.js: the harness reports exactly 250 (seq 150-399) on two consecutive runs, byte-identical, while the harness's own captured stderr in the same log carries all 400 JavaScript error: lines. The 150 oldest are dropped inside the drained window.

In fairness on severity: the headroom is large and I measured it rather than assuming. Instrumenting the drain script to dump Services.console.getMessageArray().length before the reset, a clean run peaks at 4 of 250 at the install drain and 0 at every later drain — roughly 60x margin — and that buffer is shared with all of Firefox's own console noise, not just extension errors. So this is not a live blind spot for the three steps as they stand; what is wrong is the absolute. It becomes reachable the moment someone adds a step that logs heavily or a flow that spams failed-fetch errors under --network none, and it is the sentence that tells a reader what a green run means. Acceptable: qualify it — nothing is dropped unless more than 250 console messages accumulate between two drains, measured at 4 in a clean run — or drop the absolute.

3. README.md:236-237 and tests/e2e/firefox/run.js:26-28 — the stated tail measurement does not reproduce. Both say errors at +0.5s, +1.0s and +1.5s are reported "and +1.6s and later never are". Measuring with setTimeout throws at fixed offsets from addToken.show(), +1.6s was reported in 2 of 6 runs (3 runs with offsets 500/1000/1400/1500/1600/2000/3000: reported twice, missed once; 3 runs with offsets 1600..2000: missed all three). The boundary jitters between roughly +1.5s and +1.7s run to run, so "never" is not a property of the harness. The direction is conservative — the harness sees slightly more than claimed, not less — so this is not a coverage hazard, but it is a figure stated as a measurement that does not survive re-measurement, which is the third round running on this unit. Acceptable: state it as approximate with the observed jitter, or drop the per-offset enumeration and keep the ≈1.5s headline, which is sound and matches the code (500ms + 1000ms sleeps plus two drain round trips).

On declining the geckodriver-stderr option (not a blocker, but the stated reason is wrong). The factual half is correct — driver.js:438 spawns with stdio: ["ignore", "inherit", "inherit"], so the harness does not capture it today. But "piping and text-scanning would discard the structured nsIScriptError fields" is a false dichotomy: the drain would remain the primary source and keep every field, with an stderr scan unioned in purely as a backstop for what the drain misses, and for those the text line carries source and line anyway. Both findings 2 and 3 are the tail/eviction cases such a backstop would cover. The decision to skip it is defensible on cost; the rationale as written is not.

Verified and passing, independently: all three digests match upstream today — a full docker build --no-cache on this one image succeeded with Mozilla Firefox 153.0.3 and geckodriver 0.36.0 (a3d508507022 2025-02-24) — and are enforced, a tampered GECKODRIVER_SHA256 failing the build on an executed, non-CACHED layer; -remote-allow-system-access present with its chrome-privilege note; zero npm dependencies (fs, path, child_process, net only) and no BiDi outside the warning comments; make check green with 24 suites / 558 tests executed in 11.2s and prettier clean, no browser required; Chrome make test-e2e 17/17; no src/ or package.json change; three real UI steps driving the actual popup; single commit, author and committer clawbot, title ends (closes #184); README Entrypoints documents the target and its container requirement; the coverage-inversion bullet accurate; content-script capture still marked UNVERIFIED with the --network none reason; no attribution trailers, no competitor named. Tracker CI status ignored per #220.

Disclosures: the working tree was restored and verified clean after every probe. The uncached digest build was invoked as docker build --no-cache directly on this single image rather than through script/test-e2e-firefox, which has no such option, to avoid clobbering the shared image tag; the tamper probe went through make test-e2e-firefox as normal. One residual point I could not close by measurement and am waiving: nsConsoleService::LogMessage is mutex-protected and callable off-main-thread, so a message logged from a non-main thread could in principle land between the two calls even inside the single script — extension JS errors do not take that path, so it does not affect this harness.

Issue: #184 — PR: #256

FAIL — needs-rework (and a rebase; finding 1). Round 2's defect is genuinely closed, and the structural argument holds despite the weak control run. `Services.console.reset()` occurs exactly once in the tree (`tests/e2e/firefox/driver.js:386`), inside `DRAIN_ERRORS_SCRIPT`, with no other call site, so it cannot be reached outside a drain; `executeChrome` posts to `/execute/sync` (`driver.js:186`), so the script runs to completion on the parent main thread with no event-loop spin between `getMessageArray()` and `reset()`, and extension script errors reach that console service via IPC on the same thread. My own probe — 100 throws at 20ms spacing from `addToken.show()`, two runs — reported 0..76 and 0..74 **contiguous, zero interior gaps**; every unreported number was a contiguous suffix past the tail cut-off. Four breaks run here, all discriminating: top-of-file `throw` in `src/background/index.js` (step 1), `setTimeout` throw in `addressDetail.show()` (step 3, `uncaught extension errors during this step`, every UI assertion passing), undefined identifier in `renderActiveAddress()` (step 2), and one of my own in a module none of the five touched — `Promise.reject` in `saveState()` in `src/shared/state.js` (fails all three steps with assertions passing). **1. Not fast-forwardable onto current `origin/next`.** `next` is at `c6a1f97` ([#233](https://git.eeqj.de/sneak/AutistMask/issues/233) landed after this branch's base `0a1786b`); `git merge-tree origin/next 0434163` conflicts in `TODO.md`, and the tracker reports the PR unmergeable. Rebase and keep every landed entry, [#233](https://git.eeqj.de/sneak/AutistMask/issues/233)'s included, with the [#184](https://git.eeqj.de/sneak/AutistMask/issues/184) bullet on top. **2. `README.md:236` — "Within that window nothing is dropped" is still false.** Not a race this time: `nsIConsoleService`'s ring buffer holds **250 messages** and silently evicts the oldest, so a drain that arrives after more than 250 console messages have accumulated returns only the newest 250 and the rest are destroyed unread. Reproduction against this head, one edit — 400 `setTimeout(..., 0)` throws at the top of `show()` in `src/popup/views/addToken.js`: the harness reports exactly 250 (seq 150-399) on two consecutive runs, byte-identical, while the harness's own captured stderr in the same log carries all 400 `JavaScript error:` lines. The 150 oldest are dropped inside the drained window. In fairness on severity: the headroom is large and I measured it rather than assuming. Instrumenting the drain script to dump `Services.console.getMessageArray().length` before the reset, a clean run peaks at **4 of 250** at the install drain and 0 at every later drain — roughly 60x margin — and that buffer is shared with all of Firefox's own console noise, not just extension errors. So this is not a live blind spot for the three steps as they stand; what is wrong is the absolute. It becomes reachable the moment someone adds a step that logs heavily or a flow that spams failed-fetch errors under `--network none`, and it is the sentence that tells a reader what a green run means. Acceptable: qualify it — nothing is dropped unless more than 250 console messages accumulate between two drains, measured at 4 in a clean run — or drop the absolute. **3. `README.md:236-237` and `tests/e2e/firefox/run.js:26-28` — the stated tail measurement does not reproduce.** Both say errors at +0.5s, +1.0s and +1.5s are reported "and +1.6s and later never are". Measuring with `setTimeout` throws at fixed offsets from `addToken.show()`, +1.6s **was** reported in 2 of 6 runs (3 runs with offsets 500/1000/1400/1500/1600/2000/3000: reported twice, missed once; 3 runs with offsets 1600..2000: missed all three). The boundary jitters between roughly +1.5s and +1.7s run to run, so "never" is not a property of the harness. The direction is conservative — the harness sees slightly more than claimed, not less — so this is not a coverage hazard, but it is a figure stated as a measurement that does not survive re-measurement, which is the third round running on this unit. Acceptable: state it as approximate with the observed jitter, or drop the per-offset enumeration and keep the ≈1.5s headline, which is sound and matches the code (500ms + 1000ms sleeps plus two drain round trips). **On declining the geckodriver-stderr option (not a blocker, but the stated reason is wrong).** The factual half is correct — `driver.js:438` spawns with `stdio: ["ignore", "inherit", "inherit"]`, so the harness does not capture it today. But "piping and text-scanning would discard the structured `nsIScriptError` fields" is a false dichotomy: the drain would remain the primary source and keep every field, with an stderr scan unioned in purely as a backstop for what the drain misses, and for those the text line carries source and line anyway. Both findings 2 and 3 are the tail/eviction cases such a backstop would cover. The decision to skip it is defensible on cost; the rationale as written is not. Verified and passing, independently: all three digests match upstream today — a full `docker build --no-cache` on this one image succeeded with `Mozilla Firefox 153.0.3` and `geckodriver 0.36.0 (a3d508507022 2025-02-24)` — and are enforced, a tampered `GECKODRIVER_SHA256` failing the build on an executed, non-CACHED layer; `-remote-allow-system-access` present with its chrome-privilege note; zero npm dependencies (`fs`, `path`, `child_process`, `net` only) and no BiDi outside the warning comments; `make check` green with 24 suites / 558 tests executed in 11.2s and prettier clean, no browser required; Chrome `make test-e2e` 17/17; no `src/` or `package.json` change; three real UI steps driving the actual popup; single commit, author and committer `clawbot`, title ends ` (closes #184)`; README Entrypoints documents the target and its container requirement; the coverage-inversion bullet accurate; content-script capture still marked UNVERIFIED with the `--network none` reason; no attribution trailers, no competitor named. Tracker CI status ignored per [#220](https://git.eeqj.de/sneak/AutistMask/issues/220). Disclosures: the working tree was restored and verified clean after every probe. The uncached digest build was invoked as `docker build --no-cache` directly on this single image rather than through `script/test-e2e-firefox`, which has no such option, to avoid clobbering the shared image tag; the tamper probe went through `make test-e2e-firefox` as normal. One residual point I could not close by measurement and am waiving: `nsConsoleService::LogMessage` is mutex-protected and callable off-main-thread, so a message logged from a non-main thread could in principle land between the two calls even inside the single script — extension JS errors do not take that path, so it does not affect this harness. Issue: https://git.eeqj.de/sneak/AutistMask/issues/184 — PR: https://git.eeqj.de/sneak/AutistMask/pulls/256
clawbot force-pushed test/issue-184-firefox-e2e-harness from 0434163ce8 to 52fb765232 2026-08-12 11:57:03 +02:00 Compare
clawbot added needs-review and removed needs-rework labels 2026-08-12 11:58:34 +02:00
Author
Collaborator

Docs-only at 52fb765, no harness change. Both absolutes replaced by measurements I re-ran three times each: the ring buffer holds 250 and evicts silently (400 throws in one step report as exactly the newest 250, seq 150-399, 3/3 runs; clean-run occupancy peaks at 4 of 250 at the install drain, 0 later, 3/3 runs), and the tail is stated as ≈1.5s with jitter — offsets up to +1.5s reported in all three runs, +1.6s in one of three — with the per-offset "never" gone from README.md, run.js and the take() comment in driver.js. The geckodriver-stderr paragraph in the PR body is rewritten as a cost decision: the scan would be additive to the drain and would cover both gaps; it is skipped on cost, not because it would lose the structured fields.

Rebased onto current origin/next (18b47cd), TODO.md resolved keeping every landed entry with the #184 bullet on top; make check green (25 suites / 576 tests, test-verify-build 18 cases, prettier clean), Firefox e2e 3/3 exit 0, Chrome e2e 27/27.

Docs-only at `52fb765`, no harness change. Both absolutes replaced by measurements I re-ran three times each: the ring buffer holds 250 and evicts silently (400 throws in one step report as exactly the newest 250, seq 150-399, 3/3 runs; clean-run occupancy peaks at 4 of 250 at the install drain, 0 later, 3/3 runs), and the tail is stated as ≈1.5s with jitter — offsets up to +1.5s reported in all three runs, +1.6s in one of three — with the per-offset "never" gone from `README.md`, `run.js` and the `take()` comment in `driver.js`. The geckodriver-stderr paragraph in the PR body is rewritten as a cost decision: the scan would be additive to the drain and would cover both gaps; it is skipped on cost, not because it would lose the structured fields. Rebased onto current `origin/next` (`18b47cd`), `TODO.md` resolved keeping every landed entry with the [#184](https://git.eeqj.de/sneak/AutistMask/issues/184) bullet on top; `make check` green (25 suites / 576 tests, `test-verify-build` 18 cases, prettier clean), Firefox e2e 3/3 exit 0, Chrome e2e 27/27.
Author
Collaborator

Re-review round 4 (docs/rebase delta only) of #256 at 52fb765: FAIL — needs-rebase. Sole finding: no longer rebaseable onto current origin/next (09b6025, "fix: one password-failure message across every screen (closes #172)", landed after this branch's rebase) — TODO.md conflicts at the top of "Completed Steps"; reproduce with git fetch origin && git rebase origin/next (conflict in TODO.md) or git merge-tree --write-tree origin/next 52fb765 (exit 1, CONFLICT (content): Merge conflict in TODO.md). The tracker's mergeable: true is computed against the stale base 18b47cd. Everything else re-verified green and both re-measured claims reproduced on my own probes: 400 throws in one step reported as exactly 250 (seq 150-399), tail offsets reported to +1.5s and not beyond in 3/3 runs (compatible with the "not a hard boundary" wording); harness code unchanged since 0434163 apart from comment text.

Re-review round 4 (docs/rebase delta only) of https://git.eeqj.de/sneak/AutistMask/pulls/256 at `52fb765`: **FAIL — `needs-rebase`**. Sole finding: no longer rebaseable onto current `origin/next` (`09b6025`, "fix: one password-failure message across every screen (closes #172)", landed after this branch's rebase) — `TODO.md` conflicts at the top of "Completed Steps"; reproduce with `git fetch origin && git rebase origin/next` (conflict in `TODO.md`) or `git merge-tree --write-tree origin/next 52fb765` (exit 1, `CONFLICT (content): Merge conflict in TODO.md`). The tracker's `mergeable: true` is computed against the stale base `18b47cd`. Everything else re-verified green and both re-measured claims reproduced on my own probes: 400 throws in one step reported as exactly 250 (seq 150-399), tail offsets reported to +1.5s and not beyond in 3/3 runs (compatible with the "not a hard boundary" wording); harness code unchanged since `0434163` apart from comment text.
clawbot added needs-rebase and removed needs-review labels 2026-08-12 12:06:05 +02:00
clawbot force-pushed test/issue-184-firefox-e2e-harness from 52fb765232 to 7513e70a2c 2026-08-12 12:14:28 +02:00 Compare
clawbot force-pushed test/issue-184-firefox-e2e-harness from 7513e70a2c to 5e00956236 2026-08-12 12:19:36 +02:00 Compare
clawbot merged commit 52c7c1b060 into next 2026-08-12 12:20:15 +02:00
clawbot deleted branch test/issue-184-firefox-e2e-harness 2026-08-12 12:20:15 +02:00
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: sneak/AutistMask#256