test: containerized Firefox end-to-end harness (closes #184)
All checks were successful
check / check (push) Successful in 46s

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.
This commit is contained in:
2026-08-12 08:42:22 +00:00
parent bd4bdcafc7
commit 288c5560de
7 changed files with 911 additions and 16 deletions

63
script/test-e2e-firefox Executable file
View File

@@ -0,0 +1,63 @@
#!/bin/sh
# script/test-e2e-firefox: build the extension and drive the real popup in
# a real Firefox inside a pinned container. The Firefox counterpart to
# script/test-e2e. Our own extension to scripts-to-rule-them-all.
#
# Deliberately NOT called by script/check or script/test, for the same
# reason as the Chrome suite: REPO_POLICIES.md caps make test at 20 seconds
# and a browser suite does not fit.
#
# Unlike script/test-e2e this builds its image locally, because no
# published image carries both a pinned Firefox and a matching geckodriver.
# All three external artifacts are pinned by digest inside the Dockerfile;
# see tests/e2e/firefox/Dockerfile.
set -eu
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd -P)"
ROOT="$(cd "$SCRIPT_DIR/.." && pwd -P)"
IMAGE="$("$SCRIPT_DIR/projectname")-e2e-firefox"
main() {
cd "$ROOT"
if ! command -v docker >/dev/null 2>&1; then
echo "test-e2e-firefox: docker is required to run the e2e suite" >&2
exit 1
fi
echo "Building extension for e2e..."
yarn run build 2>&1
# The build context is tests/e2e/firefox/ and holds nothing but the
# Dockerfile: the harness itself arrives over the bind mount below, so
# editing it never invalidates an image layer.
echo "Building the pinned Firefox e2e image..."
docker build -t "$IMAGE" "$ROOT/tests/e2e/firefox"
echo "Running the Firefox e2e suite..."
# --shm-size=1g: Firefox needs more than the default 64MB /dev/shm.
# --network none: the suite stubs nothing, so this is what keeps the
# run offline and deterministic. The extension swallows its own
# fetch failures, so the popup flows work unchanged; see the
# network note in README.md. Weaker than the Chrome suite's
# fixture interception, and honestly so — it proves no request
# escaped, but it cannot report which ones were attempted.
# --user: keep files the suite touches owned by the caller, not root.
# HOME=/tmp: the mapped uid has no home directory in the image.
#
# No --privileged. Firefox's sandbox logs
# "CanCreateUserNamespace() clone() failure: EPERM" on startup here;
# it is cosmetic and headless Firefox runs fine without it.
docker run --rm \
--shm-size=1g \
--network none \
--user "$(id -u):$(id -g)" \
-e HOME=/tmp \
-v "$ROOT:/work" \
-w /work \
"$IMAGE" \
node tests/e2e/firefox/run.js dist/firefox
}
main "$@"