diff --git a/package.json b/package.json index 7dfa4692..9a40f205 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "private": true, "scripts": { "test:e2e": "playwright test", + "test:e2e:local": "bash scripts/e2e-local-stack.sh", "test:e2e:headed": "playwright test --headed", "test:e2e:ui": "playwright test --ui" }, diff --git a/playwright.config.ts b/playwright.config.ts index 61133b9f..30dbe900 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -43,12 +43,17 @@ export default defineConfig({ }, ], - /* Start the backend and the Next.js dev server when running locally */ + /* Start the backend and the Next.js dev server when running locally. + The backend command first runs the local-stack setup (Docker check, + supabase start, schema + migrations + grants, env wiring) so a plain + `npm run test:e2e` works against a ready local Supabase — see + scripts/e2e-local-stack.sh. Idempotent: a few seconds when already up. */ webServer: process.env.CI ? undefined : [ { - command: "npm run dev", + command: + "bash ../scripts/e2e-local-stack.sh --setup-only && npm run dev", cwd: "backend", url: "http://localhost:3001/health", reuseExistingServer: true, diff --git a/scripts/e2e-local-stack.sh b/scripts/e2e-local-stack.sh new file mode 100755 index 00000000..19bdcbb7 --- /dev/null +++ b/scripts/e2e-local-stack.sh @@ -0,0 +1,116 @@ +#!/usr/bin/env bash +# Boot the full local e2e stack and run the Playwright suite. +# +# Local-machine mirror of .github/workflows/e2e.yml: starts the Supabase CLI +# stack (Docker), loads backend/schema.sql + backend/migrations/ + the +# service_role grants (schema.sql assumes a hosted project where service_role +# is already privileged — see docs/e2e-ci.md), points backend/.env and +# frontend/.env.local at the local stack, then runs `npx playwright test`. +# +# Usage, from the repo root: +# npm run test:e2e:local # whole suite +# npm run test:e2e:local -- -g "display name" # extra args go to Playwright +# +# With --setup-only the script prepares the stack and exits without running +# Playwright — playwright.config.ts uses this in the backend webServer command +# so a plain `npm run test:e2e` also boots against a ready local stack. +# +# The first run rewrites the Supabase lines in your env files; the previous +# (e.g. hosted) versions are kept once as .env.hosted.bak / .env.local.hosted.bak. +# Restore those backups to point back at the hosted project. +set -euo pipefail + +SETUP_ONLY=0 +if [ "${1:-}" = "--setup-only" ]; then + SETUP_ONLY=1 + shift +fi + +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +BACKEND="$ROOT/backend" +FRONTEND="$ROOT/frontend" + +if ! docker info >/dev/null 2>&1; then + echo "Docker is not running — start it first (open -a Docker) and retry." >&2 + exit 1 +fi + +cd "$BACKEND" + +if [ ! -f supabase/config.toml ]; then + npx supabase init --force --with-vscode-settings=false || + npx supabase init --force +fi + +# Idempotent: if the stack is already up this is a no-op. +npx supabase start + +STATUS=$(npx supabase status -o json) +DB_URL=$(jq -r '.DB_URL' <<<"$STATUS") +API_URL=$(jq -r '.API_URL' <<<"$STATUS") +ANON_KEY=$(jq -r '.ANON_KEY' <<<"$STATUS") +SERVICE_KEY=$(jq -r '.SERVICE_ROLE_KEY' <<<"$STATUS") + +# schema.sql is not idempotent, so only load it into a virgin database; the +# dated migrations ARE re-runnable and fill any gap schema.sql has (it lags — +# see docs/e2e-ci.md), so apply them every time. +if [ "$(psql "$DB_URL" -tAc "SELECT to_regclass('public.user_profiles') IS NULL")" = "t" ]; then + echo "Loading schema.sql into fresh database…" + psql "$DB_URL" -v ON_ERROR_STOP=1 -q -f schema.sql +fi +for m in migrations/*.sql; do + psql "$DB_URL" -q -f "$m" >/dev/null 2>&1 || + echo "warning: migration returned non-zero (already applied?): $m" +done +psql "$DB_URL" -v ON_ERROR_STOP=1 -q <<'SQL' +GRANT USAGE ON SCHEMA public TO service_role; +GRANT ALL ON ALL TABLES IN SCHEMA public TO service_role; +GRANT ALL ON ALL SEQUENCES IN SCHEMA public TO service_role; +GRANT ALL ON ALL FUNCTIONS IN SCHEMA public TO service_role; +NOTIFY pgrst, 'reload schema'; +SQL + +# Rewrite only the Supabase lines of the env files, preserving everything else +# (API keys, R2 storage, …). Keep a one-time backup of the pre-local versions. +set_kv() { + local file=$1 key=$2 value=$3 + if grep -q "^${key}=" "$file" 2>/dev/null; then + awk -v k="$key" -v v="$value" \ + 'index($0, k"=") == 1 { print k "=" v; next } { print }' \ + "$file" >"$file.tmp" && mv "$file.tmp" "$file" + else + echo "${key}=${value}" >>"$file" + fi +} + +[ -f .env ] || cp .env.example .env +[ -f .env.hosted.bak ] || cp .env .env.hosted.bak +set_kv .env SUPABASE_URL "$API_URL" +set_kv .env SUPABASE_SECRET_KEY "$SERVICE_KEY" +# The suite fires well over the backend's default 300-requests/15-min general +# cap in one run; once tripped every call 429s and profile/list waits time out. +# Same overrides CI uses — e2e is not testing throttling. +set_kv .env RATE_LIMIT_GENERAL_MAX 100000 +set_kv .env RATE_LIMIT_CHAT_MAX 100000 +set_kv .env RATE_LIMIT_CHAT_CREATE_MAX 100000 +set_kv .env RATE_LIMIT_UPLOAD_MAX 100000 +set_kv .env RATE_LIMIT_EXPORT_MAX 100000 +set_kv .env RATE_LIMIT_DATA_DELETE_MAX 100000 + +touch "$FRONTEND/.env.local" +[ -f "$FRONTEND/.env.local.hosted.bak" ] || cp "$FRONTEND/.env.local" "$FRONTEND/.env.local.hosted.bak" +set_kv "$FRONTEND/.env.local" NEXT_PUBLIC_SUPABASE_URL "$API_URL" +set_kv "$FRONTEND/.env.local" NEXT_PUBLIC_SUPABASE_PUBLISHABLE_DEFAULT_KEY "$ANON_KEY" +set_kv "$FRONTEND/.env.local" NEXT_PUBLIC_API_BASE_URL "http://localhost:3001" + +echo "Local stack ready: $API_URL (db: ${DB_URL%%\?*})" + +if [ "$SETUP_ONLY" = "1" ]; then + exit 0 +fi + +echo "NOTE: kill any backend/frontend dev servers started before this script —" +echo "they hold the old env. playwright.config.ts starts fresh ones if none run." + +cd "$ROOT" +npx playwright test "$@"