Quality Booster
Cheat Sheet Fortgeschritten

Playwright Best Practices

The essentials at a glance: locator strategy, debugging, performance tips, and common mistakes – compact and ready to use.

Kompakte PDF-Version Optimiert für A4-Druck
Playwright Testing Quick Reference

🎯 The Golden Rule

Test what the user sees – not the implementation.

// ✅ CORRECT: Semantic, robust
await page.getByRole('button', { name: 'Submit' }).click();

// ❌ WRONG: Breaks on CSS changes
await page.click('#submit-btn.btn-primary');

🔍 Locator Priority (in this order)

PriorityMethodExampleWhen to use
1getByRole()getByRole('button', { name: 'Submit' })Always first – semantic, ARIA-compliant
2getByLabel()getByLabel('Password')Form fields
3getByPlaceholder()getByPlaceholder('Enter email')Input hints
4getByText()getByText('Welcome')Non-interactive elements
5getByTestId()getByTestId('checkout-btn')When nothing else works

Never use: CSS selectors, XPath, complex DOM paths


✅ Web-First Assertions (with Auto-Wait)

Playwright automatically waits up to 5 seconds (configurable):

// ✅ CORRECT – waits for visibility
await expect(page.getByText('Success')).toBeVisible();
await expect(page).toHaveTitle(/Dashboard/);
await expect(page.getByRole('listitem')).toHaveCount(3);

// ❌ WRONG – immediate check, no waiting
expect(await page.getByText('Success').isVisible()).toBe(true);

Common Assertions

// Visibility & State
await expect(locator).toBeVisible();
await expect(locator).toBeHidden();
await expect(locator).toBeEnabled();
await expect(locator).toBeDisabled();

// Content & Values
await expect(locator).toHaveText('Exact text');
await expect(locator).toContainText('Partial text');
await expect(locator).toHaveValue('Input value');
await expect(locator).toHaveAttribute('href', '/link');

// Count & URL
await expect(page.getByRole('article')).toHaveCount(5);
await expect(page).toHaveURL('/dashboard');

⚡ Actions (Auto-Wait built-in)

No sleep() or waitForTimeout() needed:

// Navigation
await page.goto('https://example.com');

// Interactions
await page.getByRole('button').click();
await page.getByLabel('Username').fill('andi');
await page.getByRole('checkbox').check();
await page.getByLabel('Country').selectOption('Germany');

// Advanced
await page.getByText('Menu').hover();
await page.getByRole('textbox').focus();
await page.keyboard.press('Enter');
await page.getByLabel('Upload').setInputFiles('file.pdf');

🔗 Filtering & Chaining Locators

// Find element within another
const product = page.getByRole('listitem').filter({ hasText: 'Product A' });
await product.getByRole('button', { name: 'Add to cart' }).click();

// Filter by absent text
await expect(
  page.getByRole('listitem').filter({ hasNotText: 'Sold out' })
).toHaveCount(5);

// Combined filters
await page
  .getByRole('listitem')
  .filter({ hasText: 'John' })
  .filter({ has: page.getByRole('button', { name: 'Edit' }) })
  .click();

🛠️ Debugging & Development

# Test generator (record locators)
npx playwright codegen example.com

# Debug with inspector
npx playwright test --debug

# Enable trace for CI
npx playwright test --trace on
npx playwright show-report

# Debug single test
npx playwright test test.spec.ts:42 --debug

Useful Debugging Config

// playwright.config.ts
export default defineConfig({
  use: {
    // Local: full trace
    trace: 'on',
    // CI: only on retry
    // trace: 'on-first-retry',
    
    screenshot: 'only-on-failure',
    video: 'retain-on-failure',
  },
});

📋 Test Structure

import { test, expect } from '@playwright/test';

// Parallel execution in this file
test.describe.configure({ mode: 'parallel' });

test.describe('Authentication', () => {
  test.beforeEach(async ({ page }) => {
    await page.goto('/login');
  });

  test('successful login', async ({ page }) => {
    await page.getByLabel('Email').fill('test@example.com');
    await page.getByLabel('Password').fill('secret');
    await page.getByRole('button', { name: 'Sign in' }).click();
    
    await expect(page).toHaveURL('/dashboard');
    await expect(page.getByText('Welcome back')).toBeVisible();
  });

  test('failed login shows error', async ({ page }) => {
    await page.getByLabel('Email').fill('wrong@example.com');
    await page.getByRole('button', { name: 'Sign in' }).click();
    
    await expect(page.getByText('Invalid credentials')).toBeVisible();
  });
});

⚙️ Performance Optimization

// playwright.config.ts
export default defineConfig({
  // Parallel workers
  workers: process.env.CI ? 4 : undefined,
  
  // Retries on flakiness
  retries: process.env.CI ? 2 : 0,
  
  // Projects for different browsers
  projects: [
    { name: 'chromium', use: { ...devices['Desktop Chrome'] } },
    { name: 'firefox', use: { ...devices['Desktop Firefox'] } },
    { name: 'webkit', use: { ...devices['Desktop Safari'] } },
  ],
});

🚨 Common Mistakes

MistakeSolution
Using sleep(1000)Use auto-wait, expect().toBeVisible()
CSS selectors like #btn-123Use getByRole() or getByTestId()
Tests depend on other tests’ stateUse test.beforeEach for isolation
No assertions after actionAlways use expect()
Hard-coded timeoutsexpect(...).toBeVisible({ timeout: 10000 })
Testing external sitesMock with page.route()

🔗 Further Reading


This cheat sheet is updated regularly. Last updated: April 2026

Mehr praktische Ressourcen im Blog

Zur Kompakt-Version →