</>
{ }
Playwright · Component Testing · React · E2E
Playwright Component Testing:
E2E für einzelne React-Komponenten

JSDOM-basierte Tests lügen manchmal: Sie simulieren Browser-Verhalten, testen aber nicht, was der echte Browser mit einer Komponente macht. Playwright Component Testing rendert jede React-Komponente in einem echten Chromium, Firefox oder WebKit – ohne die gesamte Anwendung hochzufahren.

17 Min. Lesezeit Playwright CT · mount() · Accessibility · CI/CD Playwright 1.44+ · React 18+

1. Warum Playwright Component Testing? Das Argument gegen JSDOM

Playwright Component Testing löst ein fundamentales Problem klassischer React-Tests: JSDOM, das von Testing Library und Jest genutzte simulierte DOM, ist kein echter Browser. Es implementiert Web-APIs unvollständig, verarbeitet CSS nicht, führt keine Layout-Berechnungen durch und simuliert keine echten Browser-Ereignis-Bubbling-Kaskaden. Tests, die in JSDOM-Umgebungen laufen, können bestehen, obwohl die Komponente im echten Browser fehlerhaft ist – weil die Simulation nicht die Realität abbildet.

Playwright Component Testing geht einen anderen Weg: Es startet echte Browser-Instanzen (Chromium, Firefox, WebKit), rendert dort einzelne React-Komponenten in Isolation und erlaubt alle gewohnten Playwright-Assertions und Interaktionen direkt an der Komponente. Der entscheidende Unterschied zu normalen Playwright-E2E-Tests: Man braucht keine laufende Anwendung, keinen Webserver und kein Backend. Die Komponente wird direkt in den Browser gemountet – ähnlich wie in Storybook, aber mit vollständigem Testing-Support.

Das macht Playwright Component Testing zum idealen Werkzeug für Fälle, in denen JSDOM-Tests versagen: CSS-abhängige Interaktionen (Hover-States, Visibility), Browser-spezifisches Verhalten bei Formularen (autocomplete, native validation), echte Fokus-Verwaltung für Accessibility-Tests und Scroll-Verhalten. Gleichzeitig ist Playwright CT schneller als vollständige E2E-Tests, weil keine Anwendungsrouting-Schicht durchlaufen werden muss.

2. Setup: Playwright CT in ein React-Projekt integrieren

Die Integration von Playwright Component Testing in ein bestehendes React-Projekt ist überraschend einfach. npm init playwright@latest -- --ct richtet das Projekt ein und erstellt die notwendige Konfigurationsdatei playwright-ct.config.ts. Für React-Projekte mit Vite oder Webpack werden die entsprechenden Adapter automatisch konfiguriert. Das Setup erstellt außerdem eine playwright/index.html-Datei, in der globale Styles, Providers und Contexts definiert werden können – alles, was jede Komponente beim Mount braucht.

Component Tests werden in Dateien mit dem Suffix .ct.tsx oder .spec.tsx geschrieben und importieren mount aus @playwright/experimental-ct-react. Wichtig: Playwright CT nutzt Vite oder Webpack unter der Haube, um die Komponenten zu bundeln – das bedeutet, dass alle Imports und Aliase aus der bestehenden Konfiguration automatisch funktionieren. CSS-Module, SCSS, Tailwind und andere Preprocessors werden aus der bestehenden Build-Konfiguration übernommen.


// playwright-ct.config.ts — Component Testing configuration
import { defineConfig, devices } from '@playwright/experimental-ct-react'
import react from '@vitejs/plugin-react'
import path from 'path'

export default defineConfig({
  testDir: './src',
  testMatch: '**/*.ct.tsx',
  fullyParallel: true,
  forbidOnly: !!process.env.CI,
  retries: process.env.CI ? 2 : 0,
  workers: process.env.CI ? 1 : undefined,
  reporter: [['html', { outputFolder: 'playwright-ct-report' }]],
  use: {
    ctViteConfig: {
      plugins: [react()],
      resolve: {
        alias: { '@': path.resolve(__dirname, './src') },
      },
    },
    viewport: { width: 1280, height: 720 },
    actionTimeout: 5000,
  },
  projects: [
    { name: 'chromium', use: { ...devices['Desktop Chrome'] } },
    { name: 'firefox', use: { ...devices['Desktop Firefox'] } },
    { name: 'webkit', use: { ...devices['Desktop Safari'] } },
  ],
})

