test: containerized Firefox end-to-end harness (geckodriver, MV2 temporary add-on) #184
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Companion to the Chrome harness in
#181. Firefox needs its own harness
because Chrome cannot verify what
#153 is about.
Feasibility was probed rather than assumed, and the verdict is that this
works today, with a negative control. Details below are measured.
What was proven
geckodriver 0.36.0driving stock Firefox 153.0.3 headless in a container.POST /session/{id}/moz/addon/installwith{path: "<unpacked dist/firefox dir>", temporary: true}installs the MV2build directly. No XPI, no signing, no
xpinstall.signatures.requiredfiddling - temporary installs bypass signature checks even on release builds.
The popup uuid is pinned, not discovered, by setting the profile pref
before launch:
This works because
manifest/firefox.jsonalready declaresbrowser_specific_settings.gecko.id, so the popup URL is deterministic.The full wallet-creation flow runs under Firefox with zero console errors.
Two traps, both verified the hard way
-remote-allow-system-accessis mandatory on Firefox 153. Without it,navigating to
moz-extension://and executing chrome-context script bothfail with
unsupported operation. Firefox 142 did not need it. Pin theFirefox version or this will rot.
WebDriver BiDi
log.entryAddeddoes not work for extension pages. Thisis the important one. Verified on FF 142 and 153 with a same-session control:
on a plain
http://page BiDi delivers uncaught errors with stack traces;on the
moz-extension://popup it delivers zero events. Firefox'sremote agent excludes extension browsing contexts from BiDi observation.
Any harness built on Playwright-BiDi or Puppeteer-BiDi will silently see
nothing and report success. That is precisely the vacuous-check failure
mode this repo has already hit twice. Do not go that route.
What works instead is the privileged console service via Marionette's chrome
context -
Services.console.getMessageArray(), filtered tosourceName.startsWith("moz-extension://")and non-warning, withServices.console.reset()between steps. Same semantics as Playwright'spageerror(uncaught only), and it captures background-page andcontent-script errors for free.
Demonstrated to fail
Against
dist/firefox/built from currentmain, driving the real UI to theAdd Token screen - exit 1:
Negative control, same harness and image against a build with the one-line
import fix applied - exit 0,
view-add-tokenreached,errors: [].Different exit codes on identical inputs modulo one line. The harness
discriminates, which is the only property that makes it worth having.
Definition of done
tests/e2e/firefox/contains the WebDriver client, parameterized onFIREFOX_BIN/GECKODRIVERwith the extension directory as an argument.script/test-e2e-firefoxentrypoint plus amake test-e2e-firefoxshim,building
dist/firefox/first. Outsidemake check, same as the Chromeharness.
tests/e2e/firefox/Dockerfile. Pin all three by digest, withhuman-readable versions in comments above each:
node@sha256:d649c27dae7ba0137b3cef5dd75baa422c08dc3d9e3fc0c23dfb172dc3cc6436(node:22-bookworm-slim)sha256:22b312280900bfb174b685ece32c7b3c6d72e7f8e53d6d30f21ac41a8dc500a2sha256:0bde38707eb0a686a20c6bd50f4adcc7d60d4f73c60eb83ee9e0db8f65823e04moz-extension://source fails therun.
Add Token screen opens.
body. A green run is not evidence.
requirement.
Implementation requirements
fetchandchild_processagainst geckodriver's HTTP API - no Selenium, noPlaywright, no
ws. Keep it that way; it suits this repo's statedminimal-dependency ethos and it is genuinely less code than a driver library.
--shm-size=1g. No--privilegedneeded; theCanCreateUserNamespace() EPERMsandbox warning is cosmetic.POST /session/{id}/url. BiDibrowsingContext.navigaterefusesmoz-extension://outright.share a driver layer - Chrome stays on Playwright, Firefox is a separate
WebDriver client of roughly 200 lines. Do not build a premature abstraction
over two backends to avoid writing UI steps twice; wait until the Firefox
suite actually has enough shared steps to justify a shim, and say so in the
PR if you think it has.
-remote-allow-system-accessgrants the driver fullchrome privileges. Acceptable for a throwaway CI container, and it should
never be pointed at anything else.
step-boundary assertions but cannot attribute an error to a precise moment
within a step. Document the limitation rather than implying parity with the
Chrome harness.
Plan, following the measured facts in the body rather than re-probing them.
Layout:
tests/e2e/firefox/driver.js(WebDriver client overfetch+child_process, no deps),tests/e2e/firefox/run.js(steps), and the pinnedtests/e2e/firefox/Dockerfile.script/test-e2e-firefoxbuildsdist/firefox/,builds the image from that Dockerfile, and runs it with
--shm-size=1g. Notwired into
script/check.Error capture is the chrome-context console service exactly as recorded —
Services.console.getMessageArray(),sourceName.startsWith("moz-extension://"),warning flag excluded,
Services.console.reset()at each step boundary. No BiDianywhere in the harness.
Two things not settled by the body that I will decide by measurement:
is Playwright's and does not port. I intend
--network noneon the containerinstead — hard offline, nothing to keep in sync — and will check whether the
resulting failed fetches produce
moz-extension://console errors of theirown. If they do I will report what they are rather than widening the filter
to hide them.
moment, so steps stay small enough that the attribution is still useful. That
limitation gets documented, not papered over.
Scope is the harness. If it surfaces
#153 I will record what it
shows and leave it unfixed.
Built and pushed as
#256; the full record is in
the PR body.
Everything measured in this issue held up exactly as written — both digests
matched the URLs I fetched,
-remote-allow-system-accesswas needed, and thepinned uuid worked first try. One detail cost time and is worth adding to the
record for whoever writes the next WebDriver client here: the W3C web element
key is
element-6066-11e4-a52e-4f735466cecf, endingcecf. Getting the lastcharacter wrong yields an element reference of
undefinedand a thoroughlymisleading
no such element: The element with the reference undefined is not known in the current browsing contextfrom geckodriver.findElement()nowchecks for the key and reports the actual response instead.
Verification.
make checkgreen (19 suites, 416 tests), re-run after the rebaseonto
next.make test-e2e-firefoxexits 0 with 3/3.The discrimination demo is the one recorded here, reproduced against
src/popup/views/addToken.jsrather thansrc/popup/index.js: droppingshowViewfrom its import puts the failure at step 3 instead of step 1, sosteps 1 and 2 still run and the flow is exercised. Output matches what is in
this issue almost verbatim,
index.js:13andcontent javascriptincluded —exit 1 with
ReferenceError: showView is not definedandcurrent view is view-address, exit 0 with the import restored.I also checked the "captures background-page errors for free" claim rather than
repeating it: a deliberate throw in
src/background/index.jsfailed step 1,even though the suite never navigates to the background page.
Two decisions I flagged in the plan, now settled by measurement. Network is
--network noneon the container, and the resulting failed fetches produce nomoz-extension://console errors, so no filter widening was needed. Errorcapture stayed poll-based and the limitation is documented in
run.jsand theREADME rather than glossed.
Nothing about
#153 surfaced, which is what
I would expect — its breakage is in the content-script relay, the approval
popups and the background
windows/tabscalls, and none of those are on thethree popup-only paths covered here. Whoever takes it will need steps that
actually drive a dApp connection; the harness will not otherwise reach it.