pediatric-ai-scribe-v3/scripts/e2e.sh
Daniel f0cc537df1
Some checks failed
Forgejo Docker Build / Root app tests (push) Successful in 51s
Forgejo Docker Build / Build Docker image (push) Successful in 8s
Forgejo Docker Build / End-to-end (browser) (push) Failing after 18s
test(e2e): the browser suite follows generation into its job, and can log in again
Two faults kept every spec in this file from running, both in the same code
path and both from the SSO-only change. The fixtures minted a session by
shelling out to `docker compose exec`, which cannot work from inside the
Playwright container — it has no docker CLI and no socket — so all seventeen
specs (both viewports) died at the auth fixture before touching the page. The
harness now mints both sessions on the host and passes them in, and the seed's
connection banner is no longer mistaken for the token: it prints before it,
so the token is the last line. The host-side docker path stays as the fallback
for `npx playwright test` run directly on the host.

The specs then move to what generation is now: the click is answered with a
job, so the assertions follow the job list. A generation in flight is listed
and survives a reload, a job that lands reloads the library and says what it
was written from, a job that fails says why, and what was searched for is
reported when the job lands rather than when the button is pressed.
2026-09-16 04:02:10 +02:00

117 lines
5.9 KiB
Bash
Executable file

#!/usr/bin/env bash
# End-to-end tests: a real browser, against a real copy of the whole app, on a
# database that did not exist a minute ago.
#
# scripts/e2e.sh # fresh stack, run every spec, leave it up
# scripts/e2e.sh auth-screen # only specs matching a pattern
# scripts/e2e.sh --down # tear the stack down and stop
# scripts/e2e.sh --no-reset # reuse the running stack and its data
#
# Every run recreates the database from empty, so nothing carries over between
# runs and the migrations are proved from nothing each time. The stack is left
# running afterwards on purpose: http://127.0.0.1:3553 is then a working copy
# of the app you can click around in, and http://127.0.0.1:3554 is the report.
set -euo pipefail
cd "$(dirname "$0")/.."
COMPOSE=(docker compose -f docker-compose.yml -f docker-compose.e2e.yml)
SERVICES=(postgres-e2e redis-e2e pediatric-scribe-e2e)
PLAYWRIGHT_IMAGE="mcr.microsoft.com/playwright:v1.50.0-noble"
APP_URL="http://127.0.0.1:3553"
REPORT_URL="http://127.0.0.1:3554"
RESET=true
GREP=""
for arg in "$@"; do
case "$arg" in
--down)
echo "==> Tearing down the e2e stack"
"${COMPOSE[@]}" rm -sfv "${SERVICES[@]}" e2e-report >/dev/null 2>&1 || true
echo " gone (its database was in RAM, so nothing is left on disk)"
exit 0 ;;
--no-reset) RESET=false ;;
-*) echo "unknown option: $arg" >&2; exit 2 ;;
*) GREP="$arg" ;;
esac
done
# ── Static reference lint ─────────────────────────────────────────────
# Catches the class of bug where a JS file reaches for an id that no HTML
# element (or dynamic id assignment anywhere in the repo) ever produces — the
# lightbox and adminMilestones dead-code bugs were both this shape and both
# went undetected until someone tripped over them in the real app. Cheap, so
# it runs first and fails before anything is built.
echo "==> Static reference lint"
docker run --rm -v "$PWD:/work" -w /work node:20-alpine node scripts/lint-references.js
# ── A stack with nothing in it ────────────────────────────────────────
# rm -sfv, not `down`: `down` on a merged compose file would take production's
# services with it. This names only the e2e ones. Their database and Redis are
# tmpfs, so removing the containers is what makes the data ephemeral.
if [ "$RESET" = true ]; then
echo "==> Recreating the e2e stack (fresh database)"
"${COMPOSE[@]}" rm -sfv "${SERVICES[@]}" >/dev/null 2>&1 || true
GIT_REVISION="$(git rev-parse HEAD 2>/dev/null || echo unknown)" \
"${COMPOSE[@]}" up -d --build --wait "${SERVICES[@]}"
else
echo "==> Reusing the running e2e stack"
GIT_REVISION="$(git rev-parse HEAD 2>/dev/null || echo unknown)" \
"${COMPOSE[@]}" up -d --wait "${SERVICES[@]}"
fi
# What is actually being tested. A stale image here would make a green run
# meaningless, which is the failure mode worth naming out loud.
RUNNING="$(curl -fsS --max-time 10 "$APP_URL/api/build" | sed -n 's/.*"buildId":"\([^"]*\)".*/\1/p' || true)"
echo " testing revision ${RUNNING:-<unknown>}"
# ── Seed ──────────────────────────────────────────────────────────────
# The accounts the fixtures log in as. The database is empty every run, so
# unlike before this is not optional and a failure here is fatal: tests that
# cannot log in fail in a way that looks like the app is broken.
echo "==> Seeding e2e accounts"
"${COMPOSE[@]}" exec -T pediatric-scribe-e2e node e2e/seed.js
# ── Sessions for the browser ──────────────────────────────────────────
# Minted here, on the host, where the docker CLI exists: the browser runs in a
# container that has neither the CLI nor its socket, so a fixture that shells
# out to `docker` from in there cannot log anybody in and every spec dies at
# the auth fixture before it starts. The seed prints its connection banner
# first, so the token is the last line.
echo "==> Minting e2e sessions"
E2E_AUTH_TOKEN="$("${COMPOSE[@]}" exec -T pediatric-scribe-e2e node e2e/seed.js token "${E2E_TEST_EMAIL:-e2e-user@ped-ai.test}" | tail -n1)"
E2E_ADMIN_AUTH_TOKEN="$("${COMPOSE[@]}" exec -T pediatric-scribe-e2e node e2e/seed.js token "${E2E_ADMIN_EMAIL:-e2e-admin@ped-ai.test}" | tail -n1)"
if [ -z "$E2E_AUTH_TOKEN" ] || [ -z "$E2E_ADMIN_AUTH_TOKEN" ]; then
echo "FATAL: the e2e sessions could not be minted" >&2
exit 1
fi
# ── The browser ───────────────────────────────────────────────────────
# Host network and a loopback URL, because the browser only treats loopback as
# a secure context over plain http, and the app cannot sign in without one.
echo "==> Playwright"
set +e
docker run --rm --ipc=host \
--network=host \
-v "$PWD/e2e":/work \
-w /work \
-e BASE_URL="$APP_URL" \
-e CI=true \
-e OPENAPI_UNDESCRIBED_BUDGET="${OPENAPI_UNDESCRIBED_BUDGET:-}" \
-e E2E_AUTH_TOKEN="$E2E_AUTH_TOKEN" \
-e E2E_ADMIN_AUTH_TOKEN="$E2E_ADMIN_AUTH_TOKEN" \
"$PLAYWRIGHT_IMAGE" \
sh -c "npm install --no-audit --no-fund --silent && npx playwright test ${GREP:+--grep \"$GREP\"}"
STATUS=$?
set -e
# The report is worth serving whether the run passed or failed — a pass is
# where you check that a spec did what you thought it did.
"${COMPOSE[@]}" up -d e2e-report >/dev/null 2>&1 || true
echo
if [ "$STATUS" -eq 0 ]; then echo "==> ✅ e2e passed"; else echo "==> ❌ e2e failed (exit $STATUS)"; fi
echo " app $APP_URL (a working copy, throwaway data)"
echo " report $REPORT_URL (traces and screenshots of any failure)"
echo " stop scripts/e2e.sh --down"
exit "$STATUS"