Lernen / Cheat Sheet
Playwright Cheat Sheet
Die Playwright-Test-APIs, die Sie jeden Tag brauchen, auf einer Seite. Locators stehen in der Reihenfolge, die Playwright empfiehlt: Rolle, Label und Text vor Test-IDs, CSS oder XPath zuletzt. Jede Assertion hier ist web-first: Sie wird wiederholt, bis sie besteht oder ein Timeout eintritt – manuelle Waits brauchen Sie also nicht.
Geprüft mit Playwright Test 1.5x, TypeScript
Ausführen und debuggen
| Aufgabe | Code |
|---|---|
| Neues Projekt | npm init playwright@latest |
| Alle Tests ausführen | npx playwright test |
| Eine Datei, eine Zeile | npx playwright test tests/login.spec.ts:12 |
| Nach Titel oder Tag | npx playwright test --grep @smoke |
| Ein Browser-Projekt | npx playwright test --project=chromium |
| Browser sichtbar ausführen | npx playwright test --headed |
| Schrittweise mit dem Inspector | npx playwright test --debug |
| UI-Modus (Watch, Time Travel) | npx playwright test --ui |
| Nur Fehlschläge des letzten Laufs | npx playwright test --last-failed |
| Einen Flaky Test aufspüren | npx playwright test my.spec.ts --repeat-each=20 |
| Auf CI-Maschinen verteilen (Sharding) | npx playwright test --shard=1/4 |
| HTML-Report öffnen | npx playwright show-report |
| Einen Trace öffnen | npx playwright show-trace trace.zip |
| Ein Skript aufzeichnen | npx playwright codegen https://example.com |
Locators (bevorzugte zuerst)
| Aufgabe | Code |
|---|---|
| Nach Rolle und zugänglichem Namen | page.getByRole('button', { name: 'Sign in' }) |
| Formularfeld über sein Label | page.getByLabel('Email') |
| Nach Placeholder | page.getByPlaceholder('Search') |
| Nach Text (exakt) | page.getByText('Welcome back', { exact: true }) |
| Nach Test-ID (data-testid) | page.getByTestId('checkout-submit') |
| CSS | page.locator('#orders tbody tr') |
| XPath | page.locator('xpath=//table[@id="orders"]//tr') |
| Nach Text filtern | page.getByRole('row').filter({ hasText: 'Mei Lin' }) |
| Nach Kindelement filtern | page.getByRole('listitem').filter({ has: page.getByRole('button', { name: 'Buy' }) }) |
| Innerhalb eines Treffers verketten | row.getByRole('button', { name: 'Cancel' }) |
| Einen von mehreren wählen | .first() .last() .nth(2) |
| Innerhalb eines iframes | page.frameLocator('#payment').getByLabel('Card number') |
Locators sind lazy: Gesucht wird erst, wenn Sie eine Aktion oder Assertion ausführen. Definieren Sie sie also einmal und verwenden Sie sie wieder.
Aktionen
| Aufgabe | Code |
|---|---|
| Navigieren (relativ zur baseURL) | await page.goto('/login') |
| Klicken | await locator.click() |
| Wert eines Eingabefelds ersetzen | await locator.fill('mei@example.com') |
| Taste für Taste tippen | await locator.pressSequentially('abc', { delay: 50 }) |
| Eine Taste drücken | await locator.press('Enter') |
| Checkbox oder Radiobutton | await locator.check() / await locator.uncheck() |
| Eine Option auswählen | await page.getByLabel('Country').selectOption('UK') |
| Eine Datei hochladen | await locator.setInputFiles('fixtures/invoice.pdf') |
| Hover | await locator.hover() |
| Drag & Drop | await source.dragTo(target) |
| Den nächsten Dialog bestätigen | page.once('dialog', (d) => d.accept()) |
Web-First-Assertions
| Aufgabe | Code |
|---|---|
| Sichtbar / verborgen | await expect(locator).toBeVisible() / .toBeHidden() |
| Exakter Text | await expect(locator).toHaveText('Order placed') |
| Text enthält | await expect(locator).toContainText('placed') |
| Wert eines Eingabefelds | await expect(input).toHaveValue('42') |
| Anzahl der Treffer | await expect(page.getByRole('row')).toHaveCount(5) |
| Aktiviert / deaktiviert / angehakt | toBeEnabled() toBeDisabled() toBeChecked() |
| Attribut | await expect(menu).toHaveAttribute('aria-expanded', 'true') |
| URL und Titel | await expect(page).toHaveURL(/dashboard/) / .toHaveTitle('Orders') |
| Negieren | await expect(spinner).not.toBeVisible() |
| Nach einem Fehlschlag weitermachen | await expect.soft(total).toHaveText('£40.00') |
| Beliebige asynchrone Prüfung wiederholen | await expect.poll(async () => (await api.get(url)).status()).toBe(200) |
| Visueller Vergleich | await expect(page).toHaveScreenshot('home.png') |
Netzwerk und API
| Aufgabe | Code |
|---|---|
| Eine Response mocken | await page.route('**/api/orders', (r) => r.fulfill({ json: [] })) |
| Einen Request fehlschlagen lassen | await page.route('**/api/orders', (r) => r.abort()) |
| Eine echte Response verändern | await page.route('**/api/me', async (r) => { const res = await r.fetch(); const json = await res.json(); await r.fulfill({ response: res, json: { ...json, plan: 'pro' } }); }) |
| Auf eine Response warten | const res = page.waitForResponse('**/api/orders');
await page.getByRole('button', { name: 'Save' }).click();
expect((await res).ok()).toBeTruthy(); |
| API-Test | test('list users', async ({ request }) => {
const res = await request.get('/api/users');
await expect(res).toBeOK();
expect(await res.json()).toHaveLength(3);
}); |
Struktur, Fixtures und Hooks
| Aufgabe | Code |
|---|---|
| Tests gruppieren | test.describe('checkout', () => { /* tests */ }) |
| Vor jedem Test ausführen | test.beforeEach(async ({ page }) => { await page.goto('/'); }) |
| Einen Test taggen | test('pays by card', { tag: '@smoke' }, async ({ page }) => { /* ... */ }) |
| Bedingt überspringen | test.skip(browserName === 'webkit', 'Not supported on WebKit') |
| Parallel innerhalb einer Datei | test.describe.configure({ mode: 'parallel' }) |
| Eigene Fixture | export const test = base.extend<{ loginPage: LoginPage }>({
loginPage: async ({ page }, use) => { await use(new LoginPage(page)); },
}); |
| Angemeldeten Zustand wiederverwenden | test.use({ storageState: 'playwright/.auth/user.json' }) |
| Einen Schritt im Report benennen | await test.step('add to cart', async () => { /* ... */ }) |
playwright.config.ts
| Einstellung | Beispiel |
|---|---|
| Basis-URL für page.goto | use: { baseURL: 'http://localhost:3000' } |
| Belege bei Fehlschlag | use: { trace: 'on-first-retry', screenshot: 'only-on-failure', video: 'retain-on-failure' } |
| Retries nur in der CI | retries: process.env.CI ? 2 : 0 |
| Parallelisierung | fullyParallel: true, workers: process.env.CI ? 2 : undefined |
| Fehlschlagen, wenn test.only committet wurde | forbidOnly: !!process.env.CI |
| Zuerst die App starten | webServer: { command: 'npm run start', url: 'http://localhost:3000', reuseExistingServer: !process.env.CI } |
| Browser | projects: [{ name: 'chromium', use: { ...devices['Desktop Chrome'] } }] |
| Standard-Timeouts | timeout: 30_000, expect: { timeout: 5_000 } |