playwright-testing
SKILL.md
Playwright Testing
Overview
This skill helps AI agents write reliable end-to-end tests using Playwright. It covers project setup, writing tests with auto-waiting locators, page object patterns, authentication handling, API mocking, visual regression, accessibility testing, and CI/CD integration.
Instructions
Step 1: Project Setup
npm init playwright@latest
# Or add to existing project:
npm install -D @playwright/test && npx playwright install
// playwright.config.ts
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests',
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: [['html', { open: 'never' }], ['junit', { outputFile: 'test-results/junit.xml' }]],
use: {
baseURL: process.env.BASE_URL || '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-chrome', use: { ...devices['Pixel 5'] } },
],
webServer: {
command: 'npm run dev',
url: 'http://localhost:3000',
reuseExistingServer: !process.env.CI,
},
});
Step 2: Write Tests
import { test, expect } from '@playwright/test';
test.describe('Homepage', () => {
test('displays hero and navigates to features', async ({ page }) => {
await page.goto('/');
await expect(page.getByRole('heading', { name: /welcome/i })).toBeVisible();
await page.getByRole('link', { name: 'View Features' }).click();
await expect(page).toHaveURL(/.*features/);
});
test('shows search results', async ({ page }) => {
await page.goto('/');
await page.getByPlaceholder('Search...').fill('playwright');
await page.getByPlaceholder('Search...').press('Enter');
await expect(page.getByTestId('search-results')).toBeVisible();
await expect(page.getByTestId('search-result-item')).toHaveCount(10);
});
});
Step 3: Authentication Pattern
// tests/auth.setup.ts — authenticate once, reuse across tests
import { test as setup } from '@playwright/test';
import path from 'path';
const authFile = path.join(__dirname, '.auth/user.json');
setup('authenticate', async ({ page }) => {
await page.goto('/login');
await page.getByLabel('Email').fill('test@example.com');
await page.getByLabel('Password').fill('password123');
await page.getByRole('button', { name: 'Sign in' }).click();
await expect(page).toHaveURL('/dashboard');
await page.context().storageState({ path: authFile });
});
// In config: add setup dependency
// projects: [
// { name: 'setup', testMatch: /.*\.setup\.ts/ },
// { name: 'chromium', use: { ...devices['Desktop Chrome'], storageState: 'tests/.auth/user.json' }, dependencies: ['setup'] },
// ]
Step 4: Page Object Pattern
// tests/pages/login.page.ts
import { Page, Locator, expect } from '@playwright/test';
export class LoginPage {
readonly emailInput: Locator;
readonly passwordInput: Locator;
readonly submitButton: Locator;
readonly errorMessage: Locator;
constructor(private 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'); }
async login(email: string, password: string) {
await this.emailInput.fill(email);
await this.passwordInput.fill(password);
await this.submitButton.click();
}
async expectError(msg: string) { await expect(this.errorMessage).toContainText(msg); }
}
// tests/login.spec.ts
test.describe('Login', () => {
let loginPage: LoginPage;
test.beforeEach(async ({ page }) => { loginPage = new LoginPage(page); await loginPage.goto(); });
test('successful login redirects to dashboard', async ({ page }) => {
await loginPage.login('test@example.com', 'password123');
await expect(page).toHaveURL('/dashboard');
});
test('invalid credentials show error', async () => {
await loginPage.login('wrong@example.com', 'wrong');
await loginPage.expectError('Invalid email or password');
});
});
Step 5: API Mocking
test('shows error state when API fails', async ({ page }) => {
await page.route('**/api/projects', (route) => route.fulfill({
status: 500, contentType: 'application/json',
body: JSON.stringify({ error: 'Internal server error' }),
}));
await page.goto('/dashboard');
await expect(page.getByText('Failed to load projects')).toBeVisible();
});
test('modify API response for premium features', async ({ page }) => {
await page.route('**/api/user', async (route) => {
const response = await route.fetch();
const json = await response.json();
json.plan = 'enterprise';
await route.fulfill({ response, json });
});
await page.goto('/settings');
await expect(page.getByText('Enterprise Plan')).toBeVisible();
});
Step 6: Visual & Accessibility Testing
test('homepage visual regression', async ({ page }) => {
await page.goto('/');
await expect(page).toHaveScreenshot('homepage.png', { fullPage: true, maxDiffPixelRatio: 0.01 });
});
// Update snapshots: npx playwright test --update-snapshots
import AxeBuilder from '@axe-core/playwright';
test('no accessibility violations', async ({ page }) => {
await page.goto('/');
const results = await new AxeBuilder({ page }).withTags(['wcag2a', 'wcag2aa']).analyze();
expect(results.violations).toEqual([]);
});
Step 7: CI Integration
# .github/workflows/e2e.yml
name: E2E Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 20, cache: npm }
- run: npm ci
- run: npx playwright install --with-deps
- run: npx playwright test
- uses: actions/upload-artifact@v4
if: always()
with: { name: playwright-report, path: playwright-report/, retention-days: 30 }
Useful Commands
npx playwright test # Run all tests
npx playwright test tests/login.spec.ts # Specific file
npx playwright test --headed # See browser
npx playwright test --project=chromium # Specific browser
npx playwright test --debug # Step-through debugger
npx playwright codegen http://localhost:3000 # Record actions
npx playwright show-report # View HTML report
Examples
Example 1: Add e2e tests for a login flow with page objects
User prompt: "Write Playwright tests for our login page that test successful login, invalid credentials, and form validation, using the page object pattern."
The agent will:
- Create
tests/pages/login.page.tswith locators for email, password, submit button, and error message using role-based selectors (getByLabel,getByRole) - Add
goto(),login(), andexpectError()methods to the page object - Write three tests: successful login verifying redirect to
/dashboard, invalid credentials checking the error alert, and empty form submission checking validation messages - Set up
beforeEachto instantiate the page object and navigate to/login
Example 2: Mock API responses to test error and empty states
User prompt: "Add tests for our dashboard that verify it handles API errors gracefully and shows an empty state when there are no projects."
The agent will:
- Use
page.route('**/api/projects', ...)to intercept the API call - For the error test: fulfill with status 500 and verify the "Failed to load projects" message and Retry button appear
- For the empty test: fulfill with status 200 and an empty projects array, then verify the "No projects yet" empty state is shown
- Both tests run in isolation without needing a real backend, making them fast and reliable in CI
Guidelines
- Use role-based locators (
getByRole,getByLabel,getByText) over CSS selectors — more resilient to DOM changes - Add
data-testidattributes only when no semantic locator works - Never use
page.waitForTimeout()— use auto-waiting locators orexpectwith timeout - Run auth setup once and share state across tests via
storageState - Use page objects for complex pages to keep tests readable
- Mock external APIs in tests — test UI behavior, not third-party services
- Run tests in parallel (
fullyParallel: true) for speed - Capture traces on first retry — invaluable for debugging flaky tests in CI
- Use
webServerconfig to auto-start your dev server during tests - Keep visual snapshots in version control and review changes in PRs
Weekly Installs
1
Repository
terminalskills/skillsGitHub Stars
15
First Seen
6 days ago
Security Audits
Installed on
opencode1
codex1
claude-code1