// playwright/index.tsx — Global providers for all component tests
import React from 'react'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import '../src/index.css' // import global CSS including Tailwind

const queryClient = new QueryClient({
  defaultOptions: { queries: { retry: false, staleTime: Infinity } },
})

// beforeMount hook: wrap every mounted component with providers
export const beforeMount = ({ App }) => {
  return (
    <QueryClientProvider client={queryClient}>
      <App />
    </QueryClientProvider>
  )
}

3. mount(): Komponenten in echten Browsern rendern

Die mount()-Funktion ist das Herzstück von Playwright Component Testing. Sie rendert eine React-Komponente im echten Browser und gibt ein Playwright-Locator-Objekt zurück, über das man mit der Komponente interagieren und Assertions durchführen kann. Der Aufruf ist analog zu React Testing Library's render(), aber mit dem entscheidenden Unterschied: Man bekommt einen vollständigen Playwright-Locator zurück, der alle Playwright-Assertions, -Interaktionen und -Screenshots unterstützt.

Das Besondere an Playwright CT ist, dass man Props, Context und State vollständig kontrolliert. Mit component.update() kann man Props dynamisch ändern und prüfen, wie die Komponente auf Prop-Änderungen reagiert. Mit Hooks, die als Props injiziert werden, lässt sich auch das Verhalten bei State-Änderungen testen. Zwischen Tests wird die Komponente komplett neu in einen frischen DOM-Container gemountet – es gibt keine Zustandsverschmutzung zwischen Tests.

4. User Events und Interaktionen testen

Der größte Vorteil von Playwright Component Testing gegenüber JSDOM-basierten Tests liegt bei der Simulation von User Events. Playwright simuliert echte Browser-Events mit realistischem Timing, Fokus-Verwaltung und Event-Bubbling – genau wie ein echter Nutzer. page.click(), page.fill(), page.keyboard.press() und page.hover() verhalten sich im echten Browser, nicht in einer simulierten Umgebung. Das deckt Bugs auf, die in JSDOM nie sichtbar werden würden.

Ein konkretes Beispiel: Ein Dropdown, das beim Klick außerhalb geschlossen werden soll, funktioniert in JSDOM-Tests fast immer korrekt – weil JSDOM click-outside-Events vereinfacht behandelt. In einem echten Browser mit Playwright CT kann man exakt prüfen: Klick auf Trigger öffnet Dropdown, Klick auf Dropdown-Item schließt ihn, Klick außerhalb schließt ihn, Escape-Taste schließt ihn. Die Erwartungen sind präzise und testen echtes Browser-Verhalten.


// SearchBox.ct.tsx — Testing user interactions in a real browser
import { test, expect } from '@playwright/experimental-ct-react'
import { SearchBox } from './SearchBox'

// Mock data for testing without real API calls
const mockResults = [
  { id: '1', title: 'Next.js App Router', url: '/blog/next-js-app-router' },
  { id: '2', title: 'Playwright Component Testing', url: '/blog/playwright-ct' },
]

test('shows results when user types in search box', async ({ mount, page }) => {
  // Mount component with mock search function
  const component = await mount(
    <SearchBox
      onSearch={async (query) => mockResults.filter(r => r.title.includes(query))}
      placeholder="Search articles..."
    />
  )

  // Verify initial state: input visible, no results shown
  await expect(component.getByRole('searchbox')).toBeVisible()
  await expect(component.getByRole('listbox')).not.toBeVisible()

  // Simulate typing — Playwright fires real keyboard events
  await component.getByRole('searchbox').fill('Playwright')
  await page.waitForTimeout(300) // debounce delay

  // Results should appear after typing
  await expect(component.getByRole('listbox')).toBeVisible()
  await expect(component.getByRole('option')).toHaveCount(1)
  await expect(component.getByRole('option')).toContainText('Playwright Component Testing')

  // Pressing Escape should close the results
  await page.keyboard.press('Escape')
  await expect(component.getByRole('listbox')).not.toBeVisible()
})

test('keyboard navigation works correctly', async ({ mount, page }) => {
  const component = await mount(<SearchBox onSearch={async () => mockResults} />)

  await component.getByRole('searchbox').fill('test')
  await page.keyboard.press('ArrowDown') // focus first result
  await page.keyboard.press('ArrowDown') // focus second result
  await page.keyboard.press('Enter')     // select focused result

  // Verify that the correct result was selected
  await expect(component.getByTestId('selected-result')).toContainText('Playwright Component Testing')
})

