Run e2e tests in a Docker container

This commit is contained in:
Georgios Jason Efstathiou
2026-05-29 13:35:13 +02:00
parent 17cd7adcb8
commit a304fa471b
6 changed files with 243 additions and 0 deletions
+13
View File
@@ -0,0 +1,13 @@
node_modules
build
.svelte-kit
.git
tests/tmp
tests/artifacts
tests/visual/snapshots
data.ms
dumps
radicle-httpd/target
radicle-search/target
target
.wrangler
+32
View File
@@ -57,6 +57,38 @@ This will:
1. Compile radicle-httpd from the `radicle-httpd/` directory 1. Compile radicle-httpd from the `radicle-httpd/` directory
2. Run the full test suite against the locally compiled binary 2. Run the full test suite against the locally compiled binary
**Running e2e tests in Docker:**
If your host OS isn't supported by Playwright's browser builds (e.g. a very
new or unusual Linux distribution), run the suite inside a container instead.
Requires Docker with the Compose plugin.
scripts/test-e2e-docker
Any arguments are forwarded to `npm run test:e2e` (defaulting to
`--project chromium` when none are given):
scripts/test-e2e-docker tests/e2e/repo.spec.ts
scripts/test-e2e-docker --project chromium -g "some test name"
`SKIP_SETUP=true` is forwarded the same as for the native runner:
SKIP_SETUP=true scripts/test-e2e-docker
To test against a radicle-httpd you compile from source (the equivalent of
`npm run test:e2e:local`), pass `--local`:
scripts/test-e2e-docker --local
Notes:
* The first image build is slow (it downloads Chromium); subsequent runs are
cached. `--local` additionally installs a Rust toolchain and compiles
radicle-httpd on first use.
* The repo is bind-mounted, so test artifacts and the binary/fixture cache
land on the host as usual. `node_modules`, the cargo cache, and httpd's
build directory live in container-private volumes, leaving your host copies
untouched.
Proposing changes Proposing changes
----------------- -----------------
When proposing changes via a patch: When proposing changes via a patch:
+53
View File
@@ -0,0 +1,53 @@
# Runs the Playwright e2e suite in a Playwright-supported Linux distro.
# Useful when the host OS is too new/old for Playwright's browser builds.
#
# Two stages:
# base - Node + Chromium; runs the suite against the released radicle-httpd.
# local - base + a Rust toolchain; additionally compiles radicle-httpd from
# source for `--local` runs (needed when testing backend changes).
#
# Chromium and its OS dependencies are baked in at the project's pinned
# Playwright version. node_modules and the repo are provided at runtime via
# bind mount + named volume (see docker-compose.e2e.yml).
FROM node:24-bookworm AS base
ENV PLAYWRIGHT_BROWSERS_PATH=/ms-playwright \
DEBIAN_FRONTEND=noninteractive
# xz-utils: install-binaries unpacks .tar.xz release archives.
# git/curl/ca-certificates: fetching binaries and git operations in fixtures.
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
xz-utils ca-certificates git curl \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /work
# Install Chromium plus its apt-level runtime dependencies, pinned to the
# Playwright version in package-lock.json so the baked browser matches the
# @playwright/test that npm ci installs into the volume at runtime. Copying
# only the lockfiles keeps this layer cached across source changes.
# --ignore-scripts: the repo's postinstall (copy-katex/twemoji assets) needs
# scripts/ which isn't in this layer; those run at runtime instead.
COPY package.json package-lock.json ./
RUN npm ci --ignore-scripts \
&& npx playwright install --with-deps chromium \
&& rm -rf node_modules
ENTRYPOINT ["/work/scripts/docker-e2e-entrypoint.sh"]
# --- local stage: adds Rust so the entrypoint can build radicle-httpd ---
FROM base AS local
# Toolchain + build deps for compiling radicle-httpd (debug build).
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
build-essential pkg-config libssl-dev \
&& rm -rf /var/lib/apt/lists/*
ENV RUSTUP_HOME=/usr/local/rustup \
CARGO_HOME=/usr/local/cargo \
PATH=/usr/local/cargo/bin:$PATH
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \
| sh -s -- -y --no-modify-path --profile minimal --default-toolchain stable \
&& rustc --version
+43
View File
@@ -0,0 +1,43 @@
x-e2e-common: &e2e-common
init: true
working_dir: /work
environment:
PLAYWRIGHT_BROWSERS_PATH: /ms-playwright
services:
# Runs the suite against the released radicle-httpd binary.
e2e:
<<: *e2e-common
build:
context: .
dockerfile: Dockerfile.e2e
target: base
volumes:
# Repo bind-mounted so code edits, test artifacts, and the
# binary/fixture cache (tests/tmp) are shared with the host.
- .:/work
# Container-private node_modules: keeps the host's untouched and
# avoids a glibc mismatch with host-built native modules.
- e2e-node-modules:/work/node_modules
# Compiles radicle-httpd from source and runs the suite against it.
# Used via `scripts/test-e2e-docker --local` when testing backend changes.
e2e-local:
<<: *e2e-common
build:
context: .
dockerfile: Dockerfile.e2e
target: local
volumes:
- .:/work
- e2e-node-modules:/work/node_modules
# Container-private cargo crate cache and httpd build dir, so the
# Rust build is incremental across runs and never mixes with the
# host's (different glibc/rustc) target artifacts.
- e2e-cargo-registry:/usr/local/cargo/registry
- e2e-httpd-target:/work/radicle-httpd/target
volumes:
e2e-node-modules:
e2e-cargo-registry:
e2e-httpd-target:
+51
View File
@@ -0,0 +1,51 @@
#!/usr/bin/env bash
# Entrypoint for the e2e Docker container. Installs npm deps into the
# container-private node_modules volume (only when package-lock.json changes),
# ensures the Radicle test binaries are present, then runs Playwright. Any
# arguments are forwarded to the test command.
#
# When USE_LOCAL_HTTPD=true, runs test:e2e:local (compiles radicle-httpd from
# source and tests the frontend against it) instead of test:e2e.
set -euo pipefail
cd /work
# The container runs as root, so anything it writes into the bind-mounted repo
# would otherwise be root-owned on the host. Hand those paths back to the
# invoking user (HOST_UID/HOST_GID set by scripts/test-e2e-docker) on exit.
# radicle-httpd/target lives in a named volume, so it's not host-visible.
cleanup() {
if [ -n "${HOST_UID:-}" ]; then
chown -R "$HOST_UID:${HOST_GID:-$HOST_UID}" \
tests/tmp tests/artifacts tests/visual/snapshots build .svelte-kit \
2>/dev/null || true
fi
}
trap cleanup EXIT
LOCK_HASH=$(md5sum package-lock.json | cut -d' ' -f1)
STAMP=node_modules/.lockhash
if [ ! -f "$STAMP" ] || [ "$(cat "$STAMP")" != "$LOCK_HASH" ]; then
echo "📦 Installing npm dependencies..."
npm ci
echo "$LOCK_HASH" >"$STAMP"
else
echo "📦 npm dependencies up to date."
fi
echo "⬇️ Ensuring Radicle test binaries..."
mkdir -p tests/artifacts
./scripts/install-binaries
NPM_SCRIPT="test:e2e"
if [ "${USE_LOCAL_HTTPD:-}" = "true" ]; then
NPM_SCRIPT="test:e2e:local"
echo "🦀 Local httpd mode: radicle-httpd will be compiled from source."
fi
echo "🧪 Running e2e tests ($NPM_SCRIPT)..."
set +e
npm run "$NPM_SCRIPT" -- "$@"
status=$?
set -e
exit "$status"
+51
View File
@@ -0,0 +1,51 @@
#!/usr/bin/env bash
# Build and run the Playwright e2e suite inside a Docker container. Use this
# when the host OS is not supported by Playwright's browser builds.
#
# Usage:
# scripts/test-e2e-docker # all chromium e2e tests
# scripts/test-e2e-docker tests/e2e/foo.spec.ts # a single spec
# scripts/test-e2e-docker --project chromium -g "some test"
# scripts/test-e2e-docker --local # build radicle-httpd from
# # source and test against it
#
# Any non-flag arguments are forwarded to the test command. When none are
# given, defaults to `--project chromium`.
set -euo pipefail
REPO_ROOT=$(cd "$(dirname "$0")/.." && pwd)
cd "$REPO_ROOT"
COMPOSE="docker compose -f docker-compose.e2e.yml"
# Strip our own --local flag; forward everything else to Playwright.
LOCAL=false
ARGS=()
for arg in "$@"; do
if [ "$arg" = "--local" ]; then
LOCAL=true
else
ARGS+=("$arg")
fi
done
SERVICE=e2e
EXTRA_ENV=()
if $LOCAL; then
SERVICE=e2e-local
EXTRA_ENV=(-e USE_LOCAL_HTTPD=true)
fi
$COMPOSE build "$SERVICE"
if [ "${#ARGS[@]}" -eq 0 ]; then
ARGS=(--project chromium)
fi
# SKIP_SETUP (no value) forwards the host's value through, mirroring the
# native runner's fast-iteration flag.
exec $COMPOSE run --rm \
-e HOST_UID="$(id -u)" -e HOST_GID="$(id -g)" \
-e SKIP_SETUP \
"${EXTRA_ENV[@]}" \
"$SERVICE" "${ARGS[@]}"