🎯 Die goldene Regel
Teste, was der Nutzer sieht – nicht die Implementierung.
// ✅ RICHTIG: Semantisch, robust
await page.getByRole('button', { name: 'Absenden' }).click();
// ❌ FALSCH: Bricht bei CSS-Änderungen
await page.click('#submit-btn.btn-primary');
🔍 Locator-Priorität (in dieser Reihenfolge)
| Priorität | Methode | Beispiel | Wann verwenden |
|---|---|---|---|
| 1 | getByRole() | getByRole('button', { name: 'Submit' }) | Immer zuerst – semantisch, ARIA-konform |
| 2 | getByLabel() | getByLabel('Passwort') | Formularfelder |
| 3 | getByPlaceholder() | getByPlaceholder('E-Mail eingeben') | Input-Hints |
| 4 | getByText() | getByText('Willkommen') | Nicht-interaktive Elemente |
| 5 | getByTestId() | getByTestId('checkout-btn') | Wenn nichts anderes geht |
Nie verwenden: CSS-Selektoren, XPath, komplexe DOM-Pfade
✅ Web-First Assertions (mit Auto-Wait)
Playwright wartet automatisch bis zu 5 Sekunden (konfigurierbar):
// ✅ RICHTIG – wartet auf Sichtbarkeit
await expect(page.getByText('Erfolg')).toBeVisible();
await expect(page).toHaveTitle(/Dashboard/);
await expect(page.getByRole('listitem')).toHaveCount(3);
// ❌ FALSCH – sofortige Prüfung, kein Warten
expect(await page.getByText('Erfolg').isVisible()).toBe(true);
Häufige Assertions
// Sichtbarkeit & Status
await expect(locator).toBeVisible();
await expect(locator).toBeHidden();
await expect(locator).toBeEnabled();
await expect(locator).toBeDisabled();
// Inhalt & Werte
await expect(locator).toHaveText('Genauer Text');
await expect(locator).toContainText('Teiltext');
await expect(locator).toHaveValue('Eingabewert');
await expect(locator).toHaveAttribute('href', '/link');
// Anzahl & URL
await expect(page.getByRole('article')).toHaveCount(5);
await expect(page).toHaveURL('/dashboard');
⚡ Actions (Auto-Wait eingebaut)
Keine sleep() oder waitForTimeout() nötig:
// Navigation
await page.goto('https://example.com');
// Interaktionen
await page.getByRole('button').click();
await page.getByLabel('Benutzername').fill('andi');
await page.getByRole('checkbox').check();
await page.getByLabel('Land').selectOption('Deutschland');
// Fortgeschritten
await page.getByText('Menü').hover();
await page.getByRole('textbox').focus();
await page.keyboard.press('Enter');
await page.getByLabel('Upload').setInputFiles('datei.pdf');
🔗 Locator filtern & verketten
// Element innerhalb eines anderen finden
const produkt = page.getByRole('listitem').filter({ hasText: 'Produkt A' });
await produkt.getByRole('button', { name: 'In den Warenkorb' }).click();
// Nach abwesendem Text filtern
await expect(
page.getByRole('listitem').filter({ hasNotText: 'Ausverkauft' })
).toHaveCount(5);
// Kombinierte Filter
await page
.getByRole('listitem')
.filter({ hasText: 'Max' })
.filter({ has: page.getByRole('button', { name: 'Bearbeiten' }) })
.click();
🛠️ Debugging & Entwicklung
# Test-Generator (Locator aufzeichnen)
npx playwright codegen example.com
# Mit Inspector debuggen
npx playwright test --debug
# Trace für CI aktivieren
npx playwright test --trace on
npx playwright show-report
# Einzelnen Test debuggen
npx playwright test test.spec.ts:42 --debug
Nützliche Debugging-Config
// playwright.config.ts
export default defineConfig({
use: {
// Lokal: vollständiger Trace
trace: 'on',
// CI: nur bei Retry
// trace: 'on-first-retry',
screenshot: 'only-on-failure',
video: 'retain-on-failure',
},
});
📋 Test-Struktur
import { test, expect } from '@playwright/test';
// Parallele Ausführung in dieser Datei
test.describe.configure({ mode: 'parallel' });
test.describe('Authentifizierung', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/login');
});
test('erfolgreicher Login', async ({ page }) => {
await page.getByLabel('E-Mail').fill('test@example.com');
await page.getByLabel('Passwort').fill('geheim');
await page.getByRole('button', { name: 'Anmelden' }).click();
await expect(page).toHaveURL('/dashboard');
await expect(page.getByText('Willkommen zurück')).toBeVisible();
});
test('fehlgeschlagener Login zeigt Fehler', async ({ page }) => {
await page.getByLabel('E-Mail').fill('falsch@example.com');
await page.getByRole('button', { name: 'Anmelden' }).click();
await expect(page.getByText('Ungültige Anmeldedaten')).toBeVisible();
});
});
⚙️ Performance-Optimierung
// playwright.config.ts
export default defineConfig({
// Worker parallelisieren
workers: process.env.CI ? 4 : undefined,
// Wiederholungen bei Flakiness
retries: process.env.CI ? 2 : 0,
// Projekte für verschiedene Browser
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
{ name: 'firefox', use: { ...devices['Desktop Firefox'] } },
{ name: 'webkit', use: { ...devices['Desktop Safari'] } },
],
});
🚨 Häufige Fehler
| Fehler | Lösung |
|---|---|
sleep(1000) verwenden | Auto-wait nutzen, expect().toBeVisible() |
CSS-Selektoren wie #btn-123 | getByRole() oder getByTestId() |
| Tests hängen vom Zustand anderer Tests ab | test.beforeEach für Isolation |
| Keine Assertions nach Aktion | Immer expect() verwenden |
| Hartkodierte Timeouts | expect(...).toBeVisible({ timeout: 10000 }) |
| Externe Seiten testen | Mit page.route() mocken |
🔗 Weiterführende Links
Dieses Cheat Sheet wird regelmäßig aktualisiert. Letzte Aktualisierung: April 2026