#!/usr/bin/env bash # scripts/e2e-full.sh # # Boots the full explorer stack (postgres, elasticsearch, redis, backend # API, frontend), waits for readiness, runs the Playwright full-stack # smoke spec against it, and tears everything down. Used by the # `make e2e-full` target and by the e2e-full CI workflow. # # Env vars: # E2E_KEEP_STACK=1 # don't tear down on exit (for debugging) # E2E_SKIP_DOCKER=1 # assume backend + deps already running # EXPLORER_URL # defaults to http://localhost:3000 # EXPLORER_API_URL # defaults to http://localhost:8080 # E2E_SCREENSHOT_DIR # defaults to test-results/screenshots # JWT_SECRET # required; generated ephemerally if unset # CSP_HEADER # required; a dev-safe default is injected set -euo pipefail ROOT="$(cd "$(dirname "$0")/.." && pwd)" cd "$ROOT" COMPOSE="deployment/docker-compose.yml" COMPOSE_PROJECT="${COMPOSE_PROJECT:-explorer-e2e}" export EXPLORER_URL="${EXPLORER_URL:-http://localhost:3000}" export EXPLORER_API_URL="${EXPLORER_API_URL:-http://localhost:8080}" export E2E_SCREENSHOT_DIR="${E2E_SCREENSHOT_DIR:-$ROOT/test-results/screenshots}" mkdir -p "$E2E_SCREENSHOT_DIR" # Generate ephemeral JWT secret if the caller didn't set one. Real # deployments use fail-fast validation (see PR #3); for a local run we # want a fresh value each invocation so stale tokens don't bleed across # runs. export JWT_SECRET="${JWT_SECRET:-$(openssl rand -hex 32)}" export CSP_HEADER="${CSP_HEADER:-default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self' http://localhost:8080 ws://localhost:8080}" log() { printf '[e2e-full] %s\n' "$*"; } teardown() { local ec=$? if [[ "${E2E_KEEP_STACK:-0}" == "1" ]]; then log "E2E_KEEP_STACK=1; leaving stack running." return $ec fi log "tearing down stack" if [[ "${E2E_SKIP_DOCKER:-0}" != "1" ]]; then docker compose -p "$COMPOSE_PROJECT" -f "$COMPOSE" down -v --remove-orphans >/dev/null 2>&1 || true fi if [[ -n "${BACKEND_PID:-}" ]]; then kill "$BACKEND_PID" 2>/dev/null || true; fi if [[ -n "${FRONTEND_PID:-}" ]]; then kill "$FRONTEND_PID" 2>/dev/null || true; fi return $ec } trap teardown EXIT wait_for() { local url="$1" label="$2" retries="${3:-60}" log "waiting for $label at $url" for ((i=0; i/dev/null 2>&1; then log " $label ready" return 0 fi sleep 2 done log " $label never became ready" return 1 } if [[ "${E2E_SKIP_DOCKER:-0}" != "1" ]]; then log "starting postgres, elasticsearch, redis via docker compose" docker compose -p "$COMPOSE_PROJECT" -f "$COMPOSE" up -d postgres elasticsearch redis log "waiting for postgres" for ((i=0; i<60; i++)); do if docker compose -p "$COMPOSE_PROJECT" -f "$COMPOSE" exec -T postgres pg_isready -U explorer >/dev/null 2>&1; then break fi sleep 2 done fi export DB_HOST="${DB_HOST:-localhost}" export DB_PORT="${DB_PORT:-5432}" export DB_USER="${DB_USER:-explorer}" export DB_PASSWORD="${DB_PASSWORD:-changeme}" export DB_NAME="${DB_NAME:-explorer}" export REDIS_HOST="${REDIS_HOST:-localhost}" export REDIS_PORT="${REDIS_PORT:-6379}" export ELASTICSEARCH_URL="${ELASTICSEARCH_URL:-http://localhost:9200}" log "running migrations" (cd backend && go run database/migrations/migrate.go) || { log "migrations failed; continuing so tests can report the real backend state" } log "starting backend API on :8080" (cd backend/api/rest && go run . >/tmp/e2e-backend.log 2>&1) & BACKEND_PID=$! wait_for "$EXPLORER_API_URL/healthz" backend 120 || { log "backend log tail:"; tail -n 60 /tmp/e2e-backend.log || true exit 1 } log "building frontend" (cd frontend && npm ci --no-audit --no-fund --loglevel=error && npm run build) log "starting frontend on :3000" (cd frontend && PORT=3000 HOST=127.0.0.1 NEXT_PUBLIC_API_URL="$EXPLORER_API_URL" npm run start >/tmp/e2e-frontend.log 2>&1) & FRONTEND_PID=$! wait_for "$EXPLORER_URL" frontend 60 || { log "frontend log tail:"; tail -n 60 /tmp/e2e-frontend.log || true exit 1 } log "running Playwright full-stack smoke" npx playwright install --with-deps chromium >/dev/null EXPLORER_URL="$EXPLORER_URL" EXPLORER_API_URL="$EXPLORER_API_URL" \ npx playwright test scripts/e2e-full-stack.spec.ts --reporter=list log "done; screenshots in $E2E_SCREENSHOT_DIR"