ci(e2e): add Playwright e2e job on docker-host with throttle GoTrue and artifacts
Some checks failed
CI / flutter (pull_request) Successful in 1m15s
CI / web-build (pull_request) Successful in 54s
CI / supabase (pull_request) Successful in 3s
CI / supabase-tests (pull_request) Successful in 30s
CI / e2e (pull_request) Failing after 4m3s

Refs #38

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
cschlaefke 2026-11-03 10:45:17 +01:00
parent ec2d45c319
commit 23816377a5
6 changed files with 221 additions and 3 deletions

31
.forgejo/scripts/e2e-run.sh Executable file
View file

@ -0,0 +1,31 @@
#!/usr/bin/env bash
# Playwright-E2E (Projekte api, web, throttle) gegen den laufenden Supabase-Stack im dind (Issue #38, D2).
# Voraussetzungen: DOCKER_HOST/DOCKER_GW gesetzt, `supabase start` lief, Extra-GoTrue auf :9998 laeuft,
# Web-Build in app/build/web. Die Supabase-Ports liegen im dind; ein TCP-Relay macht sie unter
# 127.0.0.1 erreichbar, wie es die Tests und die Web-App (CORS/Origin, Site-URL localhost:3000) erwarten.
# Der Spike (SPIKE_GOTRUE_URL) laeuft in CI bewusst nicht.
set -euo pipefail
: "${DOCKER_GW:?DOCKER_GW (dind-Gateway) fehlt}"
: "${SUPABASE_ANON_KEY:?SUPABASE_ANON_KEY fehlt}"
node e2e/support/tcp-forward.mjs "$DOCKER_GW" 54321 54322 54324 9998 &
relay=$!
trap 'kill "$relay" 2>/dev/null || true' EXIT
for port in 54321 54322 54324 9998; do
for _ in $(seq 1 20); do
(exec 3<>"/dev/tcp/127.0.0.1/${port}") 2>/dev/null && break
sleep 0.5
done
done
export SUPABASE_URL=http://127.0.0.1:54321
export MAILPIT_URL=http://127.0.0.1:54324
export E2E_DB_URL=postgresql://postgres:postgres@127.0.0.1:54322/postgres
export WEB_URL=http://localhost:3000
export THROTTLE_GOTRUE_URL=http://127.0.0.1:9998
export CI=true
cd e2e
npx playwright test --project=api --project=web --project=throttle

View file

