E2E Testing
Playwright E2E tests for Next.js app and Fastify API (Scalar login).
End-to-end tests run full user flows using Playwright. Two suites: Next E2E (apps/next) tests the web app + auth; Fastify E2E (apps/fastify) tests Scalar UI login. Both use test@test.ai for magic-link when ALLOW_TEST=true.
Root QA: pnpm qa runs the full pipeline (install → checktypes → lint → build → test) and ends with pnpm test:e2e, which runs Fastify E2E then Next E2E via scripts/run-e2e.mjs (both use test:e2e:local to spawn servers). The E2E script kills any processes on ports 3000 and 3001 before starting (unless SKIP_KILL_PORTS=1).
Commands
| Command | Description |
|---|---|
pnpm test:e2e (root) | Run Fastify API E2E, then Next E2E (spawns local servers) |
pnpm test:e2e (in apps/next) | Run E2E; expects URLs via env or --app/--api params |
pnpm test:e2e:local (in apps/next) | Build (unless SKIP_BUILD=1), spawn Fastify + Next, poll until healthy, run tests, cleanup |
pnpm test:e2e (in apps/fastify) | Run E2E; expects API URL via env or --api param |
pnpm test:e2e:local (in apps/fastify) | Spawn API, poll until healthy, run tests, cleanup |
pnpm test:e2e:ui (in apps/next or apps/fastify) | Run with Playwright UI |
pnpm test:e2e:debug (in apps/next or apps/fastify) | Run in debug mode |
URL Configuration
test:e2e accepts URL params. Next: --app and --api. Fastify: --api only. Params override environment variables.
# Next (both app and API)
pnpm test:e2e --app=https://my-app.vercel.app --api=https://my-api.vercel.app
pnpm test:e2e -- --app https://localhost:3000 --api https://localhost:3001
# Fastify (API only)
pnpm test:e2e --api=https://my-api.vercel.app
pnpm test:e2e -- --api https://localhost:3001
# Environment (defaults when no params)
PLAYWRIGHT_APP_URL=https://... PLAYWRIGHT_API_URL=https://... pnpm test:e2eDefaults: Next NEXT_PUBLIC_APP_URL or http://localhost:3000; API NEXT_PUBLIC_API_URL or http://localhost:3001.
Local Development
- Full suite (root):
pnpm test:e2e— runs Fastify E2E then Next E2E (both spawn servers, poll, run, cleanup). - Next only:
pnpm --filter @repo/next test:e2e:local— builds, spawns Fastify + Next, runs Next E2E, cleanup. - Fastify only:
pnpm --filter @repo/fastify test:e2e:local— spawns API, runs Scalar login E2E, cleanup. - Manual (two terminals): Build with
pnpm --filter @repo/next build:e2e, runpnpm --filter @repo/next start:e2e:serversin one terminal andpnpm --filter @repo/next test:e2ein another. - External URLs: Use
--appand--api(Next) or--api(Fastify) to target already-running servers or Vercel previews.
Test Email (test@test.ai)
Magic-link E2E tests use test@test.ai when ALLOW_TEST=true. The token is stored in the database (verification.token_plain) and read from /test/magic-link/last. No fake outbox; works with Vercel serverless (shared DB across instances).
- Local:
test:e2e:localandstart:cisetALLOW_TEST=true. - Vercel previews: Set
ALLOW_TEST=truefor the Preview environment in project env vars.
CI
Test workflows run on pull requests (path-filtered):
- API E2E (
api-e2e.yml) — Runs whenapps/fastifyor its deps change. Spawns Fastify locally, runs Playwright. - Next E2E (
next-e2e.yml) — Runs whenapps/nextor its deps change. Spawns Fastify + Next locally, runs Playwright. - Packages (
packages-test.yml) — Runs whenpackagesortoolschange. Unit tests only; excludes app E2E.
Lint and security run on every PR. See GitHub Actions.
Vercel Deployment Protection (Manual Testing)
To run E2E manually against Vercel preview deployments with Deployment Protection enabled:
When Deployment Protection is enabled:
- Protection Bypass for Automation: In each project (Next, Fastify): Settings → Deployment Protection → Protection Bypass for Automation → enable → generate secret.
- Use the same bypass secret for both projects.
- Add
VERCEL_AUTOMATION_BYPASS_SECRETto GitHub secrets. - Playwright adds
x-vercel-protection-bypassandx-vercel-set-bypass-cookieheaders automatically when the env var is set. - OPTIONS Allowlist (for CORS): In Fastify project, Deployment Protection → OPTIONS Allowlist → add
/or/authso CORS preflight succeeds.
See Vercel Protection Bypass for Automation.
Environment Variables
- Chat E2E (chat-assistant.spec.ts): Requires
OPEN_ROUTER_API_KEY. Local: add toapps/fastify/.env.test. CI: set in GitHub secrets;test:e2e:localpassesprocess.envand.env.testto the Fastify server. Currently skipped:getAuthTokenreturns null for the chat transport in E2E.loginAsTestUserWithTokenInjectionand/api/auth/test-set-session(ALLOW_TEST only) exist for session injection when cookie propagation from magic-link verify fails. - E2E CI: Uses local servers only; no Vercel URLs. For manual testing against previews, pass
--app/--apiURLs.
ALLOW_TEST
ALLOW_TEST=true enables:
- Fake email for
@test.ai(no Resend) - DB-backed token storage for
/test/magic-link/last - Production guard: server exits if
ALLOW_TESTis true in production
Next E2E Specs
| Spec | Coverage |
|---|---|
magic-link-auth.spec.ts | Magic link login, invalid/expired token errors, email validation, protected route, JWT session |
link-email.spec.ts | "Already linked" when user has email (magic link flow via /auth/login) |
chat-assistant.spec.ts | Chat UI with authenticatedPage; Who am I? prompt (skipped until getAuthToken fixed) |
Project order
Disabled / skipped specs
chat-assistant skipped (test.describe.skip) until getAuthToken fixed for chat transport.
Troubleshooting
- Port in use:
scripts/run-e2e.mjskills processes on 3000/3001 before E2E. If ports remain in use, runbash scripts/kill-test-servers.shmanually. - Tests fail with unreachable URLs: Ensure both servers are running, or pass correct
--app/--apiURLs. - Token extraction fails: Verify
ALLOW_TEST=trueand email istest@test.ai. - Vercel CORS errors: Add
/or/authto OPTIONS Allowlist in Fastify Deployment Protection. - Protection bypass not working: Ensure
VERCEL_AUTOMATION_BYPASS_SECRETmatches the secret in both Vercel projects.
Related
- Frontend Testing - E2E-only frontend testing strategy
- GitHub Actions - CI workflow
- Vercel Deployment - Preview deployment and env vars