5. Formulare und Validierung im Browser testen

Formular-Tests sind ein Bereich, in dem Playwright Component Testing besonders überlegen ist. Native HTML5-Validierung (required, pattern, min/max) funktioniert in JSDOM nicht zuverlässig – der Browser erzwingt keine native Validation Bubble. In einem echten Browser mit Playwright CT kann man exakt testen, ob das Submit eines leeren Feldes die native Validation auslöst, ob Custom-Validation-Messages korrekt angezeigt werden und ob react-hook-form oder Zod-Validierungsmessages beim richtigen Event erscheinen.

Besonders wertvoll: Playwright CT kann testen, ob ein Formular-Submit tatsächlich ein Netzwerk-Request auslöst, ob optimistische UI-Updates korrekt angezeigt werden und ob Error-States nach einem fehlgeschlagenen Submit korrekt resettet werden. Das sind genau die Szenarien, die in rein unit-test-basierten Setups oft nicht getestet werden und erst in E2E-Tests auffallen – bei Playwright Component Testing kann man sie direkt an der Komponente testen.

6. Accessibility mit axe-core und Playwright prüfen

Playwright Component Testing ist das ideale Werkzeug für Accessibility-Tests, weil Accessibility im echten Browser stattfindet. ARIA-Rollen, Fokus-Verwaltung und Screenreader-Kompatibilität hängen vom echten DOM und echtem Browser-Verhalten ab. Mit der @axe-core/playwright-Integration kann man nach dem Mount einer Komponente automatisch einen axe-Accessibility-Scan durchführen, der bekannte WCAG-Verletzungen meldet.

Über automatische axe-Scans hinaus kann man mit Playwright CT manuelle Accessibility-Flows testen: Tab-Navigation durch interaktive Elemente, Aktivierung von Buttons per Enter oder Space, korrekte Fokus-Rückkehr nach dem Schließen eines Modals, Ankündigung von Live-Region-Updates. Diese Tests erfordern echtes Browser-Verhalten und sind in JSDOM nicht zuverlässig durchführbar. Ein Komponententest mit Playwright CT, der Tab-Navigation und axe-Scan kombiniert, gibt mehr Sicherheit als zehn Unit-Tests zusammen.


// Modal.ct.tsx — Accessibility testing in real browsers
import { test, expect } from '@playwright/experimental-ct-react'
import AxeBuilder from '@axe-core/playwright'
import { Modal } from './Modal'

test('modal has no accessibility violations', async ({ mount, page }) => {
  await mount(
    <Modal isOpen title="Confirm Delete" onClose={() => {}}>
      <p>Are you sure you want to delete this item?</p>
      <button>Cancel</button>
      <button>Confirm</button>
    </Modal>
  )

  // Run axe accessibility scan on the mounted component
  const results = await new AxeBuilder({ page })
    .withTags(['wcag2a', 'wcag2aa', 'wcag21aa'])
    .analyze()

  expect(results.violations).toEqual([]) // fails if any WCAG violation found
})

test('focus is trapped inside modal when open', async ({ mount, page }) => {
  await mount(<Modal isOpen title="Dialog" onClose={() => {}}>
    <button>First</button>
    <button>Second</button>
    <button>Third</button>
  </Modal>)

  // When modal opens, focus should move to the dialog
  await expect(page.getByRole('dialog')).toBeFocused()

  // Tab through all focusable elements — should stay inside modal
  await page.keyboard.press('Tab')
  await expect(page.getByRole('button', { name: 'First' })).toBeFocused()
  await page.keyboard.press('Tab')
  await expect(page.getByRole('button', { name: 'Second' })).toBeFocused()
  await page.keyboard.press('Tab')
  await expect(page.getByRole('button', { name: 'Third' })).toBeFocused()
  await page.keyboard.press('Tab') // wrap around — should NOT leave modal
  await expect(page.getByRole('button', { name: 'First' })).toBeFocused()
})

7. Netzwerk-Mocking in Component Tests