@ -182,3 +182,132 @@ jobs:
- name: Aufräumen
if: always()
run: npx --yes supabase@${{ env.SUPABASE_CLI_VERSION }} stop --no-backup || true
# Web-E2E (Playwright: api, web, throttle) gegen lokalen Supabase-Stack und gebaute Flutter-Web-App (Issue #38, Spec #1 D2).
# Läuft wie supabase-tests auf "docker-host" (Docker-CLI + dind per TCP). Der Stack liegt im dind; ein
# TCP-Relay (e2e/support/tcp-forward.mjs) macht ihn im Job unter 127.0.0.1 erreichbar.
# SICHERHEIT: Der Job führt PR-Code mit Zugriff auf den dind-Daemon des Runners aus (Daemon = Root-Äquivalent).
# Deshalb nicht für Fork-PRs (Bedingung unten); Fork-Beiträge nur nach Review und Übernahme in einen Branch dieses Repos.
# Gleiches Risiko hat supabase-tests; dort ist es (noch) nicht eingeschränkt.
# needs: supabase-tests, weil beide Jobs dieselben festen Container-Namen/Ports (project_id shuffle, 54321 ...) im dind belegen.
# Der Spike (SPIKE_GOTRUE_URL, Issue #37) läuft nicht in CI.
e2e:
runs-on: docker-host
needs: [supabase-tests]
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
env:
DOCKER_CLI_VERSION: 27.3.1
DIND_PORT: 2375
PUB_CACHE: ${{ github.workspace }}/.pub-cache
PLAYWRIGHT_BROWSERS_PATH: ${{ github.workspace }}/.playwright-browsers
steps:
- uses: https://code.forgejo.org/actions/checkout@v4
- name: Werkzeuge prüfen
run: |
missing=""
for t in git curl unzip xz jq; do command -v "$t" >/dev/null || missing="$missing $t"; done
if [ -n "$missing" ]; then
apt-get update -qq && apt-get install -y -qq --no-install-recommends $missing xz-utils ca-certificates
fi
node --version
- name: Docker-CLI installieren und dind anbinden
run: |
arch=$(uname -m)
curl -fsSL "https://download.docker.com/linux/static/stable/${arch}/docker-${DOCKER_CLI_VERSION}.tgz" \
| tar -xz -C /usr/local/bin --strip-components=1 docker/docker
hex=$(awk '$2=="00000000"{print $3; exit}' /proc/net/route)
gw=$(printf '%d.%d.%d.%d' "0x${hex:6:2}" "0x${hex:4:2}" "0x${hex:2:2}" "0x${hex:0:2}")
echo "DOCKER_GW=${gw}" >> "$GITHUB_ENV"
echo "DOCKER_HOST=tcp://${gw}:${DIND_PORT}" >> "$GITHUB_ENV"
DOCKER_HOST="tcp://${gw}:${DIND_PORT}" docker version --format 'dind {{.Server.Version}}'
- name: Supabase starten (mit Mailpit, ohne Studio u. a.)
run: npx --yes supabase@${{ env.SUPABASE_CLI_VERSION }} start -x studio,imgproxy,edge-runtime,logflare,vector
- name: Datenbank zurücksetzen
run: npx --yes supabase@${{ env.SUPABASE_CLI_VERSION }} db reset
- name: Lokale Schlüssel lesen
# Die Werte sind die öffentlichen Demo-Schlüssel der CLI, trotzdem nur maskiert weiterreichen.
run: |
status=$(npx --yes supabase@${{ env.SUPABASE_CLI_VERSION }} status -o env)
get() { printf '%s\n' "$status" | sed -n "s/^$1=\"\{0,1\}\([^\"]*\)\"\{0,1\}$/\1/p" | head -n1; }
anon=$(get ANON_KEY); service=$(get SERVICE_ROLE_KEY)
test -n "$anon" && test -n "$service" || { echo "ANON_KEY/SERVICE_ROLE_KEY nicht in 'supabase status -o env' gefunden"; exit 1; }
echo "::add-mask::$anon"
echo "::add-mask::$service"
echo "SUPABASE_ANON_KEY=${anon}" >> "$GITHUB_ENV"
echo "SUPABASE_SERVICE_ROLE_KEY=${service}" >> "$GITHUB_ENV"
# Env-Datei für den Web-Build: Platzhalter-Datei bleibt unverändert, der Schlüssel kommt zur Laufzeit.
jq --arg k "$anon" '.SUPABASE_ANON_KEY = $k | .SUPABASE_URL = "http://127.0.0.1:54321"' \
app/env/ci.example.json > "${RUNNER_TEMP:-/tmp}/ci.e2e.json"
echo "E2E_ENV_FILE=${RUNNER_TEMP:-/tmp}/ci.e2e.json" >> "$GITHUB_ENV"
- name: Extra-GoTrue für Drosselung (Krit. 7) starten
run: |
BIND_ADDR=0.0.0.0 HEALTH_HOST="$DOCKER_GW" bash e2e/support/start-throttle-gotrue.sh start throttle
- name: Flutter-SDK-Cache
uses: https://code.forgejo.org/actions/cache@v4
with:
path: ${{ env.FLUTTER_HOME }}
key: flutter-${{ env.FLUTTER_VERSION }}-docker-host
- name: Flutter installieren
run: |
if [ ! -x "$FLUTTER_HOME/bin/flutter" ]; then
rm -rf "$FLUTTER_HOME"
git clone --depth 1 --branch "$FLUTTER_VERSION" https://github.com/flutter/flutter.git "$FLUTTER_HOME"
fi
echo "$FLUTTER_HOME/bin" >> "$GITHUB_PATH"
export PATH="$FLUTTER_HOME/bin:$PATH"
export CI=true
flutter config --no-analytics --enable-web
flutter precache --web
- name: Pub-Cache
uses: https://code.forgejo.org/actions/cache@v4
with:
path: ${{ github.workspace }}/.pub-cache
key: pub-${{ env.FLUTTER_VERSION }}-${{ hashFiles('app/pubspec.lock') }}
restore-keys: pub-${{ env.FLUTTER_VERSION }}-
- name: Web-Build mit lokalem Anon-Key
working-directory: app
run: |
flutter pub get
flutter build web --release --dart-define-from-file="$E2E_ENV_FILE"
- name: Playwright-Cache
uses: https://code.forgejo.org/actions/cache@v4
with:
path: ${{ github.workspace }}/.playwright-browsers
key: playwright-${{ hashFiles('e2e/package-lock.json') }}
- name: E2E-Abhängigkeiten und Browser
working-directory: e2e
run: |
npm ci
npx playwright install --with-deps chromium
- name: Playwright (api, web, throttle)
run: bash .forgejo/scripts/e2e-run.sh
- name: Artefakte hochladen (Report, Traces)
if: failure()
uses: https://code.forgejo.org/forgejo/upload-artifact@v4
with:
name: playwright-${{ github.run_number }}
path: |
e2e/playwright-report
e2e/test-results
retention-days: 7
if-no-files-found: ignore
- name: Aufräumen
if: always()
run: |
bash e2e/support/start-throttle-gotrue.sh stop throttle || true
npx --yes supabase@${{ env.SUPABASE_CLI_VERSION }} stop --no-backup || true

