From eb6ec80430b7c75efe8e7775ccf3b171aee56046 Mon Sep 17 00:00:00 2001 From: clawbot Date: Wed, 12 Aug 2026 11:46:00 +0000 Subject: [PATCH] wip: e2e in ci --- .gitea/workflows/e2e.yml | 43 ++++++++++++++++++++++++++ .gitea/workflows/probe.yml | 27 ---------------- script/e2e-container | 63 ++++++++++++++++++++++++++++++++++++++ script/test-e2e | 15 ++++++--- script/test-e2e-firefox | 14 ++++++--- 5 files changed, 126 insertions(+), 36 deletions(-) create mode 100644 .gitea/workflows/e2e.yml delete mode 100644 .gitea/workflows/probe.yml create mode 100755 script/e2e-container diff --git a/.gitea/workflows/e2e.yml b/.gitea/workflows/e2e.yml new file mode 100644 index 0000000..270fdbc --- /dev/null +++ b/.gitea/workflows/e2e.yml @@ -0,0 +1,43 @@ +name: e2e +on: [push] + +# The browser end-to-end suites, one job per browser, deliberately not part +# of the check workflow. REPO_POLICIES.md caps make test at 20 seconds and +# script/cibuild is a plain `docker build .` whose Dockerfile runs +# make check; folding a browser suite into either would break that cap and +# slow the local fast path. These jobs are the automatic run the suites +# never had: before this workflow, every browser-level guarantee in the +# repo held only when a human remembered to run it by hand. +# +# One job per browser rather than two steps in one job, so a Chrome failure +# does not hide the Firefox result. +# +# These jobs REPORT, they do not gate: nothing merges or refuses to merge on +# their result by itself. Gitea decides that in branch protection, which +# this repo does not set, so a failure here is a red mark the reviewer must +# account for rather than a hard block. Making e2e a required check is the +# right end state and is deliberately not done yet, because the Chrome +# suite is flaky today under load (see the tracker) and a gate that fails +# at random teaches people to merge past red. +# +# Nothing here is allowed to pass vacuously. There is no continue-on-error +# and no `|| true`: script/test-e2e and script/test-e2e-firefox both exit +# non-zero when docker is missing, when the extension build is absent, and +# when the browser fails to start, and the Chrome harness aborts the whole +# suite if its network interception is not in effect. +jobs: + e2e-chrome: + runs-on: ubuntu-latest + steps: + # actions/checkout v4.2.2, 2026-02-22 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - run: script/bootstrap + - run: make test-e2e + + e2e-firefox: + runs-on: ubuntu-latest + steps: + # actions/checkout v4.2.2, 2026-02-22 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - run: script/bootstrap + - run: make test-e2e-firefox diff --git a/.gitea/workflows/probe.yml b/.gitea/workflows/probe.yml deleted file mode 100644 index 83d7e04..0000000 --- a/.gitea/workflows/probe.yml +++ /dev/null @@ -1,27 +0,0 @@ -name: probe -on: [push] -jobs: - probe: - runs-on: ubuntu-latest - steps: - # actions/checkout v4.2.2, 2026-02-22 - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - - run: | - set -x - id - pwd - cat /etc/hostname - docker version || true - docker info --format '{{.Driver}} {{.ServerVersion}}' || true - echo "--- mountinfo ---" - cat /proc/self/mountinfo - echo "--- inspect self by hostname ---" - docker inspect "$(cat /etc/hostname)" --format '{{json .Mounts}}' || true - echo "--- bind mount probe ---" - docker run --rm -v "$PWD:/work" -w /work alpine:3 ls -la /work | head -20 || true - echo "--- named volume probe ---" - VOL=$(docker inspect "$(cat /etc/hostname)" --format '{{range .Mounts}}{{if eq .Destination "/workspace"}}{{.Name}}{{end}}{{end}}' || true) - echo "VOL=$VOL" - docker run --rm -v "$VOL:/workspace" -w "$PWD" alpine:3 ls -la . | head -20 || true - echo "--- shm in docker run default ---" - docker run --rm alpine:3 df -h /dev/shm || true diff --git a/script/e2e-container b/script/e2e-container new file mode 100755 index 0000000..9a33830 --- /dev/null +++ b/script/e2e-container @@ -0,0 +1,63 @@ +#!/bin/sh +# script/e2e-container: run one command in a container with this repo at +# /work, and exit with that command's status. Our own extension to +# scripts-to-rule-them-all, used by script/test-e2e and +# script/test-e2e-firefox. +# +# It takes the arguments you would give `docker run`, minus the mount and +# the working directory: +# +# script/e2e-container --ipc=host -e HOME=/tmp "$IMAGE" node foo.js +# +# It exists because `docker run -v "$ROOT:/work"` cannot work under Gitea +# Actions. The runner executes the job inside a container and hands it the +# host's docker socket, so the source side of a -v is resolved by the host +# daemon and not inside the job. The job's checkout lives on a docker +# volume mounted at /workspace, which is not a host path at all: measured +# on this repo's runner, `docker run -v "$PWD:/work" ... ls -la /work` +# listed an empty directory. A suite started that way dies with "Cannot +# find module" rather than testing anything. +# +# So the repo is copied in rather than mounted. That is one mechanism for +# CI and for a laptop instead of two, which matters more than the copy: +# the path CI takes is the path a developer exercises on every local run. +# The copy costs about two seconds for this tree, against a suite that +# takes tens of seconds. +# +# Nothing is copied back out. Neither suite writes anything under the repo +# — the browser profile and every temp file live under HOME, which both +# callers point at /tmp inside the container. +set -eu + +ROOT="$(cd "$(dirname "$0")/.." && pwd -P)" + +CID="" + +cleanup() { + if [ -n "$CID" ]; then + docker rm -f "$CID" >/dev/null 2>&1 || true + fi +} + +main() { + if [ "$#" -eq 0 ]; then + echo "e2e-container: usage: e2e-container ..." >&2 + exit 2 + fi + + cd "$ROOT" + + CID="$(docker create -w /work "$@")" + trap cleanup EXIT + trap 'cleanup; exit 130' INT TERM + + # `docker cp :/work` with /work absent creates it and + # copies the contents of into it. A failure here is fatal under + # set -e, so the command below can never run against an empty /work. + docker cp . "$CID:/work" + + # --attach propagates the container's exit status. + docker start --attach "$CID" +} + +main "$@" diff --git a/script/test-e2e b/script/test-e2e index ece9a5d..22716d2 100755 --- a/script/test-e2e +++ b/script/test-e2e @@ -6,10 +6,13 @@ # Deliberately NOT called by script/check or script/test: REPO_POLICIES.md # caps make test at 20 seconds and a browser suite does not fit. Run it # yourself before touching popup views; it is the only check that can see -# a used-but-not-imported identifier blow up at runtime. +# a used-but-not-imported identifier blow up at runtime. The e2e workflow +# in .gitea/workflows/e2e.yml runs it on every push, as a job separate +# from check so the 20-second cap stays intact. set -eu -ROOT="$(cd "$(dirname "$0")/.." && pwd -P)" +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd -P)" +ROOT="$(cd "$SCRIPT_DIR/.." && pwd -P)" # mcr.microsoft.com/playwright:v1.56.0-noble, 2026-08-09 # @@ -49,14 +52,16 @@ main() { # claim in tests/e2e/network.js and README.md — not deleting the # probe. The image is pinned by digest, so this can only ever bite # on a deliberate bump. - docker run --rm \ + # + # script/e2e-container is `docker run` with the repo placed at /work; + # it copies rather than mounts, because a bind mount does not resolve + # under Gitea Actions. See the comment at the top of that script. + "$SCRIPT_DIR/e2e-container" \ --ipc=host \ --user "$(id -u):$(id -g)" \ -e HOME=/tmp \ -e PW_EXPERIMENTAL_SERVICE_WORKER_NETWORK_EVENTS=1 \ -e "E2E_TRACE_NETWORK=${E2E_TRACE_NETWORK:-0}" \ - -v "$ROOT:/work" \ - -w /work \ "$IMAGE" \ node tests/e2e/run.js } diff --git a/script/test-e2e-firefox b/script/test-e2e-firefox index ebe72d6..7ae4893 100755 --- a/script/test-e2e-firefox +++ b/script/test-e2e-firefox @@ -5,7 +5,9 @@ # # 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. +# and a browser suite does not fit. The e2e workflow in +# .gitea/workflows/e2e.yml runs it on every push, as a job separate from +# check so the 20-second cap stays intact. # # Unlike script/test-e2e this builds its image locally, because no # published image carries both a pinned Firefox and a matching geckodriver. @@ -49,13 +51,17 @@ main() { # 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 \ + # + # script/e2e-container is `docker run` with the repo placed at /work; + # it copies rather than mounts, because a bind mount does not resolve + # under Gitea Actions. See the comment at the top of that script. The + # copy happens before the container starts, so --network none still + # covers everything the suite does. + "$SCRIPT_DIR/e2e-container" \ --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 }