PL

playwright-best-practices

A guide for writing robust Playwright E2E tests by using semantic selectors and auto-waiting while avoiding flaky patterns like hardcoded delays.

Install

mkdir -p .claude/skills/playwright-best-practices && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/19057" && unzip -o skill.zip -d .claude/skills/playwright-best-practices && rm skill.zip

Installs to .claude/skills/playwright-best-practices

Activation

This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.

Playwright best practices including selectors, wait strategies, accessibility testing, responsive design, and flaky-test prevention. Use when writing or improving Playwright E2E tests.
184 chars✓ has a “when” trigger
Intermediate

Key capabilities

  • Use semantic selectors like `getByRole` and `getByTestId`.
  • Employ auto-waiting and explicit assertions instead of hardcoded delays.
  • Test accessibility using `getByRole` and keyboard navigation.
  • Isolate tests to ensure independence and clean up test data.
  • Test responsive designs across multiple viewports.
  • Prevent flaky tests by using proper waits and stable selectors.

How it works

The skill provides best practices for Playwright tests, focusing on semantic selectors, auto-waiting strategies, accessibility testing, responsive design, and test isolation to prevent flakiness.

Inputs & outputs

You give it
Playwright E2E tests that need improvement or a request to write new tests.
You get back
Reliable, maintainable Playwright tests that use semantic selectors, proper waits, and include accessibility and responsive design checks.

When to use playwright-best-practices

  • Improving test stability
  • Refactoring flaky E2E tests
  • Implementing accessibility checks
  • Testing responsive designs

About this skill

Playwright Best Practices

Expert guidance for writing reliable, maintainable Playwright tests.

Quick Reference

ConcernBest PracticeAvoid
SelectorsgetByRole, getByTestId, getByLabelCSS classes, DOM structure
WaitsAuto-waiting, explicit assertionswaitForTimeout, hardcoded delays
AccessibilitygetByRole, a11y checksVisual-only testing
Flaky testsProper waits, stable selectorsTiming-dependent assertions
IsolationCleanup after each testTests depending on each other
ParallelIndependent testsShared state

Essential Principles

Use semantic selectors: getByRole, getByLabel, getByTestId are stable. CSS classes and DOM structure change frequently.

Never waitForTimeout: Hardcoded delays make tests slow and flaky. Use auto-waiting and explicit assertions.

Test accessibility: getByRole ensures accessible markup. Keyboard tests verify a11y.

Isolate tests: Each test should work independently. Clean up test data after each test.

Responsive testing: Test mobile, tablet, desktop viewports.

Selector Best Practices

// ❌ Bad: Fragile selectors
page.click('div > div > button')
page.click('.btn-primary')
page.click('#submit-btn-123')

// ✅ Good: Stable, semantic selectors
page.getByRole('button', { name: 'Submit' })
page.getByTestId('submit-button')
page.getByLabel('Email address')

Wait Strategies

// ❌ Bad: Hardcoded waits
page.waitForTimeout(5000)  // Flaky, slow

// ✅ Good: Explicit waits
await page.waitForURL('/dashboard')
await page.waitForSelector('[data-testid="success-message"]')
await expect(page.getByTestId('loading')).toBeHidden()
await page.waitForResponse(resp => resp.url().includes('/api/users') && resp.status() === 200)

Accessibility Testing

// Good: Semantic selectors enforce a11y
await page.getByRole('button', { name: 'Submit' }).click()

// Good: Keyboard navigation test
test('is keyboard navigable', async ({ page }) => {
  await page.goto('/form')
  await page.keyboard.press('Tab')
  await expect(page.getByTestId('name-input')).toBeFocused()
})

// Good: A11y assertions (with axe-core)
await expect(page).toHaveAccessibleTree()

Responsive Testing

test.describe('Mobile', () => {
  test.use({ viewport: { width: 375, height: 667 } })

  test('shows mobile menu', async ({ page }) => {
    await page.goto('/')
    await expect(page.getByTestId('hamburger-menu')).toBeVisible()
  })
})

Common Anti-Patterns

Anti-PatternSeverityFix
waitForTimeoutCriticalUse explicit waits/assertions
CSS class selectorsHighUse getByRole/getByTestId
Tests depending on each otherHighMake tests independent
No cleanupMediumUse fixtures with proper cleanup
Only desktop testingLowTest multiple viewports
Hardcoded test dataMediumUse data factories

Success Criteria

Tests are reliable when:

  • No waitForTimeout in tests
  • Selectors are semantic (getByRole, getByTestId)
  • Tests run in isolation (independent)
  • Test data cleaned up after each test
  • Multiple viewports tested
  • Accessibility assertions present
  • Tests are deterministic (no randomness)

When not to use it

  • When using CSS classes or DOM structure for selectors.
  • When using `waitForTimeout` or hardcoded delays.
  • When tests depend on each other or lack cleanup.

Limitations

  • Avoids `waitForTimeout` due to flakiness and slowness.
  • Discourages CSS class selectors due to fragility.
  • Requires tests to be independent and clean up after each run.

How it compares

This skill guides the user to write stable and maintainable Playwright tests by enforcing semantic selectors and proper wait strategies, unlike writing tests that rely on fragile DOM structures or hardcoded delays.

Compared to similar skills

playwright-best-practices side by side with the closest alternatives in the catalog.

SkillInstallsUpdatedSafetyDifficulty
playwright-best-practices (this skill)020dNo flagsIntermediate
dependency-upgrade264moReviewIntermediate
vitest416moNo flagsIntermediate
browser-tools68moReviewIntermediate

Try saying

Example prompts that trigger this skill in your AI assistant.

Search skills

Search the agent skills registry