View file

@ -190,6 +190,18 @@ Die Playwright-Tests unter `e2e/` (Projekte `api`, `web`, `throttle`, `spike`; A
| AUTH-CFG-14, -15, -16 | `krit05-sitzung.spec.ts`, `config.spec.ts` |
| AUTH-CFG-17, -18, -19 | `krit11-sprache-benachrichtigungen.spec.ts` |
### CI-Job `e2e` (Issue #38, Spec #1 D2)
Der Job `e2e` in `.forgejo/workflows/ci.yml` läuft auf dem Runner `docker-host` (Docker-CLI + dind per TCP, wie `supabase-tests`) und führt die Projekte `api`, `web` und `throttle` aus. Ablauf:
1. `supabase start -x studio,imgproxy,edge-runtime,logflare,vector` (Mailpit und Postgres bleiben) und `supabase db reset`.
2. Anon- und Service-Role-Key kommen zur Laufzeit aus `supabase status -o env`; daraus entsteht eine temporäre Env-Datei (`jq` auf `app/env/ci.example.json`, im `RUNNER_TEMP`, nicht im Repo) für `flutter build web --dart-define-from-file`. Die Datei `ci.example.json` bleibt unverändert.
3. Zweiter GoTrue-Container für Krit. 7: `BIND_ADDR=0.0.0.0 HEALTH_HOST=<dind-Gateway> e2e/support/start-throttle-gotrue.sh start throttle` (Port 9998).
4. `.forgejo/scripts/e2e-run.sh` startet den TCP-Relay `e2e/support/tcp-forward.mjs` (127.0.0.1:54321/54322/54324/9998 -> dind-Gateway) und ruft `npx playwright test --project=api --project=web --project=throttle` auf. Der Relay ist nötig, weil die Ports im dind liegen, die Tests, die Web-App (CORS/Origin) und `e2e/support/db.ts` (erlaubt nur lokale Hosts) aber 127.0.0.1 erwarten. Die statische Auslieferung der Web-App auf Port 3000 übernimmt das `webServer` der Playwright-Konfiguration (`serve -s`).
5. Bei Fehlern werden `e2e/playwright-report` und `e2e/test-results` (Traces) als Artefakt `playwright-<Lauf>` hochgeladen. Der Schritt „Aufräumen“ (`if: always()`) entfernt den Extra-Container und stoppt den Supabase-Stack.
Das Projekt `spike` (SMTP-Ausfall, `SPIKE_GOTRUE_URL`) läuft in CI nicht. Der Job hat `needs: supabase-tests` (gleiche Container-Namen und Ports im dind) und läuft nicht für Fork-PRs: Wer PR-Code auf `docker-host` ausführt, hat Zugriff auf den dind-Daemon des Runners.
### SMTP-Ausfall (Spike #37, Stand 2026-11-03, GoTrue `v2.197.0`)
Messung gegen einen zusätzlichen GoTrue-Container, dessen SMTP-Port 1 die Verbindung ablehnt (`e2e/support/start-throttle-gotrue.sh start smtp-down`, Test `e2e/tests/spike/smtp-down.spec.ts`). Mailpit bleibt für die anderen Tests unberührt. Die Tests prüfen nichts, sie protokollieren.

View file

@ -14,9 +14,14 @@
# Der Container nutzt dasselbe Image, dieselbe DB und dasselbe Docker-Netz wie der Auth-Container
# der CLI; die Umgebung wird per `docker inspect` uebernommen (Version nicht hart kodiert). Die
# Rate-Limit-Buckets liegen im Speicher und sind damit von der CLI getrennt (R13).
# Docker-in-Docker (CI, DOCKER_HOST=tcp://...): Der Port wird im dind veroeffentlicht, nicht im Job-Container.
# Dann BIND_ADDR=0.0.0.0 und HEALTH_HOST=<dind-Gateway> setzen (siehe .forgejo/workflows/ci.yml, Job e2e);
# die Tests erreichen den Container ueber e2e/support/tcp-forward.mjs auf 127.0.0.1.
# Nur lokal/CI benutzen: Die kopierte Umgebung enthaelt die lokalen Demo-Secrets der CLI.
set -euo pipefail
BIND_ADDR="${BIND_ADDR:-127.0.0.1}"
HEALTH_HOST="${HEALTH_HOST:-127.0.0.1}"
ACTION="${1:-start}"
PROFILE="${2:-throttle}"
SOURCE="${SOURCE_CONTAINER:-$(docker ps --format '{{.Names}}' | grep -E '^supabase_auth_' | head -n1 || true)}"
@ -57,12 +62,12 @@ case "$PROFILE" in
smtp-down) ENV_ARGS+=(-e "GOTRUE_SMTP_PORT=1") ;; # Host bleibt Mailpit, Port 1 lehnt die Verbindung ab
esac
docker run -d --name "$NAME" --network "$NETWORK" -p "127.0.0.1:${HOST_PORT}:9999" \
docker run -d --name "$NAME" --network "$NETWORK" -p "${BIND_ADDR}:${HOST_PORT}:9999" \
"${ENV_ARGS[@]}" "$IMAGE" auth >/dev/null
for _ in $(seq 1 40); do
if curl -fsS "http://127.0.0.1:${HOST_PORT}/health" >/dev/null 2>&1; then
echo "Container $NAME ($IMAGE, Profil $PROFILE) laeuft auf http://127.0.0.1:${HOST_PORT}"
if curl -fsS "http://${HEALTH_HOST}:${HOST_PORT}/health" >/dev/null 2>&1; then
echo "Container $NAME ($IMAGE, Profil $PROFILE) laeuft auf http://${HEALTH_HOST}:${HOST_PORT}"
exit 0
fi
sleep 0.5