Playwright Component Testing bietet vollständiges Netzwerk-Mocking über die gleiche page.route()-API wie normale Playwright-Tests. Das ermöglicht es, HTTP-Requests, die eine Komponente auslöst, abzufangen und mit Mock-Daten zu beantworten – ohne dass die Komponente wissen muss, dass sie gemockt wird. Besonders wertvoll für Komponenten, die direkte API-Calls durchführen (z.B. mit fetch, axios oder React Query): Man kann Loading-States, Error-States und Success-States mit vollständiger Kontrolle über die API-Antwort testen.

Das Mocking-Modell von Playwright CT ist dabei mächtiger als das von MSW (Mock Service Worker): Man kann Netzwerkverzögerungen simulieren (await page.waitForTimeout(500) in der Route-Handler-Funktion), unterschiedliche HTTP-Statuscodes testen und prüfen, ob die Komponente korrekt auf 429 Too Many Requests oder 500 Internal Server Error reagiert. Außerdem kann man über page.waitForRequest() verifizieren, dass die Komponente den richtigen Endpoint mit den richtigen Parametern aufruft.

8. Visual Regression und Screenshot-Vergleiche

Da Playwright Component Testing in echten Browsern läuft, sind Screenshot-Vergleiche (Visual Regression Testing) direkt verfügbar. Mit expect(component).toHaveScreenshot('component-name.png') wird beim ersten Durchlauf ein Basis-Screenshot erstellt und bei allen folgenden Durchläufen verglichen. Pixelgenaue Unterschiede werden detektiert und in einem HTML-Report visualisiert. Das ist besonders wertvoll für Design-System-Komponenten: Eine CSS-Änderung, die unbeabsichtigt das Layout eines Buttons verändert, wird sofort durch den Screenshot-Vergleich erkannt.

Für Mobile-Testing kann man in Playwright CT den Viewport der Browser-Instanz auf typische mobile Größen setzen und damit testen, wie Komponenten bei verschiedenen Bildschirmbreiten aussehen. Responsive-Verhalten, das von CSS-Media-Queries abhängt, kann so direkt in Component Tests geprüft werden – kein Wechsel zu E2E-Tests nötig. Das macht Playwright Component Testing zum vollständigsten Werkzeug für UI-Tests, das derzeit für React verfügbar ist.

9. Playwright CT vs. Vitest/RTL vs. Cypress CT

Der Playwright Component Testing-Ansatz unterscheidet sich erheblich von anderen Teststrategien. Vitest mit React Testing Library ist schneller, braucht keine Browser und eignet sich ideal für Unit-Tests von Logik und einfachen Rendering-Tests. Cypress Component Testing ist ebenfalls browser-basiert, hat aber eine langsamere Startup-Zeit und weniger umfangreiche Browser-Unterstützung als Playwright. Die Wahl zwischen den Ansätzen hängt davon ab, welche Klasse von Bugs man finden möchte.

Kriterium Playwright CT Vitest + RTL Cypress CT
Echter Browser Ja (Chromium/FF/WebKit) Nein (JSDOM) Ja (Chrome/FF)
Testgeschwindigkeit Mittel Sehr schnell Langsam
Screenshot-Tests Nativ unterstützt Nicht möglich Plugin nötig
Accessibility axe + echte Fokus-Tests axe, eingeschränkt axe, Browser-basiert
CI-Geschwindigkeit Gut (parallel) Sehr gut Langsam

Die Empfehlung: Playwright Component Testing ergänzt, aber ersetzt nicht Vitest+RTL. Unit-Tests für Logik und einfaches Rendering bleiben bei Vitest – sie sind schneller und einfacher zu schreiben. Playwright CT kommt für alle Tests, die echtes Browser-Verhalten erfordern: Accessibility, CSS-abhängige Interaktionen, native Formular-Validierung und Screenshot-Vergleiche. Das Ergebnis ist eine Testing-Strategie mit mehreren Schichten, die unterschiedliche Klassen von Bugs abdeckt.

Mironsoft

React Testing, Playwright Integration und Test-Strategie

React-Komponenten zuverlässig testen?

Wir implementieren Playwright Component Testing in eurem React-Projekt – Setup, Test-Strategie, Accessibility-Tests und CI-Integration für maximale Testabdeckung bei minimalem Overhead.

Test-Setup

Playwright CT konfigurieren, Providers einrichten und erste Tests für kritische Komponenten schreiben

Accessibility

axe-core-Integration, Fokus-Tests und WCAG-Compliance für alle interaktiven Komponenten

CI-Integration

Playwright CT in GitHub Actions oder GitLab CI integrieren mit parallelisierter Ausführung

