---
title: "E2E Testing — Full Scope Guide (FastAPI backend + Next.js frontend)"
status: "STRICT / AI-AGENT FRIENDLY / ZERO-AMBIGUITY"
last_verified: "2025-12-18"
---
# 0) What this guide is (and what it is not)
## Goal
Create a **small, high-signal** End-to-End (E2E) test suite that catches **real production bugs** in a FastAPI + Next.js system by running:
- a real FastAPI server (served by Uvicorn via `fastapi run`) :contentReference[oaicite:0]{index=0}
- a real Next.js production server (`next build` then `next start`) :contentReference[oaicite:1]{index=1}
- a real browser-driven test runner (Playwright Test) :contentReference[oaicite:2]{index=2}
- real infrastructure dependencies when applicable (e.g., PostgreSQL service container in CI) :contentReference[oaicite:3]{index=3}
## Non-goals (brutal truth)
- This does **not** guarantee “no production bugs”. E2E reduces risk; it does not eliminate it.
- This does **not** replace unit/integration tests; E2E must remain small or it becomes flaky/ignored.
---
# 1) Versions (PIN THESE — no guessing)
## 1.1 Locked “latest” versions (verified from official registries as of 2025-12-18)
- Next.js (npm `next`): **16.0.10** :contentReference[oaicite:4]{index=4}
- Playwright (npm `playwright`): **1.57.0** :contentReference[oaicite:5]{index=5}
- FastAPI (PyPI `fastapi`): **0.124.4** :contentReference[oaicite:6]{index=6}
- Uvicorn (PyPI `uvicorn`): **0.38.0** :contentReference[oaicite:7]{index=7}
- Node.js LTS: **v24.x (Active LTS)**, latest LTS listed on nodejs.org download page (example shown: v24.12.0) :contentReference[oaicite:8]{index=8}
## 1.2 Policy (MUST)
- You MUST pin these versions (exact) in your repo.
- You MUST NOT rely on `@latest` in CI for reproducibility.
- You MUST update versions intentionally and re-run E2E suite after updates.
---
# 2) Tool choice (mandatory)
## 2.1 E2E runner: Playwright Test (TypeScript)
Playwright Test is an end-to-end test framework with its own runner and tooling. :contentReference[oaicite:9]{index=9}
## 2.2 Next.js + Playwright (official guidance)
Next.js provides an official Playwright E2E guide. :contentReference[oaicite:10]{index=10}
---
# 3) “Zero-Ambiguity” Contracts (non-negotiable)
Your AI-agent must implement tests against the following explicit contracts.
## 3.1 URLs + ports (MUST)
- Frontend base URL: `http://127.0.0.1:3000`
- Backend base URL: `http://127.0.0.1:8000`
You MAY change ports, but then you MUST update:
- Playwright `baseURL`
- backend env vars consumed by frontend (if any)
- CI health checks
## 3.2 Health endpoints (MUST)
Backend MUST expose:
- `GET /healthz` returning HTTP 200
Frontend MUST expose:
- `GET /` returning HTTP 200
Reason: CI must be able to wait until both servers are ready (without sleeps).
## 3.3 Test selector contract (MUST)
- You MUST use Playwright recommended resilient locators like `page.getByRole()` for user-facing elements. :contentReference[oaicite:11]{index=11}
- You MUST use “web-first assertions” (Playwright will wait until the condition is met). :contentReference[oaicite:12]{index=12}
- You MUST NOT use arbitrary sleeps to wait for UI state.
If you need stable hooks:
- You MUST add `data-testid="..."` attributes in Next.js components.
- You MAY customize which attribute Playwright uses via `testIdAttribute` in config. :contentReference[oaicite:13]{index=13}
---
# 4) Repository Layout (required)
repo/ backend/ pyproject.toml (or requirements.txt) app/main.py (example) frontend/ package.json e2e/ package.json playwright.config.ts global-setup.ts (optional if using setup project) tests/ smoke.spec.ts auth.setup.ts critical-flows.spec.ts storage/ .gitkeep
---
# 5) Installation (exact)
## 5.1 Frontend (Next.js) pin
In `frontend/package.json`, pin:
- `"next": "16.0.10"` :contentReference[oaicite:14]{index=14}
## 5.2 Backend (FastAPI) pin
Pin:
- `fastapi==0.124.4` :contentReference[oaicite:15]{index=15}
- `uvicorn==0.38.0` :contentReference[oaicite:16]{index=16}
## 5.3 E2E runner (Playwright) pin
In `e2e/package.json`, pin:
- `"playwright": "1.57.0"` :contentReference[oaicite:17]{index=17}
Install browser dependencies (Linux CI):
- `npx playwright install --with-deps` :contentReference[oaicite:18]{index=18}
---
# 6) Running the system in “production-like” mode (MANDATORY)
## 6.1 Next.js: production build + start (MUST)
Next.js docs specify `next build` then `next start` for a Node.js server deployment. :contentReference[oaicite:19]{index=19}
Commands:
- `cd frontend`
- `npm ci`
- `npm run build`
- `npm run start`
## 6.2 FastAPI: run with production server (MUST)
FastAPI docs state you can start Uvicorn via `fastapi run`. :contentReference[oaicite:20]{index=20}
Commands:
- `cd backend`
- install deps (your chosen method)
- `fastapi run --host 127.0.0.1 --port 8000` *(keep host/port stable)*
---
# 7) Environment variables (strict rules)
## 7.1 Next.js client vs server env
Next.js documents that non-`NEXT_PUBLIC_` env vars are only available on the server, and `NEXT_PUBLIC_` is for browser-bundled variables. :contentReference[oaicite:21]{index=21}
Rules:
- If the browser code needs it: `NEXT_PUBLIC_*`
- If only server needs it: DO NOT prefix with `NEXT_PUBLIC_`
---
# 8) Playwright configuration (STRICT TEMPLATE)
Create `e2e/playwright.config.ts`:
```ts
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests',
// Retries are a supported feature; use them ONLY on CI. :contentReference[oaicite:22]{index=22}
retries: process.env.CI ? 2 : 0,
// Official options: trace and webServer examples are documented. :contentReference[oaicite:23]{index=23}
use: {
baseURL: process.env.E2E_BASE_URL || 'http://127.0.0.1:3000',
trace: 'on-first-retry', // recommended for CI tracing :contentReference[oaicite:24]{index=24}
video: 'retain-on-failure', // video modes documented :contentReference[oaicite:25]{index=25}
// If you standardize test IDs, you can set a custom attribute name. :contentReference[oaicite:26]{index=26}
// testIdAttribute: 'data-testid',
},
reporter: [['html', { open: 'never' }]], // HTML reporter documented :contentReference[oaicite:27]{index=27}
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
],
// Run Next.js server before tests (official webServer option). :contentReference[oaicite:28]{index=28}
webServer: {
command: 'npm --prefix ../frontend run start',
url: 'http://127.0.0.1:3000',
reuseExistingServer: !process.env.CI,
timeout: 120_000,
},
});
9) Authentication strategy (NO GUESSING)¶
You MUST choose ONE of these official patterns and implement it exactly.
Option A (recommended): Setup project that generates storageState¶
Playwright docs explicitly recommend creating a setup project and using storageState. (Playwright)
9.A.1 Create e2e/tests/auth.setup.ts¶
- This test logs in once using the UI and saves storage state to
e2e/storage/auth.json.
9.A.2 Configure “setup project dependency”¶
Follow the Playwright authentication docs to declare a setup project and dependencies. (Playwright)
Option B: Global setup¶
Playwright documents global setup that authenticates once and reuses storageState. (Playwright)
Pick one. DO NOT implement both.
10) Test suite scope (FULL, but controlled)¶
10.1 Test tiers (MUST)¶
Tier 0 — Smoke (runs on every PR)¶
Purpose: fail fast if the deployment is broken. Required smoke tests:
- Homepage loads
- Login page loads
- Backend health endpoint reachable (optional UI-less check)
Tier 1 — Critical user flows (runs on every PR)¶
Pick the top 3–7 flows that, if broken, cause real business damage:
- login
- create primary entity
- update entity
- permission boundary (allowed vs forbidden)
- file upload (if applicable)
- payment flow (if applicable)
Tier 2 — Extended regression (scheduled/nightly)¶
Only if Tier 1 is stable.
10.2 Coverage rule (MUST)¶
-
Every Tier 1 flow MUST assert:
-
user-visible success state, AND
- at least one server-side consequence (visible in UI or via follow-up UI state).
11) How to write tests so they don’t become garbage¶
11.1 Locators (MUST)¶
Use Playwright’s recommended approach:
- “Prioritizing user-facing attributes … such as
page.getByRole()” (Playwright)
11.2 Assertions (MUST)¶
Use Playwright web-first assertions:
- Playwright includes async matchers that wait until expected condition is met. (Playwright)
11.3 No manual sleeps (MUST NOT)¶
If you need waiting:
- rely on locator actionability + web-first assertions (documented behavior). (Playwright)
12) Example test files (templates)¶
12.1 e2e/tests/smoke.spec.ts¶
Minimum smoke test that proves Next.js server responds:
import { test, expect } from '@playwright/test';
test('smoke: homepage loads', async ({ page }) => {
await page.goto('/');
await expect(page).toHaveURL(/\/$/);
});
(Use web-first expect matchers per Playwright docs.) (Playwright)
12.2 e2e/tests/critical-flows.spec.ts (skeleton)¶
import { test, expect } from '@playwright/test';
test('critical: user can log in', async ({ page }) => {
await page.goto('/login');
// Recommended resilient locator style. :contentReference[oaicite:36]{index=36}
await page.getByRole('textbox', { name: /email/i }).fill(process.env.E2E_USER_EMAIL!);
await page.getByRole('textbox', { name: /password/i }).fill(process.env.E2E_USER_PASSWORD!);
await page.getByRole('button', { name: /sign in/i }).click();
// Web-first assertion waits for UI state. :contentReference[oaicite:37]{index=37}
await expect(page.getByRole('heading', { name: /dashboard/i })).toBeVisible();
});
13) Debug artifacts (MANDATORY in CI)¶
13.1 Trace (MUST)¶
Playwright trace viewer docs recommend running traces on CI on first retry with trace: 'on-first-retry'. (Playwright)
13.2 Video (MUST)¶
Playwright documents retain-on-failure mode for video. (Playwright)
14) CI: GitHub Actions (STRICT TEMPLATE)¶
This template uses:
setup-node(official action) (GitHub)setup-python(official action) (GitHub)- PostgreSQL service container (GitHub docs) (GitHub Docs)
Create .github/workflows/e2e.yml:
name: e2e
on:
pull_request:
push:
branches: [ main ]
jobs:
e2e:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16
env:
POSTGRES_USER: e2e
POSTGRES_PASSWORD: e2e
POSTGRES_DB: e2e
ports:
- 5432:5432
options: >-
--health-cmd="pg_isready -U e2e -d e2e"
--health-interval=10s
--health-timeout=5s
--health-retries=10
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Node (LTS)
uses: actions/setup-node@v4
with:
node-version: "24"
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.13"
# Backend deps
- name: Install backend deps
working-directory: backend
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
# Frontend deps + build
- name: Install frontend deps
working-directory: frontend
run: npm ci
- name: Build frontend (production)
working-directory: frontend
run: npm run build
# E2E deps + browsers
- name: Install E2E deps
working-directory: e2e
run: npm ci
- name: Install Playwright browsers
working-directory: e2e
run: npx playwright install --with-deps
# Start backend
- name: Start backend
working-directory: backend
env:
DATABASE_URL: postgresql://e2e:e2e@127.0.0.1:5432/e2e
run: |
nohup fastapi run --host 127.0.0.1 --port 8000 > backend.log 2>&1 &
for i in {1..60}; do
curl -fsS http://127.0.0.1:8000/healthz && break
sleep 2
done
# Start frontend (production server)
- name: Start frontend
working-directory: frontend
env:
NEXT_PUBLIC_API_BASE_URL: http://127.0.0.1:8000
run: |
nohup npm run start > frontend.log 2>&1 &
for i in {1..60}; do
curl -fsS http://127.0.0.1:3000/ && break
sleep 2
done
# Run tests
- name: Run Playwright tests
working-directory: e2e
env:
E2E_BASE_URL: http://127.0.0.1:3000
E2E_USER_EMAIL: e2e-user@example.com
E2E_USER_PASSWORD: password
run: npx playwright test
# Upload artifacts on failure
- name: Upload Playwright report
if: failure()
uses: actions/upload-artifact@v4
with:
name: playwright-report
path: e2e/playwright-report/
- name: Upload logs
if: failure()
uses: actions/upload-artifact@v4
with:
name: service-logs
path: |
backend/backend.log
frontend/frontend.log
NOTES (MUST):
- You MUST ensure the backend creates/migrates schema before tests (use your migration tool).
- You MUST ensure the E2E user exists (seed step or test-only setup flow).
- You MUST NOT run E2E against
next dev. Usebuild+startper Next.js docs. (Next.js) - You MUST NOT “fix flakiness” by increasing retries. Retries are supported, but they are not a cure. (Playwright)
15) Maintenance rules (so this stays trustworthy)¶
15.1 “E2E budget” rule (MUST)¶
- Tier 1 E2E tests MUST remain under ~7 flows unless you have a dedicated test owner.
- If E2E becomes flaky, developers will ignore it. That’s game over.
15.2 When a test fails (MUST triage in this order)¶
- Open Playwright HTML report (official reporter) (Playwright)
- Inspect trace (on-first-retry) (Playwright)
- Inspect video (retain-on-failure) (Playwright)
- Inspect backend/frontend logs
- Fix root cause (race conditions / selectors / data state / readiness)
16) Minimum acceptance criteria (your AI-agent MUST satisfy)¶
A PR is E2E-complete only if:
- Uses pinned versions (Section 1).
- Runs Next.js with
build+start(not dev). (Next.js) - Runs FastAPI with
fastapi run(production server path). (fastapi.tiangolo.com) - Uses
getByRole()(or equivalent resilient locator) for UI interactions. (Playwright) - Uses web-first assertions (
expect(...).toBeVisible()etc.). (Playwright) - Captures trace and video for failures in CI. (Playwright)
- Does not use arbitrary sleeps.