ci(e2e): add Playwright e2e job on docker-host with throttle GoTrue and artifacts
Refs #38 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
ec2d45c319
commit
23816377a5
6 changed files with 221 additions and 3 deletions
31
.forgejo/scripts/e2e-run.sh
Executable file
31
.forgejo/scripts/e2e-run.sh
Executable 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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
40
e2e/support/tcp-forward.mjs
Normal file
40
e2e/support/tcp-forward.mjs
Normal 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}`));
|
||||
}
|
||||
|
|
@ -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.
|
||||
|
|
|
|||
Loading…
Reference in a new issue