Use Cases (Aktualisiert: 3.6.2026)

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.

E2E-Tests mit Claude Code: Playwright/Cypress und CI in der Praxis

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

FallWarum E2E passtWichtige Erwartung
Login zum DashboardAuth, Redirect und Rechte greifen zusammenÜberschrift, Nutzerstatus, Logout, Fehler
Checkout oder KontaktformularDirekter Einfluss auf Umsatz oder LeadsBestellnummer, Bestätigung, kein Doppelsenden
Artikel zu PDF oder TrainingSchützt Umsatzpfade jenseits von WerbungCTA-href, Event, mobile Sichtbarkeit
Riskante Admin-AktionVerhindert Löschung und RechtefehlerModal, Audit-Log, Rollenprüfung
Lokalisierte SeitenÜbersetzung bricht oft LinksLocale-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

AspektPlaywrightCypress
BrowserChromium, Firefox, WebKitChrome-Familie, Firefox, Edge
ParallelisierungIn Playwright Test enthaltenOft mit Dashboard geplant
Mehrere Tabs und KontexteStarkEingeschränkter
DebuggingTrace Viewer, UI mode, HTML reportAngenehme interaktive GUI
Beste NutzungCI, mehrere Browser, isolierte AuthFrontend-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.

#Claude Code #E2E-Tests #Playwright #Cypress #CI
Kostenlos

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.

Masa

Über den Autor

Masa

Engineer für praktische Claude-Code-Workflows und Team-Einführung.