Next.js E2E-Tests: Praxisleitfaden für automatisierte Tests mit Playwright

Auf dem Bug-Ticket blinkt ein rotes „Dringend“ – der Payment-Flow ist wieder kaputt. In Staging alles grün, in Production nicht.
Letzte Woche vor dem Release: über dreißig Seiten manuell geklickt, ein Dutzend Formulare ausgefüllt, drei Browser durch – und trotzdem der Button übersehen, der nur auf Mobile ganz unten sichtbar wird. Die PM-Nachricht: „Nutzer sagen, Gutscheine gehen nicht.“
Manuelles Testen hält nicht mehr mit.
Ich habe Cypress, Selenium, Puppeteer und Playwright verglichen – am Ende Playwright: Multi-Browser, Setup einfacher als bei Cypress. In der ersten Woche fünf Bugs, die manuell nie auffielen – Firefox-only-CSS, Race Conditions bei async APIs.
Am Anfang viele Stolpersteine: Config mehrfach überarbeitet, Tests zweimal neu geschrieben. Heute läuft alles durch, CI/CD vollautomatisch, jeder Commit triggert Tests – Production-Bugs deutlich seltener.
Warum Playwright (vs. Cypress)
Cypress kennt jeder: einfaches Setup, gute Docs, aktive Community. Warum trotzdem Playwright?
Drei Punkte haben mich umgestimmt:
Schwache Multi-Browser-Unterstützung. Firefox und Safari bei Cypress waren lange halbgar – praktisch läuft vieles nur in Chromium. Einmal Payment-Seite in Chrome perfekt, in Safari White Screen – wegen einer CSS-Eigenschaft, die Safari nicht mag. Playwright unterstützt Chromium, Firefox und WebKit nativ; eine Suite deckt die gängigen Browser ab.
Geschwindigkeit. Playwright parallelisiert stark. Cypress oft seriell – 50 Cases in über zehn Minuten; Playwright mit 8 Workern in etwa fünf. In CI zählt jede Minute.
API-Design. Von Cypress zu Playwright fühlt sich async/await zuerst fremd an – Cypress’ Chaining ist angenehm. Nach einer Weile passt es besser zu modernem JavaScript und zum Stil von Next.js Server Components.
Cypress ist nicht schlecht. Nur Chrome, wenig Test-Erfahrung im Team → Cypress ist oft der schnellere Einstieg. Debugging und Time Travel sind exzellent.
Für mich passte Playwright besser:
- Cross-Browser nötig
- Next.js/React-Erfahrung,
async/awaitvertraut - schnelles Feedback in CI
- API Routes und SSR-Seiten testen
Kein absolutes „besser/schlechter“ – kontextabhängig. Kleines Projekt, wenig Test-Kultur: Cypress zum schnellen Start. Größerer Stack, langfristige Automation: Playwright.
Next.js + Playwright: Konfiguration in der Praxis
Installation in drei Schritten:
npm init playwright@latest
# oder mit pnpm
pnpm create playwright
Beim Setup empfehle ich:
- TypeScript? Yes (Typen sparen Fehler)
- Testverzeichnis? tests (Standard reicht)
- GitHub Actions? Yes (für CI/CD)
Danach typischerweise:
your-nextjs-project/
├── tests/ # Testfälle
│ └── example.spec.ts
├── playwright.config.ts # Playwright-Konfiguration
└── .github/
└── workflows/
└── playwright.yml # CI-Konfiguration
Konfiguration: typische Fallstricke
Die Standard-playwright.config.ts passt für normale Web-Apps – bei Next.js sollten Sie anpassen. Nach einem halben Jahr ist das meine stabile Variante:
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
// Testverzeichnis
testDir: './tests',
// Globales Timeout: 30 Sekunden pro Test
timeout: 30 * 1000,
// Expect-Timeout: 5 Sekunden für Element-Suche
expect: {
timeout: 5000,
},
// Retries bei Fehlschlag (in CI empfohlen)
retries: process.env.CI ? 2 : 0,
// Parallele Worker (8 Kerne → 4 lokal)
workers: process.env.CI ? 2 : 4,
// Reporter
reporter: [
['html'], // HTML-Report
['list'], // Terminal-Liste
process.env.CI ? ['github'] : ['list'], // GitHub-Format in CI
],
// Next.js Dev-Server starten
webServer: {
command: 'npm run dev',
port: 3000,
timeout: 120 * 1000, // Cold Start: genug Zeit für Kompilierung
reuseExistingServer: !process.env.CI, // Lokal Server wiederverwenden
},
// Projekte (Multi-Browser)
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
{
name: 'firefox',
use: { ...devices['Desktop Firefox'] },
},
{
name: 'webkit',
use: { ...devices['Desktop Safari'] },
},
// Mobile (optional)
{
name: 'Mobile Chrome',
use: { ...devices['Pixel 5'] },
},
],
// Globale Einstellungen
use: {
baseURL: 'http://localhost:3000',
trace: 'on-first-retry', // Trace bei Retry für Debugging
screenshot: 'only-on-failure', // Screenshot bei Fehler
video: 'retain-on-failure', // Video bei Fehler behalten
},
});
Häufige Stolpersteine
-
webServer.timeoutgroßzügig setzen. Anfangs 30 Sekunden – Next.js Cold Start kompiliert, Timeout. 120 Sekunden sind stabil. -
Lokal
reuseExistingServer: true. Sonst startet Next.js bei jedem Testlauf neu – nervt. -
Worker-Anzahl begrenzen. Einmal so viele Worker wie CPU-Kerne – Rechner hing. Jetzt etwa die Hälfte der Kerne: schnell und stabil.
-
Mobile Tests optional. Bei responsivem Next.js fängt Mobile Chrome mobile Bugs – verdoppelt aber oft die Laufzeit.
Config steht? Beispieltests starten:
npx playwright test
Grünes passed → Umgebung passt. Dann echte Testfälle schreiben.
Best Practices für UI-Tests (Page Object Model)
Am Anfang alles in einer Datei – Login-Test über 100 Zeilen, page.locator, page.fill, page.click überall. Selektor geändert → zwölf Dateien anfassen. Erschöpfend.
Page Object Model (POM) macht den Code deutlich lesbarer: Seitenaktionen in eine Klasse, Tests rufen nur Methoden auf.
Ohne POM (Anti-Pattern)
// tests/login.spec.ts
import { test, expect } from '@playwright/test';
test('用户登录', async ({ page }) => {
await page.goto('/login');
// 直接操作元素,代码重复
await page.locator('input[name="email"]').fill('[email protected]');
await page.locator('input[name="password"]').fill('password123');
await page.locator('button[type="submit"]').click();
await expect(page.locator('h1')).toContainText('Dashboard');
});
test('登录失败提示', async ({ page }) => {
await page.goto('/login');
// 又来一遍同样的操作...
await page.locator('input[name="email"]').fill('[email protected]');
await page.locator('input[name="password"]').fill('wrongpass');
await page.locator('button[type="submit"]').click();
await expect(page.locator('.error')).toBeVisible();
});
Problem: ändert Design input[name="email"] zu input[id="email"], müssen alle Tests mit.
Mit POM (empfohlen)
Page Object anlegen:
// tests/pages/LoginPage.ts
import { Page, Locator } from '@playwright/test';
export class LoginPage {
readonly page: Page;
readonly emailInput: Locator;
readonly passwordInput: Locator;
readonly submitButton: Locator;
readonly errorMessage: Locator;
readonly dashboardTitle: Locator;
constructor(page: Page) {
this.page = page;
this.emailInput = page.locator('input[name="email"]');
this.passwordInput = page.locator('input[name="password"]');
this.submitButton = page.locator('button[type="submit"]');
this.errorMessage = page.locator('.error');
this.dashboardTitle = page.locator('h1');
}
// 封装登录操作
async login(email: string, password: string) {
await this.emailInput.fill(email);
await this.passwordInput.fill(password);
await this.submitButton.click();
}
// 封装导航操作
async goto() {
await this.page.goto('/login');
}
// 封装验证逻辑
async expectLoginSuccess() {
await this.dashboardTitle.waitFor();
await expect(this.dashboardTitle).toContainText('Dashboard');
}
async expectLoginError() {
await expect(this.errorMessage).toBeVisible();
}
}
Tests werden kurz:
// tests/login.spec.ts
import { test } from '@playwright/test';
import { LoginPage } from './pages/LoginPage';
test('用户登录', async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.goto();
await loginPage.login('[email protected]', 'password123');
await loginPage.expectLoginSuccess();
});
test('登录失败提示', async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.goto();
await loginPage.login('[email protected]', 'wrongpass');
await loginPage.expectLoginError();
});
Selektor-Änderung nur in LoginPage.ts. Tests lesen sich wie Prosa – auch für Neue im Team.
Verzeichnisstruktur im echten Projekt
tests/
├── pages/ # Page Objects
│ ├── LoginPage.ts
│ ├── DashboardPage.ts
│ └── CheckoutPage.ts
├── fixtures/ # Testdaten und Helfer
│ └── testData.ts
├── auth.spec.ts # Auth-Tests
├── checkout.spec.ts # Checkout-Flow
└── dashboard.spec.ts # Dashboard-Tests
Erfahrungen und Tipps
-
Nicht over-engineeren. Nicht jede Seite braucht ein Page Object. Einmal getestet → direkt im Test reicht.
-
Semantische Methodennamen.
async fillLoginForm()schlägtasync fillForm()– Ihr zukünftiges Ich dankt Ihnen. -
Warte-Logik ins Page Object. Playwright wartet smart, manchmal braucht es
waitFor()– dort kapseln, Tests bleiben sauber. -
Testdaten zentral. Credentials in
fixtures/testData.ts:
// tests/fixtures/testData.ts
export const testUsers = {
validUser: {
email: '[email protected]',
password: 'password123'
},
invalidUser: {
email: '[email protected]',
password: 'wrongpass'
}
};
Verwendung:
import { testUsers } from './fixtures/testData';
await loginPage.login(testUsers.validUser.email, testUsers.validUser.password);
Wartungskosten sinken spürbar. Workflow: Page Object → wenige Testzeilen → fertig.
E2E-Tests für API Routes
Next.js API Routes gehören zur App – also testen. Früher Postman per Hand, heute Playwright mit request, oft ohne Browser.
Basis-API-Tests
Beispiel: User-Liste abrufen:
// tests/api/users.spec.ts
import { test, expect } from '@playwright/test';
test.describe('用户 API 测试', () => {
test('GET /api/users - 获取用户列表', async ({ request }) => {
const response = await request.get('/api/users');
// 验证状态码
expect(response.status()).toBe(200);
// 验证响应格式
const users = await response.json();
expect(Array.isArray(users)).toBeTruthy();
expect(users.length).toBeGreaterThan(0);
// 验证数据结构
expect(users[0]).toHaveProperty('id');
expect(users[0]).toHaveProperty('email');
expect(users[0]).toHaveProperty('name');
});
test('POST /api/users - 创建用户', async ({ request }) => {
const newUser = {
email: '[email protected]',
name: 'Test User',
password: 'password123'
};
const response = await request.post('/api/users', {
data: newUser
});
expect(response.status()).toBe(201);
const createdUser = await response.json();
expect(createdUser.email).toBe(newUser.email);
expect(createdUser).not.toHaveProperty('password'); // 密码不应该返回
});
test('POST /api/users - 邮箱重复应返回错误', async ({ request }) => {
const duplicateUser = {
email: '[email protected]',
name: 'Duplicate User',
password: 'password123'
};
const response = await request.post('/api/users', {
data: duplicateUser
});
expect(response.status()).toBe(400);
const error = await response.json();
expect(error.message).toContain('邮箱已存在');
});
});
API-Tests mit Authentifizierung
Viele Endpoints brauchen Login – Token holen, Header setzen:
// tests/api/auth.spec.ts
import { test, expect } from '@playwright/test';
let authToken: string;
test.describe('需要认证的 API', () => {
// 所有测试前先登录获取 token
test.beforeAll(async ({ request }) => {
const response = await request.post('/api/auth/login', {
data: {
email: '[email protected]',
password: 'password123'
}
});
const { token } = await response.json();
authToken = token;
});
test('GET /api/profile - 获取用户资料', async ({ request }) => {
const response = await request.get('/api/profile', {
headers: {
'Authorization': `Bearer ${authToken}`
}
});
expect(response.status()).toBe(200);
const profile = await response.json();
expect(profile.email).toBe('[email protected]');
});
test('未登录访问应返回 401', async ({ request }) => {
const response = await request.get('/api/profile');
expect(response.status()).toBe(401);
});
});
Gemischte Tests: UI + API
Stark kombiniert – z. B. Artikel veröffentlichen:
// tests/posts.spec.ts
import { test, expect } from '@playwright/test';
test('发布文章完整流程', async ({ page, request }) => {
// 1. 先通过页面登录
await page.goto('/login');
await page.fill('input[name="email"]', '[email protected]');
await page.fill('input[name="password"]', 'password123');
await page.click('button[type="submit"]');
// 2. 进入文章编辑页
await page.goto('/posts/new');
await page.fill('input[name="title"]', '测试文章标题');
await page.fill('textarea[name="content"]', '这是测试内容');
await page.click('button:has-text("发布")');
// 3. 等待跳转到文章详情页
await page.waitForURL(/\/posts\/\d+/);
// 4. 通过 API 验证文章确实创建成功
const url = page.url();
const postId = url.split('/').pop();
const response = await request.get(`/api/posts/${postId}`);
expect(response.status()).toBe(200);
const post = await response.json();
expect(post.title).toBe('测试文章标题');
expect(post.content).toBe('这是测试内容');
expect(post.status).toBe('published');
});
Vorteil: UI und Backend zusammen. Einmal zeigte die Seite „Erfolg“, in der DB stand draft – Status-Update war falsch.
Praxis-Tipps
-
Grenzfälle testen. Fehlende Parameter, falsche Typen, fehlende Rechte – nicht nur Happy Path.
-
Testdaten aufräumen. API schreibt in die DB –
afterAlloder dedizierte Test-DB:
test.afterAll(async ({ request }) => {
await request.delete('/api/test/cleanup');
});
-
Externe Dienste mocken. Payment/SMS in Tests nicht live aufrufen.
-
Latenz prüfen. Optional Timing asserten:
const start = Date.now();
await request.get('/api/users');
const duration = Date.now() - start;
expect(duration).toBeLessThan(1000); // 接口响应应该在 1 秒内
UI + API decken oft ~90 % ab – den Rest fangen Unit-Tests.
GitHub Actions CI/CD
Tests geschrieben → CI anbinden. Jeder Push läuft automatisch – wie oft war man sicher, bis CI rot wurde.
npm init playwright legt oft schon .github/workflows/playwright.yml an – die Basis reicht selten produktiv.
Basis-CI
Standard-Workflow:
name: Playwright Tests
on:
push:
branches: [ main, master ]
pull_request:
branches: [ main, master ]
jobs:
test:
timeout-minutes: 60
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18
- name: Install dependencies
run: npm ci
- name: Install Playwright Browsers
run: npx playwright install --with-deps
- name: Run Playwright tests
run: npx playwright test
- uses: actions/upload-artifact@v3
if: always()
with:
name: playwright-report
path: playwright-report/
retention-days: 30
Schwächen: Browser jedes Mal neu, keine Test-DB, Report nur als Download.
Produktionsnahe Konfiguration
Mit Cache, DB und Report-Deploy:
name: E2E Tests
on:
push:
branches: [ main, dev ]
pull_request:
branches: [ main ]
jobs:
test:
timeout-minutes: 60
runs-on: ubuntu-latest
services:
# 测试数据库(PostgreSQL)
postgres:
image: postgres:15
env:
POSTGRES_USER: test
POSTGRES_PASSWORD: test
POSTGRES_DB: testdb
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 5432:5432
env:
DATABASE_URL: postgresql://test:test@localhost:5432/testdb
NODE_ENV: test
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Cache Playwright browsers
uses: actions/cache@v3
with:
path: ~/.cache/ms-playwright
key: ${{ runner.os }}-playwright-${{ hashFiles('**/package-lock.json') }}
- name: Install Playwright Browsers
run: npx playwright install --with-deps chromium
- name: Run database migrations
run: npm run db:migrate
- name: Run Playwright tests
run: npx playwright test
- name: Upload test results
if: always()
uses: actions/upload-artifact@v4
with:
name: playwright-report
path: playwright-report/
retention-days: 30
# 如果是主分支,把报告部署到 GitHub Pages
- name: Deploy report to GitHub Pages
if: always() && github.ref == 'refs/heads/main'
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./playwright-report
Wichtige Punkte
-
Service-Container: PostgreSQL für API-Tests – bei MySQL
mysql:8analog. -
Browser-Cache:
actions/cachefür~/.cache/ms-playwright. In CI oft nur Chromium – drei Browser sind langsam. -
Migrationen vor Tests: z. B. in
package.json:
{
"scripts": {
"db:migrate": "prisma migrate deploy"
}
}
- Report auf GitHub Pages: Team sieht Ergebnisse im Browser.
Umgebungsvariablen
Secrets unter Settings → Secrets:
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
NEXTAUTH_SECRET: ${{ secrets.NEXTAUTH_SECRET }}
STRIPE_SECRET_KEY: ${{ secrets.STRIPE_TEST_KEY }}
Debugging bei CI-Fehlern
-
Trace:
trace: 'on-first-retry'–npx playwright show-trace trace.zipreplayt den Lauf. -
Screenshots/Videos bei Failure.
-
Lokal CI simulieren mit
act:
# 安装 act
brew install act # macOS
# 或
choco install act # Windows
# 运行 workflow
act -j test
Fallstricke
-
Timeout sinnvoll. 30 Minuten und ein hängender Test verschwenden Runner-Zeit – 60 Minuten mit Monitoring langsamer Tests.
-
Wenige Worker in CI. Zwei Worker reichen oft.
-
Retries begrenzen.
retries: 2für Flakiness – echte Bugs löst Wiederholen nicht.
Mit CI steigt die Qualität spürbar: PRs brauchen grün vor Merge.
Abdeckung und Reports
Nach dem Lauf zählt das Ergebnis. Playwright-HTML-Reports sind übersichtlich.
HTML-Report
npx playwright show-report
Zeigt Status, Dauer, Screenshots, Videos, Trace. Trace Viewer ist Gold: Schritte, Requests, DOM, Console – präzise Fehlerstelle.
Funktionsabdeckung
E2E misst Funktions-, nicht Code-Coverage. Checkliste im Repo:
## 测试覆盖清单
### 用户认证
- [x] 登录(正常流程)
- [x] 登录失败(错误密码)
- [x] 注册
- [x] 找回密码
- [ ] 第三方登录(Google)
### 商品管理
- [x] 添加商品
- [x] 编辑商品
- [x] 删除商品
- [ ] 批量导入
### 订单流程
- [x] 加购物车
- [x] 结算
- [x] 支付(模拟环境)
- [ ] 退款流程
In tests/README.md pflegen – Lücken auf einen Blick.
Code-Coverage (optional)
Mit Istanbul/v8 möglich – selten nötig für E2E:
// playwright.config.ts
export default defineConfig({
use: {
// 启用代码覆盖率
trace: 'on',
// 注入覆盖率收集代码
contextOptions: {
recordVideo: {
dir: 'test-results/videos'
}
}
}
});
Unit-Tests decken Logik ab; E2E prüft, ob Flows für Nutzer funktionieren.
Custom Reporter
Slack/DingTalk-Benachrichtigung:
// my-reporter.ts
import { Reporter } from '@playwright/test/reporter';
class SlackReporter implements Reporter {
onEnd(result) {
const passed = result.suites.filter(s => s.ok).length;
const failed = result.suites.length - passed;
// 发送到 Slack
fetch('https://hooks.slack.com/services/YOUR_WEBHOOK', {
method: 'POST',
body: JSON.stringify({
text: `测试完成:${passed} 通过,${failed} 失败`
})
});
}
}
export default SlackReporter;
Aktivieren:
// playwright.config.ts
export default defineConfig({
reporter: [
['html'],
['./my-reporter.ts']
]
});
Trends
Playwright Test Runner visualisiert Traces. Pragmatisch: Pass-Rate und Laufzeit in CSV → Google Sheets.
Mein Workflow
-
Lokal: Terminal; bei Failure
--debug:npx playwright test --debug -
PR-Review: CI-HTML-Report, langsame Tests prüfen.
-
Wöchentlich: Coverage-Checkliste aktualisieren.
Reports sind mehr als Zahlen – sie verbessern Prozess und Qualität.
Fazit
Vor einem halben Jahr manuell bis drei Uhr nachts – heute deutlich entspannter.
Playwright + Next.js wirkt wartungsarm: einmal sauber konfiguriert, CI auf jedem Commit, Releases mit mehr Vertrauen. Weniger Production-Bugs, weniger PM-Panik.
Wenn Sie noch manuell testen:
- Mit Kernflows starten – Login, Payment zuerst, nicht alles auf einmal.
- Page Object Model nutzen – kurzfristig mehr Aufwand, langfristig Gewinn.
- CI/CD – Automation ohne automatischen Lauf lohnt wenig.
- Keine 100 %-Jagd – Kern sichern, Rest pragmatisch.
E2E ist auch Team-Kultur: gemeinsame Qualitätslatte, Tests als lebendige Dokumentation, planbare Releases.
Ich bin oft um halb sechs fertig – Zeit fürs Fitnessstudio, die Jahreskarte war fast verfallen.
Fangen Sie mit Tests an – Ihr späteres Ich wird dankbar sein.
FAQ
Playwright oder Cypress – was soll ich wählen?
• Playwright: Cross-Browser (Chromium/Firefox/WebKit), schnelle Parallelisierung (8 Worker etwa 3× schneller als Cypress), async/await-Syntax, geeignet für mittlere und große Projekte
• Cypress: Chrome-fokussiert, starkes Time-Travel-Debugging, große Community, einsteigerfreundlich
Nur Chrome und wenig Test-Erfahrung im Team → Cypress. Cross-Browser, schnelles CI, React/Next.js-Erfahrung → Playwright.
Muss ich Page Object Model unbedingt verwenden?
Bei kleinen Projekten (unter 10 Testfällen) oder einmalig getesteten Seiten reicht direkter Code in den Tests. Aber wenn:
• mehrere Tests dieselbe Seite bedienen
• mehrere Personen Tests pflegen
• das Projekt langfristig wächst
dann spart POM viel Ärger. Ein geänderter Selektor zeigt den Wert: ohne POM 10+ Dateien anfassen, mit POM nur eine.
Tests in CI laufen ständig in Timeout – was tun?
• webServer.timeout zu kurz: auf 120 Sekunden setzen (Next.js Cold Start kompiliert)
• zu viele Worker: in CI 2–4 reichen meist
• fehlerhafte Tests: Trace-Datei analysieren – langsame Requests oder Element-Timeouts?
• langsame Browser-Installation: actions/cache für Browser-Dateien
Tipp: in CI nur Chromium, lokal alle Browser – deutlich schneller.
Wie verwalte ich Testdaten? Muss ich die Datenbank jedes Mal manuell leeren?
• Separate Test-Datenbank: nur für Tests, regelmäßig leeren, Dev-Umgebung unberührt
• Nach jedem Test aufräumen: Cleanup in test.afterAll() – Lücken möglich
• Docker-Container: frische DB pro Lauf, danach zerstören (sauber, aber langsamer)
Praxis: Kombination – CI mit Docker-DB, lokal separate Test-DB plus afterAll-Cleanup.
Soll ich Drittanbieter-APIs in Tests mocken?
• Kosten: echte Payment-/SMS-Calls kosten pro Lauf
• Geschwindigkeit: langsame Drittanbieter bremsen die Suite
• Stabilität: Ausfall externer Dienste darf Tests nicht killen
Playwright kann Requests abfangen:
await page.route('**/api/payment', route => route.fulfill({ status: 200, body: '{"success": true}' }));
Alternativ in Next.js API Routes per Umgebungsvariable Mock-Daten zurückgeben.
Wie viel Testabdeckung ist „gut genug“?
Prioritäten:
• Kernflows (Login, Payment, Bestellung): 100 %
• häufig genutzte Features (Produkte, Warenkorb): über 80 %
• seltene Flows (Passwort-Reset, Refund): über 50 %
• Randfeatures (Theme, Sprache): optional
Nicht 100 % jagen – Kernflows sicher, Rest pragmatisch. 100 % Kern + ~60 % gesamt fängt oft ~90 % der Bugs.
Brauche ich nach Playwright-E2E noch Unit-Tests?
• E2E (Playwright): User-Flows, Integration, langsam aber breit
• Unit (Jest/Vitest): Logik, Grenzfälle, Fehlerbehandlung – schnell, lokal
Faustregel: ~70 % Unit, ~30 % E2E. Utils, Hooks, Komponentenlogik per Unit; komplette User-Journeys per E2E.
Unit findet die Stelle, E2E beweist, dass es für Nutzer funktioniert.
12 Min. Lesezeit · Veröffentlicht am: 7. Jan. 2026 · Aktualisiert am: 14. Juli 2026
Next.js Komplettleitfaden
Wenn du über die Suche hier gelandet bist, kommst du am schnellsten weiter, indem du zum vorherigen oder nächsten Beitrag dieser Serie springst.
Vorheriger
Next.js Unit-Tests in der Praxis: Vollständiger Jest + React Testing Library Konfigurationsguide
Next.js-15-Testumgebung von Grund auf einrichten: Jest + React Testing Library, Client/Server Components, Hook-Tests, Mock-Techniken und Fehlerbehebung – mit vollständigen Codebeispielen.
Teil 35 von 51
Nächster
Next.js E-Commerce in der Praxis: Warenkorb und Stripe-Zahlung – vollständige Implementierung
Schritt-für-Schritt-Anleitung zum Aufbau eines E-Commerce-Warenkorbs und Zahlungssystems mit Zustand und Stripe – State-Management-Auswahl, Checkout Session, Webhook und Bestellverarbeitung inklusive direkt nutzbarer Codebeispiele
Teil 37 von 51
Ähnliche Beiträge
Next.js App Router Einstieg: Kernkonzepte und Grundlagen im Überblick

Next.js App Router Einstieg: Kernkonzepte und Grundlagen im Überblick
Next.js 15 in der Praxis: Wie ich an einem Wochenende ein produktionsreifes Blog-System aufgebaut habe


Kommentare
Melde dich mit GitHub an, um einen Kommentar zu hinterlassen