Testing & E2E

Playwright mit Claude Code: End-to-End Testing 2026

Browser-Automatisierung, Page Object Model, API-Testing, Visual Regression und GitHub Actions CI — Claude Code schreibt zuverlässige E2E-Tests für React, Vue und Next.js-Apps.

6. Mai 2026 · 11 min Lesezeit

Playwright hat sich 2026 als der De-facto-Standard für End-to-End-Tests im JavaScript-Ökosystem etabliert. Ob React, Vue, Next.js oder SvelteKit — Playwright läuft cross-browser (Chromium, Firefox, WebKit), unterstützt TypeScript nativ und liefert mit dem Trace Viewer ein unvergleichliches Debugging-Erlebnis. Claude Code versteht die Playwright-API vollständig und generiert wartbare, produktionsreife Tests in wenigen Minuten.

Kurzfassung: Dieser Guide zeigt dir sechs Kernbereiche von Playwright 2026 — von der Grundkonfiguration über Page Object Model und API-Testing bis hin zu Visual Regression und CI/CD-Integration mit GitHub Actions. Alle Codebeispiele sind direkt aus realen Projekten adaptiert.

01

Grundlagen & Konfiguration

02

Page Object Model

03

Assertions & Debugging

04

API-Testing

05

Visual Regression

06

CI/CD mit GitHub Actions

1. Playwright Grundlagen & Konfiguration

Playwright wird über npm installiert und bringt alle Browser-Binaries mit. Die Konfigurationsdatei playwright.config.ts steuert Browser, Timeouts, Base-URL und Reporting-Optionen zentral für das gesamte Projekt.

Install Config

Installation und initiale Einrichtung

# Playwright installieren (interaktiver Setup-Wizard) npm init playwright@latest # Nur Paket hinzufügen (ohne Wizard) npm install -D @playwright/test # Browser-Binaries herunterladen npx playwright install # Nur bestimmte Browser (CI-Optimierung) npx playwright install chromium firefox webkit

playwright.config.ts — Vollständige Produktionskonfiguration

