E2E-Tests mit Claude Code: Playwright/Cypress und CI in der Praxis
Playwright-E2E mit Claude Code: CI, typische Fehler, lauffähige Beispiele und CTA-Prüfung.
Erst festlegen, was der E2E-Test schützen soll
Ein von Claude Code erzeugter Login-Test mit grünem Ergebnis reicht nicht aus. Ein E2E-Test prüft einen Nutzerfluss in einem echten Browser. Er ist deshalb langsamer und teurer zu debuggen als ein Unit-Test. Wenn jede UI-Kleinigkeit als E2E-Test endet, wird die CI laut und das Team nimmt Fehler irgendwann nicht mehr ernst.
Wähle zuerst die Flows, deren Ausfall wirklich schadet: Login, Checkout, Kontaktformular, riskante Admin-Aktion und CTA aus einem Artikel zu Produkt oder Training. Masa hat bei ClaudeCodeLab gesehen, dass eine Seite optisch korrekt laden kann, während der Link am Artikelende zur Trainingsseite kaputt ist. Genau solche Fehler sind für Monetarisierung wichtiger als reine Pixelabweichungen.
Dieser Leitfaden nutzt die aktuellen Playwright-Hinweise zu Installation, Locators, CI und Trace Viewer. Ergänzend passen API-Testautomatisierung und der Teststrategie-Leitfaden.
flowchart LR
A["Claude Code beauftragen"] --> B["Drei kritische Flows wählen"]
B --> C["Playwright-Tests schreiben"]
C --> D["Trace und Bericht auswerten"]
D --> E["CI absichern"]
C --> F["CTA und Umsatzpfad prüfen"]
Ein brauchbarer Prompt für Claude Code
Gib Scope, Erfolgskriterien, Fehlerfälle, erlaubte Dateien und Prüfkommandos an. Ohne diese Angaben schreibt Claude Code leicht fragile Tests auf CSS-Klassen. Playwright empfiehlt Locators wie getByRole, getByLabel, getByText und getByTestId, weil sie mit automatischem Warten und wiederholbaren Assertions zusammenarbeiten.
Ziel: E2E-Tests für Login, Checkout und Newsletter-Anmeldung.
Tool: Playwright Test. Am Ende kurz Cypress einordnen.
Regeln:
- role, label, text oder testid vor CSS-Klassen verwenden.
- Fehlerfälle testen, nicht nur den Happy Path.
- In CI workers: 1 und trace: on-first-retry nutzen.
- Prüfen, dass der Monetarisierungs-CTA ein href behält.
Erlaubte Dateien:
- tests/e2e/**/*.spec.ts
- playwright.config.ts
Prüfung:
- npx playwright test
- npx playwright test --headed für lokale Analyse
Schreibe auch, was nicht getestet wird. Echte Zahlungen, echter Mailversand, Werbeklicks und CRM-Schreibzugriffe gehören nicht in jeden E2E-Lauf. Nutze Testmodus, Mocks oder API-Vertragstests. Für diese Grenzen helfen Webhook-Implementierung und Analytics-Implementierung.
Setup-Kommandos
Für neue Projekte reichen diese Befehle. Die offizielle Dokumentation erklärt, dass jede Playwright-Version bestimmte Browser-Binaries erwartet. Nach einem Update solltest du die Browser daher neu installieren. In Linux-CI ist --with-deps sinnvoll, weil auch Systemabhängigkeiten installiert werden.
npm init playwright@latest
npx playwright install
npx playwright test
npx playwright test --headed
npx playwright test --ui
npx playwright show-report
--headed öffnet lokal einen sichtbaren Browser. --ui startet den interaktiven Modus. CI läuft normalerweise headless. Wenn ein Test lokal besteht und in CI fällt, prüfe Animationen, Viewport, Schriftarten, Zeitzone, CPU-Geschwindigkeit und externe API-Wartezeiten.
Ausführbares Playwright-Beispiel
Der folgende Spec ist eigenständig. Er baut mit page.setContent eine kleine Seite auf und benötigt keinen App-Server. Kopiere ihn nach der Playwright-Installation in tests/e2e/claude-code-e2e.spec.ts. In einer echten App ersetzt du renderDemoApp(page) durch page.goto('/login') und passt Labels oder data-testid an.
// tests/e2e/claude-code-e2e.spec.ts
import { test, expect, type Page } from '@playwright/test';
async function renderDemoApp(page: Page) {
await page.setContent(`
<!doctype html>
<main>
<form id="login-form" aria-label="Login form">
<label>Email <input id="email" aria-label="Email" /></label>
<label>Password <input id="password" aria-label="Password" type="password" /></label>
<button type="submit">Log in</button>
<p id="login-error" role="alert" hidden></p>
</form>
<section id="dashboard" hidden>
<h1>Dashboard</h1>
<a data-testid="training-cta" href="/training/">Book Claude Code training</a>
<button id="add-plan">Add Pro plan to cart</button>
<span data-testid="cart-count">0</span>
<button id="buy">Complete purchase</button>
<p data-testid="order-status" role="status"></p>
<label>Newsletter email <input id="newsletter-email" aria-label="Newsletter email" /></label>
<button id="newsletter-button" type="button">Join newsletter</button>
<p id="newsletter-error" role="alert" hidden></p>
<p data-testid="newsletter-status" role="status"></p>
</section>
</main>
<script>
const state = { cart: 0 };
document.querySelector('#login-form').addEventListener('submit', (event) => {
event.preventDefault();
const email = document.querySelector('#email').value;
const password = document.querySelector('#password').value;
const error = document.querySelector('#login-error');
if (email === 'masa@example.com' && password === 'password123') {
document.querySelector('#login-form').hidden = true;
document.querySelector('#dashboard').hidden = false;
error.hidden = true;
return;
}
error.textContent = 'Authentication failed';
error.hidden = false;
});
document.querySelector('#add-plan').addEventListener('click', () => {
state.cart += 1;
document.querySelector('[data-testid="cart-count"]').textContent = String(state.cart);
});
document.querySelector('#buy').addEventListener('click', () => {
const status = document.querySelector('[data-testid="order-status"]');
status.textContent = state.cart === 0 ? 'Cart is empty' : 'Order ORD-1001 completed';
});
document.querySelector('#newsletter-button').addEventListener('click', () => {
const email = document.querySelector('#newsletter-email').value;
const error = document.querySelector('#newsletter-error');
const status = document.querySelector('[data-testid="newsletter-status"]');
if (!email.includes('@')) {
error.textContent = 'Enter a valid email';
error.hidden = false;
status.textContent = '';
return;
}
error.hidden = true;
status.textContent = 'Thanks, we will send the checklist.';
});
</script>
`);
}
async function loginAsDemoUser(page: Page) {
await page.getByLabel('Email').fill('masa@example.com');
await page.getByLabel('Password').fill('password123');
await page.getByRole('button', { name: 'Log in' }).click();
}
test.describe('Claude Code E2E starter', () => {
test('use case 1: login shows dashboard and keeps the training CTA', async ({ page }) => {
await renderDemoApp(page);
await loginAsDemoUser(page);
await expect(page.getByRole('heading', { name: 'Dashboard' })).toBeVisible();
await expect(page.getByTestId('training-cta')).toHaveAttribute('href', '/training/');
});
test('use case 2: checkout flow creates an order number', async ({ page }) => {
await renderDemoApp(page);
await loginAsDemoUser(page);
await page.getByRole('button', { name: 'Add Pro plan to cart' }).click();
await expect(page.getByTestId('cart-count')).toHaveText('1');
await page.getByRole('button', { name: 'Complete purchase' }).click();
await expect(page.getByTestId('order-status')).toContainText('ORD-1001');
});
test('use case 3: newsletter validation blocks invalid leads', async ({ page }) => {
await renderDemoApp(page);
await loginAsDemoUser(page);
await page.getByRole('button', { name: 'Join newsletter' }).click();
await expect(page.getByRole('alert')).toContainText('Enter a valid email');
await page.getByLabel('Newsletter email').fill('reader@example.com');
await page.getByRole('button', { name: 'Join newsletter' }).click();
await expect(page.getByTestId('newsletter-status')).toContainText('Thanks');
});
});
Der Spec deckt drei konkrete Fälle ab: Login, Checkout und Lead-Erfassung. Zusätzlich prüft er das href des Training-CTA, also genau die Stelle, die dünne E2E-Suiten oft übersehen.
Praxistaugliche Playwright-Konfiguration
Die Konfiguration soll im Fehlerfall Beweise liefern. Die Playwright-CI-Dokumentation empfiehlt in CI workers: 1, um Stabilität und Reproduzierbarkeit zu priorisieren.
// playwright.config.ts
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests/e2e',
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
use: {
baseURL: process.env.BASE_URL ?? 'http://127.0.0.1:3000',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
video: 'retain-on-failure',
},
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
{ name: 'webkit', use: { ...devices['Desktop Safari'] } },
],
});
trace: 'on-first-retry' zeigt, was sichtbar war, welcher Klick scheiterte und welche Assertion auslief. Damit kann Claude Code deutlich besser analysieren als mit einer einzelnen CI-Fehlermeldung.
GitHub Actions für CI
In CI entstehen viele Fehler durch fehlende Browser oder Linux-Abhängigkeiten. Starte einfach und ergänze Cache oder Sharding erst, wenn Laufzeit wirklich zum Problem wird.
# .github/workflows/playwright.yml
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@v5
- uses: actions/setup-node@v6
with:
node-version: lts/*
- name: Install dependencies
run: npm ci
- name: Install Playwright browsers
run: npx playwright install --with-deps
- name: Run E2E tests
run: npx playwright test
- uses: actions/upload-artifact@v5
if: ${{ !cancelled() }}
with:
name: playwright-report
path: playwright-report/
retention-days: 30
Wenn du eine Preview-URL testest, übergib BASE_URL an den Workflow. Wenn die App in CI gestartet werden muss, ergänze webServer in playwright.config.ts.
Sinnvolle E2E-Anwendungsfälle
| Fall | Warum E2E passt | Wichtige Erwartung |
|---|---|---|
| Login zum Dashboard | Auth, Redirect und Rechte greifen zusammen | Überschrift, Nutzerstatus, Logout, Fehler |
| Checkout oder Kontaktformular | Direkter Einfluss auf Umsatz oder Leads | Bestellnummer, Bestätigung, kein Doppelsenden |
| Artikel zu PDF oder Training | Schützt Umsatzpfade jenseits von Werbung | CTA-href, Event, mobile Sichtbarkeit |
| Riskante Admin-Aktion | Verhindert Löschung und Rechtefehler | Modal, Audit-Log, Rollenprüfung |
| Lokalisierte Seiten | Übersetzung bricht oft Links | Locale-URL, natürliche Texte, externe Links |
Gib diese Tabelle an Claude Code weiter. So entstehen Tests, die Ergebnisse prüfen, nicht nur Klicks ausführen.
Häufige Fehler
Vermeide waitForTimeout(3000). Feste Wartezeiten bestehen lokal und scheitern in CI. Warte auf den echten Zustand: Element sichtbar, URL geändert oder Statustext vorhanden.
Vermeide .btn-primary:nth-child(2). Eine Designänderung bricht den Test. Nutze role, label, text und stabile data-testid.
Teile keinen Zustand zwischen Tests. Warenkorb, Cookie oder Datenbankeintrag aus einem Test dürfen nicht Voraussetzung für den nächsten sein.
Ignoriere headed/headless-Unterschiede nicht. Schriftarten, GPU, Scroll, Zwischenablage, Datei-Upload und Viewport können in CI anders sein. CI bleibt headless, Diagnose läuft mit --headed oder --ui.
Rufe keine echten Zahlungs-, Mail-, Werbe- oder CRM-Systeme bei jedem Lauf auf. Testmodus, Mocks oder API-Vertragstests sind stabiler.
Playwright oder Cypress
| Aspekt | Playwright | Cypress |
|---|---|---|
| Browser | Chromium, Firefox, WebKit | Chrome-Familie, Firefox, Edge |
| Parallelisierung | In Playwright Test enthalten | Oft mit Dashboard geplant |
| Mehrere Tabs und Kontexte | Stark | Eingeschränkter |
| Debugging | Trace Viewer, UI mode, HTML report | Angenehme interaktive GUI |
| Beste Nutzung | CI, mehrere Browser, isolierte Auth | Frontend-Teams mit Cypress-Bestand |
Cypress bleibt eine gute Wahl. Wenn dein Team bereits Cypress nutzt, migriere nicht aus Modegründen. Ergänze Playwright für einen kritischen Flow und vergleiche Flakiness, Trace-Qualität und CI-Zeit. Prüfe Details immer in den offiziellen Dokus von Playwright und Cypress.
Monetarisierungs-CTA und Ergebnis
E2E schützt auch Umsatz. Ein defekter Beratungslink, eine Produktkarte, ein Gratisformular oder ein Training-CTA kann teurer sein als ein visueller Fehler. Einzelpersonen starten mit dem kostenlosen Claude-Code-Spickzettel. Wiederverwendbare Prompts gibt es unter Produkte und Vorlagen. Teams, die CI-Gates, CLAUDE.md, Review-Regeln und E2E-Verantwortung klären müssen, nutzen Claude-Code-Training und Beratung.
Ich habe das Beispiel geprüft, indem Spec und Konfiguration als TypeScript-Snippets für eine Syntaxvalidierung extrahiert wurden. Der nützlichste Schritt war, die Geschäftserwartung direkt in den Testnamen zu schreiben: Training-CTA erhalten. In echten Projekten reichen zuerst drei Tests: Login, Checkout oder Lead-Formular und Artikel-CTA. Danach kann man stabil in CI auf Admin- und Sprachseiten erweitern.
Kostenloses PDF: Claude-Code-Cheatsheet
E-Mail eintragen und eine Seite mit Befehlen, Review-Gewohnheiten und sicheren Workflows herunterladen.
Wir schützen Ihre Daten und senden keinen Spam.
Über den Autor
Masa
Engineer für praktische Claude-Code-Workflows und Team-Einführung.
Ähnliche Artikel
Claude Code Workflow von Obsidian zu CLAUDE.md
Obsidian-Arbeitsnotizen in CLAUDE.md-Betriebsnotizen verwandeln und Kontext nicht ständig neu erklären.
Claude Code Revenue CTA Routing: Artikel zu PDF, Gumroad und Beratung führen
Ein Claude-Code-Ablauf, der Leser nach Absicht zu Gratis-PDF, Gumroad oder Beratung führt.
Claude-Code-Team-Handoff-Regeln: Belege, Berechtigungen, Rollback und Umsatzpfade
Ein praktisches Claude-Code-Handoff für Review-Belege, Berechtigungen, Rollback, Gratis-PDF, Gumroad und Beratung.