View file

@ -0,0 +1,40 @@
// Einfacher TCP-Relay fuer CI unter Docker-in-Docker (Issue #38, D2).
//
// Die Supabase-Ports (Kong 54321, Postgres 54322, Mailpit 54324, Extra-GoTrue 9998) liegen im dind,
// nicht im Job-Container. Die Tests und die Web-App erwarten 127.0.0.1. Dieses Skript leitet
// 127.0.0.1:<port> an <host>:<port> weiter (HTTP und Postgres, rein TCP).
//
// Aufruf: node tcp-forward.mjs <ziel-host> <port> [<port> ...]
import net from 'node:net';
const [host, ...ports] = process.argv.slice(2);
if (!host || ports.length === 0) {
console.error('Aufruf: node tcp-forward.mjs <ziel-host> <port> [<port> ...]');
process.exit(2);
}
for (const p of ports) {
const port = Number(p);
if (!Number.isInteger(port) || port < 1 || port > 65535) {
console.error(`Ungueltiger Port: ${p}`);
process.exit(2);
}
net
.createServer((client) => {
const upstream = net.connect(port, host);
client.pipe(upstream).pipe(client);
const close = () => {
client.destroy();
upstream.destroy();
};
client.on('error', close);
upstream.on('error', close);
client.on('close', close);
upstream.on('close', close);
})
.on('error', (err) => {
console.error(`Relay ${port}: ${err.message}`);
process.exit(1);
})
.listen(port, '127.0.0.1', () => console.log(`127.0.0.1:${port} -> ${host}:${port}`));
}

View file

@ -26,6 +26,7 @@ supabase test db # alle pgTAP-Tests ausführen
- `e2e/` testet GoTrue über Kong (`api`), die gebaute Web-App (`web`), die IP-Drosselung (`throttle`) und den SMTP-Ausfall (`spike`). Voraussetzung: `supabase start` mit Mailpit und Postgres (Ports 54321, 54322, 54324).
- `e2e/support/db.ts` verbindet sich direkt mit dem lokalen Postgres (Rolle `postgres`) und setzt `recovery_sent_at`/`confirmation_sent_at` für die Ablauftests; es bricht bei nicht-lokalem Host ab.
- Der Extra-Container für `throttle`/`spike` startet mit `e2e/support/start-throttle-gotrue.sh`. Details: [`e2e/README.md`](../e2e/README.md).
- CI: Job `e2e` in `.forgejo/workflows/ci.yml` (Runner `docker-host`, dind); Ablauf in [`docs/auth-konfiguration.md`](../docs/auth-konfiguration.md) Abschnitt 7.
## Regeln
- Jede Tabelle hat `id` (uuid, PK), `created_at`, `updated_at` und Row Level Security.