Das Kit enthält 17 E2E-Testdateien, die 50+ Szenarien in den Bereichen Authentifizierung, AI-Chat, Abrechnung, Dashboard, Pricing und Security abdecken. Alle Tests laufen gegen MSW-gemockte APIs — keine Datenbank, keine externen Services und keine Clerk-Authentifizierung in CI erforderlich.
Konfiguration
Die Playwright-Konfiguration definiert Browser, Timeouts, Retry-Strategie und den Test-Webserver:
playwright.config.ts
import { defineConfig, devices } from '@playwright/test'
import dotenv from 'dotenv'
import path from 'path'
// Load test environment variables (quietly)
dotenv.config({
path: path.resolve(process.cwd(), '.env.test'),
debug: false, // Suppress dotenv debug output that clutters CI logs
})
export default defineConfig({
testDir: './e2e',
// PHASE 2: Parallel execution re-enabled with robust safeguards
// - User IDs synchronized between auth system and seed data
// - Clerk API calls guarded by shouldUseClerk()
// - Fail-fast logic prevents user creation in test mode
fullyParallel: true,
// MONOREPO: Global setup is at repository root, not within boilerplate app
globalSetup: require.resolve('../../playwright/global-setup.ts'),
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
// PHASE 3: Single worker in CI for maximum stability and predictable execution
// Reduces resource contention and ensures consistent test timing
workers: (() => {
const ciWorkers = process.env.CI ? 1 : undefined
if (process.env.CI) {
console.log('[Playwright Config] CI mode detected')
console.log('[Playwright Config] Workers: 1 (optimized for stability)')
console.log(
'[Playwright Config] Test timeout: 90s (increased for slow dashboard prefetch)'
)
}
return ciWorkers
})(),
reporter: process.env.CI ? [['html'], ['github']] : 'html',
// PHASE 4: Unified timeouts for CI and local (environment parity)
// Dashboard prefetch takes 7-9s + page render time = 9-10s total
// Generous timeouts prevent false failures on slow routes
// Both CI and local now use same values for consistent behavior
timeout: 90 * 1000, // 90s global timeout (was: 30s local, 90s CI)
expect: {
timeout: 20 * 1000, // 20s for assertions (was: 10s local, 20s CI)
},
use: {
baseURL: process.env.BASE_URL || 'http://localhost:3000',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
// CRITICAL: Navigation timeout must exceed dashboard prefetch (7-9s) + render time
// Local dev server adds compilation overhead (~2s), requiring 30s total
// CI production server is faster but uses same timeout for consistency
navigationTimeout: 30 * 1000, // 30s navigation (was: 10s local, 30s CI)
actionTimeout: 15 * 1000, // 15s for actions (was: 5s local, 15s CI)
// Disable animations for faster tests
video: 'retain-on-failure',
launchOptions: {
args: ['--disable-blink-features=AutomationControlled'],
},
},
projects: [
// OPTIMIZATION: Primary browser for CI
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
// OPTIMIZATION: Other browsers only for main branch or local development
...(process.env.FULL_TEST_SUITE === 'true' || !process.env.CI
? [
{
name: 'firefox',
use: { ...devices['Desktop Firefox'] },
},
{
name: 'webkit',
use: { ...devices['Desktop Safari'] },
},
// Mobile Testing - only when explicitly needed
{
name: 'mobile-chrome',
use: { ...devices['Pixel 5'] },
},
{
name: 'mobile-safari',
use: { ...devices['iPhone 12'] },
},
]
: []),
],
webServer: {
// Use dedicated script to ensure clean test environment
command: './scripts/start-test-server.sh',
url: 'http://localhost:3000',
reuseExistingServer: false, // Always start fresh for tests
timeout: 2 * 60 * 1000, // 2 minutes to start
stdout: 'pipe',
stderr: 'pipe',
},
})
Wichtige Designentscheidungen:
- Einheitliche Timeouts — CI und lokal verwenden identische Werte (90 Sek. global, 20 Sek. expect, 30 Sek. Navigation), um Überraschungen à la „funktioniert lokal, schlägt in CI fehl" zu vermeiden
- Navigations-Timeout von 30 Sek. — Dashboard-Seiten laden beim Start Daten vor (7–9 Sek.), plus Kompilierungs-Overhead während der Entwicklung (~2 Sek.). Ein zu knapper Timeout hier verursacht falsche Fehler
- Einzelner Worker in CI — Reduziert Ressourcenkonflikte und sorgt für vorhersehbare Ausführungsreihenfolge
fullyParallel: truelokal für schnelles Feedback, mit korrekter User-ID-Synchronisation zwischen Auth-System und Seed-Daten- Frischer Server pro Durchlauf —
reuseExistingServer: falsestellt sicher, dass Tests mit einem sauberen Zustand beginnen
Dashboard-Seiten benötigen 7–9 Sek. zum Laden aufgrund des Datenprefetchings. Setze Navigations-Timeouts nicht unter 30 Sek. und Expect-Timeouts nicht unter 20 Sek. — diese Werte berücksichtigen den Prefetch-Zyklus plus Render-Zeit.
Tests ausführen
| Befehl | Beschreibung |
|---|---|
pnpm test:e2e | Alle E2E-Tests ausführen (Playwright, nur Chromium in CI) |
pnpm test:e2e:smoke | Nur Smoke-Tests — schnelles Feedback während der Entwicklung |
pnpm test:e2e:full | Vollständige Suite mit FULL_TEST_SUITE=true (alle Browser) |
pnpm test:e2e:ui | Visueller UI-Modus — Tests interaktiv im Browser ausführen |
pnpm test:e2e:debug | Debug-Modus — öffnet Playwright Inspector mit Schritt-für-Schritt-Ausführung |
pnpm test:e2e:prod | Production-Bundle bauen, dann Smoke-Tests ausführen |
pnpm test:e2e:prod:full | Production-Bundle bauen, dann vollständige Suite ausführen |
pnpm test:e2e:smoke führt nur die kritischen Pfad-Tests auf Chromium aus. Dies dauert unter 2 Minuten und erkennt die meisten Regressionen. Spare die vollständige Suite für die Validierung vor dem Merge auf.Verzeichnisstruktur der Tests
apps/boilerplate/e2e/
smoke/ # Schnelle Tests für kritische Pfade
auth.smoke.spec.ts # Login, Registrierung, Logout
ai-chat.smoke.spec.ts # Grundlegender AI-Chat-Ablauf
ai-support.smoke.spec.ts # Support-Bot-Interaktion
billing.smoke.spec.ts # Abrechnungsseite lädt
dashboard.smoke.spec.ts # Dashboard-Rendering
pricing-checkout.smoke.spec.ts # Pricing-Seite und Checkout
public.smoke.spec.ts # Öffentliche Seiten (Home, About)
showcase.smoke.spec.ts # Feature-Showcase
demo/
demo-flow.spec.ts # Demo-Modus End-to-End-Ablauf
examples/
test-data-usage.spec.ts # Beispiel-Patterns für neue Tests
helpers/
auth.helper.ts # Login/Registrierung/Logout-Utilities
test-config.ts # Selektoren, Timeouts, Umgebungserkennung
ai-chat.spec.ts # Vollständige AI-Chat-Test-Suite
ai-support.spec.ts # Vollständige AI-Support-Test-Suite
auth-flow.spec.ts # Vollständige Authentifizierungsabläufe
credit-display.spec.ts # Credit-UI und Anzeigelogik
security.spec.ts # Validierung von Security-Features
subscription-checkout.spec.ts # Vollständiger Subscription- und Checkout-Ablauf
smoke-tests.spec.ts # Legacy-Smoke-Tests
Smoke-Tests vs. vollständige Tests
| Aspekt | Smoke-Tests | Vollständige Tests |
|---|---|---|
| Ort | apps/boilerplate/e2e/smoke/*.smoke.spec.ts | apps/boilerplate/e2e/*.spec.ts |
| Umfang | Nur kritische Pfade (Login, Navigation, grundlegende Interaktionen) | Tiefes Feature-Testing (Grenzfälle, Fehlerbehandlung, Responsiveness) |
| Browser | Nur Chromium | Chromium + Firefox + WebKit + Mobile (mit FULL_TEST_SUITE) |
| Dauer | ~1–2 Minuten | ~10–15 Minuten (alle Browser) |
| Wann ausführen | Bei jeder Code-Änderung, jedem PR | Vor dem Merge in main, Release-Validierung |
| CI-Trigger | Jeder Push | FULL_TEST_SUITE=true oder main-Branch |
Die Browser-Matrix wird durch die Umgebungsvariable
FULL_TEST_SUITE gesteuert:- Standard (CI): Nur Chromium — schnelles Feedback für jeden PR
FULL_TEST_SUITE=true: Fügt Firefox, WebKit, Mobile Chrome (Pixel 5) und Mobile Safari (iPhone 12) hinzu- Lokale Entwicklung: Standardmäßig alle Browser für gründliches Testen aktiviert
Testumgebung
Umgebungsvariablen
E2E-Tests laden
apps/boilerplate/.env.test, das folgendes konfiguriert:NODE_ENV=test— Aktiviert Test-Modus-Verhalten in der gesamten AppNEXT_PUBLIC_MSW_ENABLED=true— Aktiviert MSW im Browser für API-MockingNEXT_PUBLIC_CLERK_ENABLED=false— Deaktiviert Clerk-Authentifizierung (alle Auth-Keys geleert)NEXT_PUBLIC_PAYMENTS_ENABLED=false— Deaktiviert Lemon Squeezy-ZahlungsverarbeitungNEXT_PUBLIC_EMAIL_ENABLED=false— Deaktiviert den Resend-E-Mail-Service- Alle Service-API-Keys auf Dummy-Werte gesetzt — MSW fängt jeden externen Aufruf ab
E2E-Tests müssen alle externen Services deaktivieren — nicht nur Clerk. Ohne
PAYMENTS_ENABLED=false könnten Tests echte Lemon Squeezy API-Aufrufe versuchen. Ohne EMAIL_ENABLED=false könnten Tests echte Resend API-Aufrufe versuchen. Die Datei apps/boilerplate/.env.test konfiguriert standardmäßig alle drei.Globales Setup
Die globale Setup-Datei im Monorepo-Root startet den MSW-Server, bevor Tests ausgeführt werden:
typescript
// playwright/global-setup.ts
export default async function globalSetup() {
// Testumgebung setzen
process.env.NODE_ENV = 'test'
process.env.NEXT_PUBLIC_CLERK_ENABLED = 'false'
// MSW-Server für API-Mocking starten
const { server } = await import('./msw-setup')
server.listen({ onUnhandledRequest: 'warn' })
// Teardown-Funktion zurückgeben
return () => server.close()
}
Das bedeutet: Für E2E-Tests werden weder Datenbank noch externe Services benötigt — MSW ersetzt alles.
Test-Server-Start
Playwright startet für jeden Testdurchlauf einen frischen Next.js-Server über
scripts/start-test-server.sh. Der Server hat ein 2-Minuten-Startup-Timeout, um Cold Builds zu berücksichtigen.Test-Helpers & Fixtures
Testkonfiguration
e2e/helpers/test-config.ts
/**
* Test Configuration Utilities
* Provides environment-specific test configuration
*/
import { isTestEnvironment } from '@/lib/test-utils'
/**
* Determines if we're running in CI environment with Clerk bypass
* Uses the centralized test-utils for consistency
*/
export function isClerkBypassed(): boolean {
return isTestEnvironment()
}
/**
* Skip test helper for Clerk-specific tests
*/
export function skipIfClerkBypassed(testTitle: string) {
return isClerkBypassed()
? `${testTitle} (SKIPPED: Clerk bypassed in CI)`
: testTitle
}
/**
* Test environment configuration
*/
export const testConfig = {
// Longer timeouts for CI environment
waitTimeout: process.env.CI ? 15000 : 10000,
expectTimeout: process.env.CI ? 20000 : 15000,
// Environment flags
isCI: Boolean(process.env.CI),
isTestEnv: process.env.NODE_ENV === 'test',
clerkBypassed: isClerkBypassed(),
// Test selectors
selectors: {
signInButton:
'[data-testid="signin-button"], nav a[href="/login"], nav button:has-text("Sign in")',
getStartedButton:
'[data-testid="get-started-button"], nav a[href="/register"], nav button:has-text("Get started")',
mobileMenuButton:
'[data-testid="mobile-menu-button"], button:has(.lucide-menu), button:has(svg)',
mobileSignInButton:
'[data-testid="mobile-signin-button"], a[href="/login"]',
mobileGetStartedButton:
'[data-testid="mobile-get-started-button"], a[href="/register"]',
},
}
Das
testConfig-Objekt bietet:- Umgebungsabhängige Timeouts — Längere Wartezeiten in CI, wo Ressourcen begrenzt sind
- Flexible Selektoren — Jeder Selektor ist eine CSS-Selektorliste, die mehrere mögliche DOM-Strukturen abgleicht. Dies berücksichtigt Clerks dynamische UI-Komponenten und Test-ID-Variationen
- Clerk-Bypass-Erkennung —
isClerkBypassed()gibt in Testumgebungentruezurück, sodass Tests echte Authentifizierungsabläufe überspringen können
Authentifizierungs-Helpers
Die Datei
auth.helper.ts stellt Utilities für Authentifizierungsabläufe in E2E-Tests bereit:| Helper | Zweck |
|---|---|
waitForClerkAuth() | Wartet, bis Clerk-Komponenten fertig geladen sind |
registerWithClerk() | Registriert einen neuen Benutzer über das Clerk SignUp-Formular |
loginWithClerk() | Meldet einen bestehenden Benutzer über das Clerk SignIn-Formular an |
logoutUser() | Meldet den aktuellen Benutzer ab |
isLoggedIn() | Prüft, ob ein Benutzer aktuell authentifiziert ist |
createTestUser() | Generiert einen eindeutigen Testbenutzer mit zufälliger E-Mail |
getSubscribedTestUser() | Gibt einen vorkonfigurierten Testbenutzer mit aktiver Subscription zurück |
Im Test-Modus (Clerk umgangen) verwenden diese Helpers vereinfachte Abläufe, die nicht mit echten Clerk-Formularen interagieren. Das macht Tests schneller und zuverlässiger in CI.
E2E-Tests schreiben
Einfacher Navigationstest
typescript
import { test, expect } from '@playwright/test'
test('homepage loads and shows hero section', async ({ page }) => {
await page.goto('/')
await expect(page.locator('h1')).toBeVisible()
await expect(page.locator('[data-testid="hero-section"]')).toBeVisible()
})
Responsiveness testen
Desktop- und Mobile-Viewports in derselben Datei testen:
typescript
import { test, expect } from '@playwright/test'
import { testConfig } from './helpers/test-config'
test('mobile menu opens and closes', async ({ page }) => {
// Mobile Viewport setzen
await page.setViewportSize({ width: 375, height: 667 })
await page.goto('/')
// Mobilmenü öffnen
const menuButton = page.locator(testConfig.selectors.mobileMenuButton)
await menuButton.click()
// Prüfen, ob Menüeinträge sichtbar sind
await expect(
page.locator(testConfig.selectors.mobileSignInButton)
).toBeVisible()
// Schließen durch Klick außerhalb
await page.locator('body').click()
await expect(
page.locator(testConfig.selectors.mobileSignInButton)
).not.toBeVisible()
})
Routen-Interception
MSWs Service Worker fängt
fetch()-Anfragen, die der Next.js-Server innerhalb von Playwrights Browser-Kontext macht, nicht zuverlässig ab. Verwende für E2E-spezifische Response-Überschreibungen immer Playwrights natives page.route() — es interceptiert auf Netzwerkebene, bevor die Anfrage den Browser verlässt, und ist damit vollständig zuverlässig.API-Responses für bestimmte Testszenarien mit Playwrights Routen-Interception überschreiben:
typescript
test('shows error state when API fails', async ({ page }) => {
// Dashboard-API abfangen und Fehler zurückgeben
await page.route('/api/dashboard', (route) => {
route.fulfill({
status: 500,
contentType: 'application/json',
body: JSON.stringify({ error: 'Internal Server Error' }),
})
})
await page.goto('/dashboard')
await expect(page.getByText(/something went wrong/i)).toBeVisible()
})
Barrierefreiheit testen
Grundlegende Barrierefreiheitsanforderungen in E2E-Tests prüfen:
typescript
test('pricing page meets accessibility requirements', async ({ page }) => {
await page.goto('/pricing')
// Prüfen, ob alle Bilder Alt-Text haben
const images = page.locator('img')
for (const image of await images.all()) {
await expect(image).toHaveAttribute('alt')
}
// Überschriften-Hierarchie prüfen
const h1Count = await page.locator('h1').count()
expect(h1Count).toBe(1)
})
CI/CD-Integration
CI-Konfiguration
E2E-Tests laufen in CI mit optimierten Einstellungen:
| Einstellung | Wert | Grund |
|---|---|---|
| Worker | 1 | Einzelner Worker verhindert Ressourcenkonflikte |
| Retries | 2 | Retries fangen flüchtige Fehler ab (Netzwerk, Timing) |
| Screenshot | Bei Fehler | Erfasst visuellen Zustand zum Debuggen |
| Trace | Beim ersten Retry | Vollständige Ausführungs-Trace für flaky Test-Analyse |
| Video | Bei Fehler behalten | Video-Playback für komplexes Interaktions-Debugging |
| Reporter | HTML + GitHub | HTML für lokale Überprüfung, GitHub für PR-Annotationen |
Timeout-Strategie
| Timeout | Wert | Was abgedeckt wird |
|---|---|---|
| Global | 90 Sek. | Gesamtzeit pro Test (inkl. aller Navigationen und Assertions) |
| Expect | 20 Sek. | Einzelner Assertion-Timeout (z.B. „warte bis Element sichtbar") |
| Navigation | 30 Sek. | Seitennavigation inkl. Prefetch und Render |
| Aktion | 15 Sek. | Benutzeraktionen (Klick, Tippen, Auswählen) |
| Web-Server | 120 Sek. | Next.js-Build- und Server-Startzeit |
Diese Werte sind bewusst großzügig. Schnelle Tests werden weit unter diesen Grenzen abschließen, während langsame Operationen (Dashboard-Prefetch, Cold Builds) keine falschen Fehler erzeugen.
Debugging
Playwright Inspector
bash
pnpm test:e2e:debug
Öffnet den Playwright Inspector mit Schritt-für-Schritt-Ausführung. Du kannst:
- An jeder Zeile pausieren und die Seite untersuchen
- Den Selektor-Picker verwenden, um Elemente zu finden
- Aktionen einzeln durchgehen
- Konsolen- und Netzwerk-Logs ansehen
Visueller UI-Modus
bash
pnpm test:e2e:ui
Öffnet eine browserbasierte Oberfläche mit:
- Allen Tests mit Bestanden/Nicht-Bestanden-Status
- Live-Seitenvorschau während der Testausführung
- Zeitstrahl der Aktionen und Assertions
- Screenshots bei jedem Schritt
CI-Fehler-Artefakte
Wenn Tests in CI fehlschlagen, generiert Playwright:
- Screenshots — Werden automatisch bei Fehlern erfasst
- Traces — Werden beim ersten Retry aufgezeichnet, ansehbar mit
npx playwright show-trace trace.zip - Videos — Für fehlgeschlagene Tests aufbewahrt, zeigen die gesamte Testausführung
- HTML-Bericht — Nach jedem CI-Durchlauf generiert unter
playwright-report/index.html
Um die vollständige Cross-Browser-Suite lokal auszuführen, setze
FULL_TEST_SUITE=true pnpm test:e2e. Dies fügt Firefox, WebKit und mobile Browser (Pixel 5, iPhone 12) zur Testmatrix hinzu.Wichtige Dateien
| Datei | Zweck |
|---|---|
apps/boilerplate/playwright.config.ts | Browser-Projekte, Timeouts, Retries, Webserver-Konfiguration |
apps/boilerplate/e2e/helpers/test-config.ts | Selektoren, Timeouts, Umgebungserkennung |
apps/boilerplate/e2e/helpers/auth.helper.ts | Clerk-Authentifizierungs-Utilities für E2E |
apps/boilerplate/e2e/smoke/ | Kritische Pfad-Smoke-Tests (schnell, nur Chromium) |
apps/boilerplate/scripts/start-test-server.sh | Skript für sauberen Test-Server-Start |
apps/boilerplate/.env.test | Test-Umgebungsvariablen (MSW aktiviert, Clerk deaktiviert) |