Pro Playwright Skill
Boost productivity using this complete, browser, automation, playwright. Includes structured workflows, validation checks, and reusable patterns for utilities.
Playwright Testing
A professional end-to-end testing skill for web applications using Playwright, covering test writing, page object patterns, visual regression testing, and CI/CD integration.
When to Use
Choose Playwright when:
- Writing end-to-end tests that simulate real user interactions across browsers
- Testing web applications that require JavaScript rendering and dynamic content
- Building visual regression test suites with screenshot comparisons
- Running parallel cross-browser tests in CI/CD pipelines
Consider alternatives when:
- Writing unit tests for components — use Jest, Vitest, or Testing Library
- Testing REST APIs — use Supertest or Postman/Newman
- Performing simple smoke tests — use lightweight HTTP checking tools
Quick Start
# Initialize Playwright in a project npm init playwright@latest # Run tests npx playwright test # Run with UI mode for debugging npx playwright test --ui
import { test, expect, type Page } from '@playwright/test'; test.describe('Authentication', () => { test('should log in with valid credentials', async ({ page }) => { await page.goto('/login'); await page.fill('[data-testid="email"]', '[email protected]'); await page.fill('[data-testid="password"]', 'secure-password'); await page.click('[data-testid="login-button"]'); await expect(page).toHaveURL('/dashboard'); await expect(page.locator('[data-testid="welcome-message"]')) .toContainText('Welcome'); }); test('should show error for invalid credentials', async ({ page }) => { await page.goto('/login'); await page.fill('[data-testid="email"]', '[email protected]'); await page.fill('[data-testid="password"]', 'wrong'); await page.click('[data-testid="login-button"]'); await expect(page.locator('.error-alert')).toBeVisible(); await expect(page.locator('.error-alert')) .toContainText('Invalid credentials'); }); }); // API mocking for isolated testing test('should display products from API', async ({ page }) => { await page.route('**/api/products', async (route) => { await route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify([ { id: 1, name: 'Product A', price: 29.99 }, { id: 2, name: 'Product B', price: 49.99 } ]) }); }); await page.goto('/products'); await expect(page.locator('.product-card')).toHaveCount(2); await expect(page.locator('.product-card').first()).toContainText('Product A'); });
Core Concepts
Test Organization Patterns
| Pattern | Use Case | Files |
|---|---|---|
| Page Object Model | UI interaction abstraction | pages/*.ts |
| Fixtures | Shared setup/teardown | fixtures/*.ts |
| Test Helpers | Common assertions/actions | helpers/*.ts |
| API Mocks | Deterministic data | mocks/*.ts |
| Visual Baselines | Screenshot references | screenshots/*.png |
Page Object Model Implementation
// pages/DashboardPage.ts import { type Page, type Locator, expect } from '@playwright/test'; export class DashboardPage { readonly page: Page; readonly heading: Locator; readonly statsCards: Locator; readonly searchInput: Locator; readonly userMenu: Locator; constructor(page: Page) { this.page = page; this.heading = page.locator('h1'); this.statsCards = page.locator('[data-testid="stat-card"]'); this.searchInput = page.locator('[data-testid="search"]'); this.userMenu = page.locator('[data-testid="user-menu"]'); } async goto() { await this.page.goto('/dashboard'); await expect(this.heading).toBeVisible(); } async search(query: string) { await this.searchInput.fill(query); await this.searchInput.press('Enter'); await this.page.waitForResponse('**/api/search*'); } async getStats() { const cards = await this.statsCards.all(); return Promise.all(cards.map(async (card) => ({ label: await card.locator('.label').textContent(), value: await card.locator('.value').textContent() }))); } async logout() { await this.userMenu.click(); await this.page.click('[data-testid="logout"]'); await expect(this.page).toHaveURL('/login'); } } // tests/dashboard.spec.ts import { test, expect } from '@playwright/test'; import { DashboardPage } from '../pages/DashboardPage'; test('should display dashboard stats', async ({ page }) => { const dashboard = new DashboardPage(page); await dashboard.goto(); const stats = await dashboard.getStats(); expect(stats.length).toBeGreaterThan(0); expect(stats[0].value).toBeDefined(); });
Configuration
| Option | Description | Default |
|---|---|---|
baseURL | Application base URL for tests | "http://localhost:3000" |
browsers | Browsers to test: chromium, firefox, webkit | ["chromium"] |
headless | Run browsers in headless mode | true |
workers | Parallel test workers | 50% of CPU cores |
retries | Number of test retries on failure | 0 (2 in CI) |
timeout | Test timeout in milliseconds | 30000 |
screenshot | Screenshot on failure: on, off, only-on-failure | "only-on-failure" |
trace | Trace recording: on, off, on-first-retry | "on-first-retry" |
Best Practices
- Use
data-testidattributes for selectors instead of CSS classes or dynamic IDs — test IDs are stable across redesigns and make it clear which elements are used by tests, preventing accidental breakage - Enable tracing on first retry in CI so that failing tests produce a downloadable trace file showing every action, network request, and screenshot — this eliminates the "works on my machine" debugging problem
- Mock external APIs using
page.route()to make tests deterministic and fast — real API calls introduce flakiness from network latency, rate limits, and changing data - Run tests in parallel across browsers by configuring Playwright projects for chromium, firefox, and webkit, letting the test runner distribute work across available CPU cores
- Use visual regression testing sparingly for critical UI components like checkout forms and landing pages, not for every page — too many screenshot tests create a maintenance burden when the design evolves intentionally
Common Issues
Flaky tests in CI but stable locally: CI environments are slower and have different timing characteristics. Use Playwright's auto-waiting instead of manual timeouts, add waitForResponse after actions that trigger API calls, and enable retries in CI with retries: 2 in the Playwright config while keeping retries at 0 locally to catch issues early.
Authentication state not shared between tests: Each test starts with a fresh browser context. Use Playwright's storageState feature to save authentication cookies after a setup step, then load that state in subsequent tests so every test does not need to log in independently.
Visual regression tests failing on different OS: Font rendering differs between macOS, Linux, and Windows, causing screenshot diffs. Run visual tests in Docker containers with consistent fonts and rendering, or use a platform-aware threshold that allows minor pixel differences while still catching meaningful visual regressions.
Reviews
No reviews yet. Be the first to review this template!
Similar Templates
Full-Stack Code Reviewer
Comprehensive code review skill that checks for security vulnerabilities, performance issues, accessibility, and best practices across frontend and backend code.
Test Suite Generator
Generates comprehensive test suites with unit tests, integration tests, and edge cases. Supports Jest, Vitest, Pytest, and Go testing.
Pro Architecture Workspace
Battle-tested skill for architectural, decision, making, framework. Includes structured workflows, validation checks, and reusable patterns for development.