playwright-testing

Playwright Testing Expertise

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "playwright-testing" with this command: npx skills add c0ntr0lledcha0s/claude-code-plugin-automations/c0ntr0lledcha0s-claude-code-plugin-automations-playwright-testing

Playwright Testing Expertise

You are an expert in Playwright testing framework with deep knowledge of browser automation, selectors, page objects, and best practices for end-to-end testing.

Your Capabilities

  • Playwright Configuration: Projects, browsers, reporters, and fixtures

  • Locators & Selectors: Role-based, text, CSS, and chained locators

  • Page Object Model: Organizing tests with page objects

  • Assertions: Built-in assertions, custom matchers, auto-waiting

  • Test Fixtures: Built-in and custom fixtures, test isolation

  • Debugging: Traces, screenshots, videos, and Playwright Inspector

  • API Testing: Request fixtures and API testing capabilities

When to Use This Skill

Claude should automatically invoke this skill when:

  • The user mentions Playwright, playwright.config, or Playwright features

  • Files matching *.spec.ts in e2e, tests, or playwright directories are encountered

  • The user asks about locators, page objects, or browser automation

  • E2E or integration testing is discussed

  • Browser testing configuration is needed

How to Use This Skill

Accessing Resources

Use {baseDir} to reference files in this skill directory:

  • Scripts: {baseDir}/scripts/

  • Documentation: {baseDir}/references/

  • Templates: {baseDir}/assets/

Available Resources

This skill includes ready-to-use resources in {baseDir} :

  • references/playwright-cheatsheet.md - Quick reference for locators, assertions, actions, and CLI commands

  • assets/page-object.template.ts - Complete Page Object Model template with base class and examples

  • scripts/check-playwright-setup.sh - Validates Playwright configuration and browser installation

Playwright Best Practices

Test Structure

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

test.describe('Contact Form', () => { test.beforeEach(async ({ page }) => { await page.goto('/contact'); });

test('should show success message after form submission', async ({ page }) => { // Arrange await page.getByLabel('Name').fill('Test User'); await page.getByLabel('Email').fill('test@example.com'); await page.getByLabel('Message').fill('Hello, this is a test message.');

// Act
await page.getByRole('button', { name: 'Submit' }).click();

// Assert
await expect(page.getByText('Thank you for your message')).toBeVisible();
await expect(page.getByLabel('Name')).toBeEmpty();

}); });

Locator Best Practices

Preferred Locators (Most Resilient)

// Role-based (best) page.getByRole('button', { name: 'Submit' }); page.getByRole('textbox', { name: 'Email' }); page.getByRole('heading', { level: 1 });

// Label-based page.getByLabel('Email address'); page.getByPlaceholder('Enter your email');

// Text-based page.getByText('Welcome'); page.getByTitle('Close');

Chaining Locators

page.getByRole('listitem') .filter({ hasText: 'Product 1' }) .getByRole('button', { name: 'Add' });

Test IDs (Last Resort)

page.getByTestId('submit-button');

Page Object Pattern

// pages/login.page.ts import { Page, Locator, expect } from '@playwright/test';

export class LoginPage { private readonly page: Page; readonly emailInput: Locator; readonly passwordInput: Locator; readonly submitButton: Locator; readonly errorMessage: Locator;

constructor(page: Page) { this.page = page; this.emailInput = page.getByLabel('Email'); this.passwordInput = page.getByLabel('Password'); this.submitButton = page.getByRole('button', { name: 'Sign in' }); this.errorMessage = page.getByRole('alert'); }

async goto() { await this.page.goto('/login'); await expect(this.emailInput).toBeVisible(); }

async login(email: string, password: string) { await this.emailInput.fill(email); await this.passwordInput.fill(password); await this.submitButton.click(); }

async getError(): Promise<string | null> { if (await this.errorMessage.isVisible()) { return this.errorMessage.textContent(); } return null; } }

// Usage in test import { test, expect } from '@playwright/test'; import { LoginPage } from './pages/login.page';

test('should login successfully', async ({ page }) => { const loginPage = new LoginPage(page); await loginPage.goto(); await loginPage.login('user@test.com', 'password'); await expect(page).toHaveURL('/dashboard'); });

test('should show error for invalid credentials', async ({ page }) => { const loginPage = new LoginPage(page); await loginPage.goto(); await loginPage.login('invalid@test.com', 'wrongpassword'); const error = await loginPage.getError(); expect(error).toContain('Invalid credentials'); });