10. Zusammenfassung

Playwright Component Testing schließt die Lücke zwischen Unit-Tests (zu isoliert, JSDOM) und E2E-Tests (zu langsam, erfordert Full-App). Echte Browser, vollständiges CSS-Rendering, echte Event-Simulation und nativer Accessibility-Support machen Playwright CT zum idealen Werkzeug für alle Tests, die echtes Browser-Verhalten erfordern. Das Setup ist einfach, die Integration in bestehende Playwright-Infrastruktur reibungslos und die Testgeschwindigkeit liegt deutlich unter der von Full-E2E-Tests.

Die strategische Empfehlung: Eine Testing-Pyramide mit drei Schichten. Vitest+RTL für schnelle Unit-Tests der Logik, Playwright Component Testing für browser-abhängige Komponentenverhalten und Accessibility, und vollständige Playwright-E2E-Tests für kritische User-Flows. Diese Kombination gibt maximale Testabdeckung bei optimierter Ausführungsgeschwindigkeit. Playwright CT ist keine Konkurrenz zu anderen Testing-Ansätzen – es ist die fehlende Schicht zwischen Unit- und E2E-Tests.

Playwright Component Testing — Das Wichtigste auf einen Blick

Echter Browser

Chromium, Firefox, WebKit – kein JSDOM. CSS-Rendering, echte Events, native Formular-Validierung und Fokus-Verwaltung wie beim echten Nutzer.

mount() API

Direktes Rendern von React-Komponenten ohne laufende App. Props, Context und State vollständig kontrollierbar. component.update() für dynamische Props.

Accessibility

axe-core-Integration für WCAG-Scans. Echte Tab-Navigation und Fokus-Tests. ARIA-Rollen im echten Browser prüfen.

Screenshot-Tests

Nativ unterstützte Visual Regression. toHaveScreenshot() für pixel-genaue Vergleiche. Responsive-Tests durch Viewport-Konfiguration.

11. FAQ: Playwright Component Testing

1CT vs. E2E-Tests: Was ist der Unterschied?
CT rendert Komponenten in Isolation ohne laufende App. E2E testet vollständige User-Flows durch die gesamte Anwendung. CT schneller und gezielter, E2E für Integration und Backend.
2Brauche ich einen Next.js-Server für CT?
Nein. Playwright CT bundelt die Komponente direkt per Vite ohne Webserver. Deutlich schneller als E2E, keine Backend-Abhängigkeiten.
3Playwright CT mit React Testing Library kombinieren?
Im selben Projekt möglich. Vitest+RTL für schnelle Unit-Tests, Playwright CT für browser-abhängige Tests. RTL-Locator-Konzepte wie getByRole in Playwright nativ verfügbar.
4Wie funktioniert CSS in CT-Tests?
Vite/Webpack-Build der Anwendung. Global-CSS, CSS-Module, Tailwind automatisch geladen. CSS wird tatsächlich gerendert – echter Browser, kein JSDOM-Fake.
5Ist Playwright CT produktionsreif?
Seit Playwright 1.44 stabil. "Experimental" im Paketnamen ist historisch. Aktiv von Microsoft weiterentwickelt, in großen Projekten produktiv eingesetzt.
6Wie schnell sind CT-Tests verglichen mit E2E?
Deutlich schneller. Kein Server-Start, kein Routing. CT 1-3 Sekunden, E2E 5-30 Sekunden. In CI mit Parallelisierung sehr gut skalierbar.
7Context-Provider in CT-Tests einrichten?
Per beforeMount-Hook in playwright/index.tsx. QueryClientProvider, ThemeProvider, AuthProvider global registrieren. Einzelne Tests können eigene Wrapper beim mount() übergeben.
8Netzwerk-Requests in CT mocken?
Mit page.route() wie in normalen Playwright-Tests. Abfangen, Mock-Daten, Verzögerung, Fehler simulieren. page.waitForRequest() für Request-Verifikation.
9Welche Frameworks werden unterstützt?
React, Vue, Svelte offiziell. Solid, Preact Community-Adapter. React-Paket: @playwright/experimental-ct-react.
10React Hooks mit Playwright CT testen?
Indirekt per Wrapper-Komponente. Hook nutzen, Zustand rendern, Verhalten bei Eingaben und State-Änderungen testen. Keine direkte Hook-API, aber vollständige Verhaltenstests möglich.