import { defineConfig, devices } from '@playwright/test'; export default defineConfig({ // Verzeichnis mit Testdateien testDir: './tests/e2e', // Parallele Ausführung für schnellere Runs fullyParallel: true, workers: process.env.CI ? 4 : undefined, // In CI strenger: kein .only erlaubt forbidOnly: !!process.env.CI, // Fehlgeschlagene Tests 2x wiederholen (CI-Flakiness-Schutz) retries: process.env.CI ? 2 : 0, // Globale Einstellungen für alle Tests use: { baseURL: process.env.BASE_URL || 'http://localhost:3000', trace: 'on-first-retry', // Trace nur bei Fehlern speichern screenshot: 'only-on-failure', // Screenshots bei Fehlern video: 'retain-on-failure', // Videos bei Fehlern behalten actionTimeout: 10_000, // 10s pro Aktion navigationTimeout: 30_000, // 30s für Seitennavigation }, // Reporter: HTML-Report lokal, GitHub-Annotations in CI reporter: process.env.CI ? [['github'], ['html', { open: 'never' }]] : [['html'], ['line']], // Browser-Matrix: 3 Engines × Desktop + Mobile projects: [ { name: 'chromium', use: { ...devices['Desktop Chrome'] }, }, { name: 'firefox', use: { ...devices['Desktop Firefox'] }, }, { name: 'webkit', use: { ...devices['Desktop Safari'] }, }, { name: 'mobile-chrome', use: { ...devices['Pixel 7'] }, }, { name: 'mobile-safari', use: { ...devices['iPhone 15'] }, }, ], // Dev-Server vor Tests starten (nur lokal) webServer: { command: 'npm run dev', url: 'http://localhost:3000', reuseExistingServer: !process.env.CI, timeout: 120_000, }, });

Erster Test — Locator-Strategien und Auto-Waiting

Playwright setzt auf semantische Locators: getByRole, getByText, getByLabel und getByPlaceholder sind stabiler als CSS-Selektoren, weil sie testen wie ein Benutzer interagiert — nicht wie der Code aufgebaut ist. Jede Aktion wartet automatisch, bis das Element actionable ist (sichtbar, aktiviert, stabil).

import { test, expect } from '@playwright/test'; test.describe('Produkt-Suche', () => { test.beforeEach(async ({ page }) => { await page.goto('/'); }); test('findet Produkt über Suchfeld', async ({ page }) => { // Semantischer Locator — kein CSS, kein XPath await page.getByRole('searchbox', { name: 'Produktsuche' }).fill('Laptop'); await page.getByRole('button', { name: 'Suchen' }).click(); // Auto-waiting: wartet auf Netzwerkruhe nach Click await page.waitForLoadState('networkidle'); // Assertion — Playwright wartet automatisch bis Bedingung erfüllt await expect(page.getByRole('listitem')).toHaveCount.greaterThan(0); await expect(page.getByText('Laptop Pro 16"')).toBeVisible(); await expect(page).toHaveURL(/search\?q=Laptop/); }); test('zeigt Fehlermeldung bei leerem Suchfeld', async ({ page }) => { await page.getByRole('button', { name: 'Suchen' }).click(); const error = page.getByRole('alert'); await expect(error).toBeVisible(); await expect(error).toHaveText('Bitte Suchbegriff eingeben'); }); test('Keyboard-Navigation funktioniert', async ({ page }) => { await page.getByRole('searchbox').fill('Notebook'); await page.keyboard.press('Enter'); // getByTestId als letzter Ausweg wenn keine semantische Rolle verfügbar await expect(page.getByTestId('search-results')).toBeVisible(); }); });

Locator-Priorität (Best Practice 2026)

2. Page Object Model (POM)

POM Fixtures

Das Page Object Model kapselt alle Interaktionen mit einer Seite in einer Klasse. Tests werden dadurch kürzer, lesbarer und wartbarer: Ändert sich ein Selektor, muss er nur an einem Ort angepasst werden. Playwright Fixtures erweitern das Muster um automatische Instanziierung.

BasePage — Gemeinsame Basisklasse

// tests/pages/BasePage.ts import { type Page, type Locator } from '@playwright/test'; export abstract class BasePage { protected readonly page: Page; constructor(page: Page) { this.page = page; } async navigate(path = '/'): Promise<void> { await this.page.goto(path); await this.page.waitForLoadState('domcontentloaded'); } async waitForNetworkIdle(): Promise<void> { await this.page.waitForLoadState('networkidle'); } async getPageTitle(): Promise<string> { return this.page.title(); } async takeScreenshot(name: string): Promise<void> { await this.page.screenshot({ path: `screenshots/${name}.png`, fullPage: true }); } }

LoginPage — Konkrete Page-Klasse

// tests/pages/LoginPage.ts import { type Page, type Locator, expect } from '@playwright/test'; import { BasePage } from './BasePage'; export class LoginPage extends BasePage { // Locators als readonly properties — einmal definiert, überall verwendbar readonly emailInput: Locator; readonly passwordInput: Locator; readonly submitButton: Locator; readonly errorMessage: Locator; readonly forgotPasswordLink: Locator; constructor(page: Page) { super(page); this.emailInput = page.getByLabel('E-Mail-Adresse'); this.passwordInput = page.getByLabel('Passwort'); this.submitButton = page.getByRole('button', { name: 'Anmelden' }); this.errorMessage = page.getByRole('alert'); this.forgotPasswordLink = page.getByRole('link', { name: 'Passwort vergessen?' }); } // Reusable Action: vollständiger Login-Flow async login(email: string, password: string): Promise<void> { await this.navigate('/login'); await this.emailInput.fill(email); await this.passwordInput.fill(password); await this.submitButton.click(); await this.waitForNetworkIdle(); } async expectError(message: string): Promise<void> { await expect(this.errorMessage).toBeVisible(); await expect(this.errorMessage).toContainText(message); } async expectRedirectToDashboard(): Promise<void> { await expect(this.page).toHaveURL('/dashboard'); } }

DashboardPage

// tests/pages/DashboardPage.ts import { type Page, type Locator, expect } from '@playwright/test'; import { BasePage } from './BasePage'; export class DashboardPage extends BasePage { readonly welcomeHeading: Locator; readonly statsGrid: Locator; readonly recentOrdersTable: Locator; readonly logoutButton: Locator; readonly notificationBadge: Locator; constructor(page: Page) { super(page); this.welcomeHeading = page.getByRole('heading', { level: 1 }); this.statsGrid = page.getByTestId('stats-grid'); this.recentOrdersTable = page.getByRole('table'); this.logoutButton = page.getByRole('button', { name: 'Abmelden' }); this.notificationBadge = page.getByTestId('notification-count'); } async getStatValue(label: string): Promise<string> { const stat = this.page.getByTestId(`stat-${label}`); return (await stat.textContent()) ?? ''; } async expectLoaded(): Promise<void> { await expect(this.welcomeHeading).toBeVisible(); await expect(this.statsGrid).toBeVisible(); } }

Fixtures — automatische Page-Instanziierung

// tests/fixtures.ts import { test as base, expect } from '@playwright/test'; import { LoginPage } from './pages/LoginPage'; import { DashboardPage } from './pages/DashboardPage'; // Typen für eigene Fixtures definieren type Fixtures = { loginPage: LoginPage; dashboardPage: DashboardPage; authenticatedPage: DashboardPage; // bereits eingeloggt }; export const test = base.extend<Fixtures>({ loginPage: async ({ page }, use) => { const loginPage = new LoginPage(page); await loginPage.navigate('/login'); await use(loginPage); }, dashboardPage: async ({ page }, use) => { await use(new DashboardPage(page)); }, // Fixture mit vorausgehendem Login-Schritt authenticatedPage: async ({ page }, use) => { const login = new LoginPage(page); await login.login( process.env.TEST_EMAIL ?? 'test@example.com', process.env.TEST_PASSWORD ?? 'TestPass123!' ); await login.expectRedirectToDashboard(); await use(new DashboardPage(page)); }, }); export { expect };

Test mit POM und Fixtures

// tests/e2e/auth.spec.ts — sauber und lesbar dank POM import { test, expect } from '../fixtures'; test.describe('Authentifizierung', () => { test('erfolgreicher Login leitet auf Dashboard weiter', async ({ loginPage, dashboardPage }) => { await loginPage.login('user@example.com', 'SecurePass!'); await dashboardPage.expectLoaded(); } ); test('falsches Passwort zeigt Fehlermeldung', async ({ loginPage }) => { await loginPage.login('user@example.com', 'wrong'); await loginPage.expectError('Ungültige Anmeldedaten'); } ); test('Dashboard-Statistiken nach Login korrekt', async ({ authenticatedPage }) => { await authenticatedPage.expectLoaded(); const revenue = await authenticatedPage.getStatValue('revenue'); expect(revenue).toMatch(/€[\d,.]+/); } ); });

POM-Vorteile auf einen Blick

3. Assertions & Debugging

Assertions Trace Viewer

Playwright-Assertions sind auto-retrying: Sie versuchen die Bedingung bis zu einem Timeout (Standard: 5 Sekunden) zu erfüllen, bevor der Test fehlschlägt. Das eliminiert fragile waitFor-Aufrufe und macht Tests robuster.

Häufig verwendete Web-First-Assertions

import { test, expect } from '@playwright/test'; test('Assertions Showcase', async ({ page }) => { await page.goto('/produkte'); // Sichtbarkeit await expect(page.getByRole('heading', { name: 'Produkte' })).toBeVisible(); await expect(page.getByTestId('loader')).toBeHidden(); // Text-Inhalt await expect(page.getByTestId('product-count')).toHaveText('42 Produkte'); await expect(page.getByTestId('page-title')).toContainText('Produkte'); // URL und Titel await expect(page).toHaveURL('/produkte'); await expect(page).toHaveTitle(/Produkte/); // Formular-Elemente await expect(page.getByLabel('Suche')).toBeEnabled(); await expect(page.getByLabel('Kategorie')).toHaveValue('alle'); // Attribute und CSS await expect(page.getByRole('img', { name: 'Logo' })).toHaveAttribute('alt', 'SpockyMagicAI'); await expect(page.getByTestId('status-dot')).toHaveCSS('background-color', 'rgb(34, 197, 94)'); // Listen und Anzahl const cards = page.getByTestId('product-card'); await expect(cards).toHaveCount(12); // erste Seite = 12 Einträge });

Soft Assertions — alle Fehler sammeln

// Soft Assertions: Test läuft komplett durch, auch wenn ein expect fehlschlägt test('Produktdetail-Seite vollständig', async ({ page }) => { await page.goto('/produkte/laptop-pro-16'); // Alle Assertions werden geprüft, nicht beim ersten Fehler abgebrochen await expect.soft(page.getByRole('heading', { level: 1 })).toBeVisible(); await expect.soft(page.getByTestId('price')).toContainText('€'); await expect.soft(page.getByRole('button', { name: 'In den Warenkorb' })).toBeEnabled(); await expect.soft(page.getByAltText('Produktbild')).toBeVisible(); await expect.soft(page.getByTestId('rating')).toHaveText(/[\d.]+\/5/); // Am Ende: schlägt fehl wenn IRGENDEINE soft assertion fehlschlug expect(test.info().errors).toHaveLength(0); });

Debugging: UI Mode, Trace Viewer und Screenshots

# Interactive UI Mode — Tests live beobachten und debuggen npx playwright test --ui # Bestimmte Testdatei im headed Browser ausführen npx playwright test auth.spec.ts --headed # Nur ein spezifischer Test per -g (grep) npx playwright test -g "erfolgreicher Login" # Trace direkt mitaufzeichnen npx playwright test --trace on # Trace lokal öffnen nach fehlgeschlagenem Test npx playwright show-trace test-results/auth-login/trace.zip # Debug-Modus: Playwright Inspector öffnet sich PWDEBUG=1 npx playwright test auth.spec.ts
// Manueller Screenshot im Test (für Fehleranalyse) test('Warenkorb-Flow', async ({ page }) => { await page.goto('/produkte/laptop-pro-16'); await page.getByRole('button', { name: 'In den Warenkorb' }).click(); // Screenshot zum Debugging zwischenspeichern await page.screenshot({ path: 'debug/after-add-to-cart.png' }); // Auf Warenkorb-Feedback warten await expect(page.getByRole('alert')).toContainText('Produkt hinzugefügt'); // Vollständige Seite screenshotten await page.screenshot({ path: 'debug/cart-confirmation.png', fullPage: true }); });

4. API-Testing mit Playwright

API Backend Seeding

Playwright enthält einen eingebauten API-Testing-Client (APIRequestContext), der sich nahtlos mit den Browser-Tests kombinieren lässt. Damit können APIs direkt getestet werden, ohne einen Browser zu starten — ideal für Backend-Seeding vor E2E-Tests oder als eigene API-Test-Suite.

Direkte API-Calls: GET, POST, PUT, DELETE

import { test, expect } from '@playwright/test'; test.describe('Produkt-API', () => { const API_BASE = 'http://localhost:3001/api'; test('GET /products — Liste zurückgeben', async ({ request }) => { const response = await request.get(`${API_BASE}/products`); expect(response.ok()).toBeTruthy(); expect(response.status()).toBe(200); const body = await response.json(); expect(body).toHaveProperty('data'); expect(body.data).toBeInstanceOf(Array); expect(body.data.length).toBeGreaterThan(0); }); test('POST /products — neues Produkt anlegen', async ({ request }) => { const newProduct = { name: 'Test-Laptop 2026', price: 1299.99, category: 'laptops', stock: 50, }; const response = await request.post(`${API_BASE}/products`, { headers: { 'Authorization': `Bearer ${process.env.API_TOKEN}` }, data: newProduct, }); expect(response.status()).toBe(201); const created = await response.json(); expect(created).toMatchObject({ name: 'Test-Laptop 2026', price: 1299.99 }); expect(created).toHaveProperty('id'); }); test('PUT /products/:id — Produkt aktualisieren', async ({ request }) => { const response = await request.put(`${API_BASE}/products/42`, { headers: { 'Authorization': `Bearer ${process.env.API_TOKEN}` }, data: { price: 999.99, stock: 25 }, }); expect(response.ok()).toBeTruthy(); const updated = await response.json(); expect(updated.price).toBe(999.99); }); test('DELETE /products/:id — Produkt löschen', async ({ request }) => { const response = await request.delete(`${API_BASE}/products/99`, { headers: { 'Authorization': `Bearer ${process.env.API_TOKEN}` }, }); expect(response.status()).toBe(204); }); });

Backend-Seeding: API vor E2E-Tests befüllen

// tests/e2e/checkout.spec.ts — Testdaten per API anlegen, dann E2E testen import { test, expect } from '@playwright/test'; interface Product { id: number; name: string; price: number } test.describe('Checkout-Flow', () => { let testProduct: Product; // Setup: Testprodukt per API anlegen test.beforeAll(async ({ request }) => { const res = await request.post('/api/products', { headers: { Authorization: `Bearer ${process.env.SEED_TOKEN}` }, data: { name: 'E2E-Testprodukt', price: 49.99, stock: 100 }, }); testProduct = await res.json(); }); // Teardown: Testprodukt per API wieder löschen test.afterAll(async ({ request }) => { await request.delete(`/api/products/${testProduct.id}`, { headers: { Authorization: `Bearer ${process.env.SEED_TOKEN}` }, }); }); test('Produkt kaufen — vollständiger Flow', async ({ page }) => { await page.goto(`/produkte/${testProduct.id}`); await page.getByRole('button', { name: 'In den Warenkorb' }).click(); await page.getByRole('link', { name: 'Zum Warenkorb' }).click(); await expect(page.getByText(testProduct.name)).toBeVisible(); await expect(page.getByTestId('cart-total')).toContainText('€49.99'); }); });

APIRequestContext — globaler Client mit Auth

// tests/fixtures.ts — APIRequestContext mit Auth-Header import { test as base, request } from '@playwright/test'; export const test = base.extend({ apiContext: async ({}, use) => { const ctx = await request.newContext({ baseURL: process.env.API_BASE_URL ?? 'http://localhost:3001', extraHTTPHeaders: { Accept: 'application/json', Authorization: `Bearer ${process.env.API_TOKEN}`, }, }); await use(ctx); await ctx.dispose(); }, });

5. Visual Regression Testing

Visual Snapshots

Playwright kann Screenshot-Diffs gegen Baseline-Images vergleichen. Pixel-genaue Vergleiche erkennen unbeabsichtigte CSS-Änderungen, Layout-Verschiebungen und Rendering-Unterschiede zwischen Browsers — bevor sie in Produktion gelangen.

Basis-Setup: toHaveScreenshot

import { test, expect } from '@playwright/test'; test.describe('Visual Regression', () => { test('Startseite sieht korrekt aus', async ({ page }) => { await page.goto('/'); await page.waitForLoadState('networkidle'); // Screenshot gegen Baseline vergleichen await expect(page).toHaveScreenshot('startseite.png', { maxDiffPixels: 100, // Toleranz: max 100 Pixel-Unterschied threshold: 0.2, // 20% Farbdifferenz erlaubt (Anti-Aliasing) fullPage: true, }); }); test('Produkt-Karte visuell korrekt', async ({ page }) => { await page.goto('/produkte'); await page.waitForLoadState('networkidle'); // Nur einen bestimmten Bereich der Seite screenshotten const firstCard = page.getByTestId('product-card').first(); await expect(firstCard).toHaveScreenshot('produkt-karte.png'); }); test('Mobile-Ansicht — Hamburger-Menü', async ({ page }) => { // Viewport auf Mobile setzen await page.setViewportSize({ width: 375, height: 812 }); await page.goto('/'); await page.getByRole('button', { name: 'Menü öffnen' }).click(); await expect(page).toHaveScreenshot('mobile-menu-open.png'); }); });

Baseline-Management und Update-Workflow

# Baselines erstmals generieren (beim ersten Run) npx playwright test --update-snapshots # Snapshots für einen bestimmten Browser aktualisieren npx playwright test --update-snapshots --project=chromium # Nur visual-regression Tests laufen lassen npx playwright test visual-regression.spec.ts # Diff-Report nach fehlgeschlagenem Run anzeigen npx playwright show-report

pixelmatch-Konfiguration in playwright.config.ts

export default defineConfig({ // ... andere Optionen ... // Globale Snapshot-Einstellungen expect: { // Screenshots in eigenem Verzeichnis ablegen snapshotDir: './tests/snapshots', toHaveScreenshot: { // Pixelmatch-Schwellwert (0-1): 0.2 = 20% Toleranz pro Pixel threshold: 0.2, // Max. absolute Pixel-Unterschiede (für Animation/Fonts) maxDiffPixels: 50, // Animationen stoppen für stabile Vergleiche animations: 'disabled', // Caret (Cursor) in Inputs verstecken caret: 'hide', // Skalierung: css = device-unabhängig scale: 'css', }, }, });

Visual-Regression Best Practices

6. CI/CD-Integration mit GitHub Actions

GitHub Actions Sharding Matrix

GitHub Actions ist die natürliche Heimat für Playwright-Tests. Der offizielle Playwright-Action installiert Dependencies und Browser in einem Schritt. Mit Browser-Matrix und Test-Sharding laufen auch große Test-Suites in wenigen Minuten.

Vollständige GitHub Actions Workflow-Datei

# .github/workflows/e2e-playwright.yml name: Playwright E2E Tests on: push: branches: [main, develop] pull_request: branches: [main] jobs: playwright: name: 'Playwright (${{ matrix.project }})' runs-on: ubuntu-latest timeout-minutes: 30 strategy: fail-fast: false # Alle Browsers laufen, auch wenn einer fehlschlägt matrix: project: [chromium, firefox, webkit] shard: [1/3, 2/3, 3/3] # Test-Suite in 3 Shards aufteilen steps: - name: Checkout Code uses: actions/checkout@v4 - name: Node.js einrichten uses: actions/setup-node@v4 with: node-version: '20' cache: 'npm' - name: Dependencies installieren run: npm ci - name: Playwright Browser installieren run: npx playwright install --with-deps ${{ matrix.project }} - name: Dev-Server starten (Hintergrund) run: npm run build && npm run start & env: NODE_ENV: test DATABASE_URL: ${{ secrets.TEST_DATABASE_URL }} API_TOKEN: ${{ secrets.TEST_API_TOKEN }} - name: Warten bis Server bereit run: npx wait-on http://localhost:3000 --timeout 60000 - name: Playwright Tests ausführen run: | npx playwright test \ --project=${{ matrix.project }} \ --shard=${{ matrix.shard }} \ --reporter=html env: CI: 'true' BASE_URL: 'http://localhost:3000' - name: HTML-Report als Artefakt hochladen uses: actions/upload-artifact@v4 if: always() # Auch bei Testfehlern uploaden! with: name: playwright-report-${{ matrix.project }}-${{ strategy.job-index }} path: playwright-report/ retention-days: 7 - name: Traces hochladen (nur bei Fehler) uses: actions/upload-artifact@v4 if: failure() with: name: test-results-${{ matrix.project }}-${{ strategy.job-index }} path: test-results/ retention-days: 3

Sharding ohne Matrix — für einfachere Setups

# Shard 1 von 4 laufen lassen npx playwright test --shard=1/4 # In GitHub Actions ohne Matrix-Strategy: # Jeder Job bekommt eine andere Shard-Nummer via ${{ matrix.shardIndex }} strategy: matrix: shardIndex: [1, 2, 3, 4] shardTotal: [4] steps: - run: npx playwright test --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }}

Retry-Konfiguration und Flakiness-Management

// playwright.config.ts — CI-Retries export default defineConfig({ retries: process.env.CI ? 2 : 0, // Flaky-Test-Report: zeigt welche Tests mehrere Versuche brauchten reporter: [ ['html'], ['json', { outputFile: 'test-results/results.json' }], ['junit', { outputFile: 'test-results/junit.xml' }], // für GitHub PR-Summary ], use: { // Bei Retry: Trace immer aufzeichnen trace: 'on-first-retry', video: 'retain-on-failure', }, });

Browser-Matrix: Chromium vs. Firefox vs. WebKit

Browser Engine Testabdeckung Typische Laufzeit Pflicht in CI?
Chromium Blink Chrome + Edge ~2 min Ja (Primary)
Firefox Gecko Firefox alle Versionen ~3 min Ja
WebKit WebKit Safari macOS + iOS ~4 min Ja (Safari-Bugs)
Mobile Chrome Blink Android Chrome ~2.5 min Empfohlen
Mobile Safari WebKit iPhone Safari ~4 min Empfohlen

Performance-Tipp: Mit Sharding (z.B. --shard=1/4) und fullyParallel: true laufen 200+ Tests in unter 5 Minuten in CI. Nutze test.skip mit Browser-Kondition statt Browser aus der Matrix zu entfernen: test.skip(browserName === 'webkit', 'WebKit unterstützt keine WebRTC')

Test-Report in GitHub PR-Summary anzeigen

# .github/workflows/e2e-playwright.yml — PR-Kommentar mit Test-Ergebnissen - name: Test-Report als PR-Kommentar uses: dawidd6/action-send-mail@v3 # Oder: Playwright GitHub Reporter nutzt automatisch GitHub Annotations # → rote X und Checks direkt im PR ohne extra Step # Empfehlung: GitHub Reporter in playwright.config.ts aktivieren # reporter: process.env.CI ? [['github'], ['html']] : [['html']] # → Playwright schreibt ::error:: Annotations in GitHub Actions Logs

CI/CD Checkliste für Playwright

Claude Code für deine Playwright-Tests

Page Object Model, API-Seeding, Visual Regression und GitHub Actions CI — Claude Code versteht den kompletten Playwright-Stack und generiert produktionsreife E2E-Tests für React, Vue, Next.js und SvelteKit. Jetzt 14 Tage kostenlos testen.

14 Tage kostenlos testen →