Auto-Waiting & Assertions

// Auto-waits for element await expect(page.getByRole('alert')).toBeVisible(); await expect(page.getByRole('button')).toBeEnabled(); await expect(page.getByText('Count: 5')).toBeVisible();

// Negative assertions await expect(page.getByRole('dialog')).toBeHidden(); await expect(page.getByText('Error')).not.toBeVisible();

// With custom timeout await expect(page.getByText('Loaded')).toBeVisible({ timeout: 10000 });

Fixtures

// fixtures.ts import { test as base } from '@playwright/test';

export const test = base.extend<{ authenticatedPage: Page; }>({ authenticatedPage: async ({ page }, use) => { await page.goto('/login'); await page.getByLabel('Email').fill('test@test.com'); await page.getByLabel('Password').fill('password'); await page.getByRole('button', { name: 'Login' }).click(); await page.waitForURL('/dashboard'); await use(page); }, });

Storage State Authentication

For efficient authentication without UI login each time:

// Setup: Save auth state after login (run once) // auth.setup.ts import { test as setup, expect } from '@playwright/test';

setup('authenticate', async ({ page }) => { await page.goto('/login'); await page.getByLabel('Email').fill('test@example.com'); await page.getByLabel('Password').fill('password'); await page.getByRole('button', { name: 'Sign in' }).click(); await expect(page).toHaveURL('/dashboard');

// Save storage state (cookies, localStorage) await page.context().storageState({ path: '.auth/user.json' }); });

// playwright.config.ts export default defineConfig({ projects: [ { name: 'setup', testMatch: /.*.setup.ts/ }, { name: 'chromium', use: { storageState: '.auth/user.json' }, dependencies: ['setup'], }, ], });

// Tests automatically have auth state test('dashboard loads for authenticated user', async ({ page }) => { await page.goto('/dashboard'); await expect(page.getByText('Welcome back')).toBeVisible(); });

Network Mocking & Interception

Mock API responses for reliable, fast tests:

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

test('should display mocked user data', async ({ page }) => { // Mock API response await page.route('**/api/users', route => { route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify([ { id: 1, name: 'Test User', email: 'test@example.com' } ]), }); });

await page.goto('/users'); await expect(page.getByText('Test User')).toBeVisible(); });

test('should handle API errors gracefully', async ({ page }) => { // Mock error response await page.route('**/api/users', route => { route.fulfill({ status: 500, body: JSON.stringify({ error: 'Internal Server Error' }), }); });

await page.goto('/users'); await expect(page.getByText('Failed to load users')).toBeVisible(); });

test('should handle network failure', async ({ page }) => { // Abort network request await page.route('**/api/data', route => route.abort());

await page.goto('/data'); await expect(page.getByText('Network error')).toBeVisible(); });

test('should handle slow responses', async ({ page }) => { // Simulate slow API await page.route('**/api/slow', async route => { await new Promise(resolve => setTimeout(resolve, 3000)); await route.continue(); });

await page.goto('/slow-page'); await expect(page.getByText('Loading...')).toBeVisible(); });

// Modify request/response test('should modify request headers', async ({ page }) => { await page.route('/api/', route => { route.continue({ headers: { ...route.request().headers(), 'X-Test-Header': 'test-value', }, }); }); });

Accessibility Testing

Integrate accessibility audits with @axe-core/playwright:

// Install: npm install @axe-core/playwright import { test, expect } from '@playwright/test'; import AxeBuilder from '@axe-core/playwright';

test('should pass accessibility audit', async ({ page }) => { await page.goto('/');

const results = await new AxeBuilder({ page }).analyze();

expect(results.violations).toEqual([]); });

test('should pass accessibility audit for specific section', async ({ page }) => { await page.goto('/dashboard');

const results = await new AxeBuilder({ page }) .include('#main-content') .exclude('#third-party-widget') .withTags(['wcag2a', 'wcag2aa']) .analyze();

expect(results.violations).toEqual([]); });

// Check specific rules test('should have proper color contrast', async ({ page }) => { await page.goto('/');

const results = await new AxeBuilder({ page }) .withRules(['color-contrast']) .analyze();

expect(results.violations).toEqual([]); });

// Detailed violation reporting test('accessibility check with detailed report', async ({ page }) => { await page.goto('/');

const results = await new AxeBuilder({ page }).analyze();

if (results.violations.length > 0) { console.log('Accessibility violations:'); results.violations.forEach(violation => { console.log(- ${violation.id}: ${violation.description}); violation.nodes.forEach(node => { console.log( Element: ${node.html}); console.log( Fix: ${node.failureSummary}); }); }); }

expect(results.violations).toEqual([]); });

Visual Regression Testing

Compare screenshots to detect visual changes:

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

test('homepage visual regression', async ({ page }) => { await page.goto('/');

// Full page screenshot comparison await expect(page).toHaveScreenshot('homepage.png'); });

test('component visual regression', async ({ page }) => { await page.goto('/components');

// Element-specific screenshot const button = page.getByRole('button', { name: 'Submit' }); await expect(button).toHaveScreenshot('submit-button.png'); });

test('visual with threshold', async ({ page }) => { await page.goto('/');

// Allow small differences await expect(page).toHaveScreenshot('homepage.png', { maxDiffPixels: 100, threshold: 0.2, }); });

// Update snapshots: npx playwright test --update-snapshots

Playwright Configuration

Basic Configuration

// playwright.config.ts import { defineConfig, devices } from '@playwright/test';

export default defineConfig({ testDir: './e2e', fullyParallel: true, forbidOnly: !!process.env.CI, retries: process.env.CI ? 2 : 0, workers: process.env.CI ? 1 : undefined, reporter: 'html', use: { baseURL: 'http://localhost:3000', trace: 'on-first-retry', screenshot: 'only-on-failure', }, projects: [ { name: 'chromium', use: { ...devices['Desktop Chrome'] } }, { name: 'firefox', use: { ...devices['Desktop Firefox'] } }, { name: 'webkit', use: { ...devices['Desktop Safari'] } }, { name: 'mobile', use: { ...devices['iPhone 13'] } }, ], webServer: { command: 'npm run start', url: 'http://localhost:3000', reuseExistingServer: !process.env.CI, }, });

Debugging & Troubleshooting

Debug Mode

npx playwright test --debug npx playwright test --ui

Traces

// Capture trace on failure use: { trace: 'on-first-retry', }

// View trace npx playwright show-trace trace.zip

Screenshots

await page.screenshot({ path: 'screenshot.png', fullPage: true });

Common Issues & Solutions

Issue: Flaky tests

  • Use auto-waiting assertions instead of fixed waits

  • Wait for specific elements instead of networkidle (which fails with WebSockets, long-polling, analytics): // Bad: networkidle is unreliable await page.waitForLoadState('networkidle');

// Good: wait for specific content await expect(page.getByRole('main')).toBeVisible(); await expect(page.getByTestId('data-loaded')).toBeAttached();

  • Ensure proper test isolation

Issue: Elements not found

  • Use Playwright Inspector to find better locators

  • Prefer role-based selectors

  • Check for iframes or shadow DOM

Issue: Slow tests

  • Reuse authentication state

  • Use test.describe.parallel()

  • Mock slow API calls

Examples

Example 1: Form Testing

When testing forms:

  • Use getByLabel for inputs

  • Use fill() instead of type() for speed

  • Submit with button locator

  • Assert on success message or navigation

Example 2: Table Testing

When testing tables/lists:

  • Use getByRole('row') for table rows

  • Filter by content with .filter()

  • Chain to find actions within rows

  • Assert on row count or content

Version Compatibility

The patterns in this skill require the following minimum versions:

Feature Minimum Version Notes

getByRole with name 1.27+ Role-based locators with accessible name

toHaveScreenshot 1.22+ Visual regression testing

storageState 1.13+ Authentication state persistence

@axe-core/playwright 4.7+ Accessibility testing integration

route.fulfill 1.0+ Network mocking (stable)

test.describe.configure 1.24+ Parallel/serial test configuration

Feature Detection

Check your Playwright version:

npx playwright --version

Upgrading

Update Playwright

npm install -D @playwright/test@latest

Update browsers

npx playwright install

Important Notes

  • Playwright is automatically invoked when relevant

  • Always check playwright.config.ts for project settings

  • Prefer role-based locators for resilient tests

  • Use auto-waiting assertions instead of explicit waits

  • Consider mobile and cross-browser testing in CI

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

writing-docs

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

investigating-codebases

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

building-mcp-servers

No summary provided by upstream source.

Repository SourceNeeds Review