
qa-testing-playwright
by vasilyu1983
SKILL.md
name: qa-testing-playwright description: "End-to-end web application testing with Playwright: scope control, stable selectors, parallelization/sharding, flake control, network mocking vs real services, visual testing tradeoffs, and CI/CD integration."
QA Testing (Playwright, Jan 2026) — Quick Reference
This skill enables high-signal, cost-aware E2E testing of web applications using Playwright.
Core references: Playwright best practices (https://playwright.dev/docs/best-practices), locators (https://playwright.dev/docs/locators), retries (https://playwright.dev/docs/test-retries), sharding (https://playwright.dev/docs/test-sharding), and the trace viewer (https://playwright.dev/docs/trace-viewer).
Core QA (Default)
Scope Control (What E2E Is For)
- E2E exists to protect critical user journeys and high-risk integrations.
- Keep E2E thin; push most coverage down to unit/integration/contract tests.
- Test the contract and user intent, not CSS structure.
Selector Strategy (Stability First)
- Prefer user-facing locators:
getByRole,getByLabel,getByText(Playwright locators: https://playwright.dev/docs/locators). - Use
data-testidas a last resort for complex widgets or non-semantic UI. - Avoid XPath and brittle CSS selectors.
Flake Control (Make CI Reliable)
- Avoid sleeps; rely on Playwright auto-wait and web-first assertions (https://playwright.dev/docs/best-practices).
- Use retries as a signal, not a crutch; investigate rerun-pass tests as flakes (https://playwright.dev/docs/test-retries).
- Always capture traces/screenshots/video on failure; use trace viewer to localize root cause (https://playwright.dev/docs/trace-viewer).
- If clicks are intercepted by overlays, prefer keyboard activation or close the overlay rather than
force: true.
CI Economics (Parallelize and Shard)
- Run smoke E2E on PRs; run full regression on schedule or per-release [Inference].
- Shard long suites across machines to keep PR feedback fast (https://playwright.dev/docs/test-sharding).
Do / Avoid
Do:
- Make tests independent and deterministic (isolated state, stable data).
- Use network mocking for third-party dependencies; test your integration contract, not their UI.
Avoid:
- “Test everything E2E” as a default.
- Weakening assertions to “fix” flakes (silent regressions).
Quick Reference
| Task | Command | When to Use |
|---|---|---|
| Init Playwright | npm init playwright@latest | New project setup |
| Run all tests | npx playwright test | Full test suite |
| Run with UI | npx playwright test --ui | Debugging, visual mode |
| Run specific test | npx playwright test login.spec.ts | Targeted testing |
| Generate code | npx playwright codegen | Record interactions |
| Show report | npx playwright show-report | View test results |
When to Use This Skill
Claude should invoke this skill when a user requests:
- Write E2E tests for web application
- Test user authentication flows
- Verify form submissions and validation
- Test responsive designs across viewports
- Automate browser interactions
- Set up Playwright in CI/CD
- Debug failing E2E tests
Quick Start
Installation
# Initialize Playwright
npm init playwright@latest
# Or add to existing project
npm install -D @playwright/test
npx playwright install
Project Structure
project/
├── playwright.config.ts
├── tests/
│ ├── auth.spec.ts
│ ├── checkout.spec.ts
│ └── fixtures/
│ └── auth.fixture.ts
├── pages/
│ ├── LoginPage.ts
│ └── DashboardPage.ts
└── .github/workflows/
└── playwright.yml
Core Testing Patterns
Browser Policy (Bundled vs Stable Channels)
- Default: bundled browsers are designed for reliable automation.
- When you must test against stable Chrome/Edge (policy/compliance, media codecs), use browser channels (see Playwright “Browsers”: https://playwright.dev/docs/browsers).
Locator Priority (2025 Best Practice)
Use role locators as the primary approach — they test from the user's perspective:
// 1. Role locators (preferred)
await page.getByRole('textbox', { name: 'Email' }).fill('user@example.com');
await page.getByRole('button', { name: 'Sign in' }).click();
await expect(page.getByRole('heading', { name: 'Dashboard' })).toBeVisible();
// 2. Label/text locators
await page.getByLabel('Email').fill('user@example.com');
await page.getByText('Sign in').click();
// 3. Test IDs (fallback for complex scenarios)
await page.getByTestId('user-avatar').click();
Common UI Flake Patterns
- Overlay intercepts pointer events: use keyboard activation to follow the accessible path.
- Menus: open via
buttonrole, selectmenuitemrole to avoid hidden link clicks.
const menuButton = page.getByRole('button', { name: /open menu/i });
await menuButton.focus();
await menuButton.press('Enter');
const menuItem = page.getByRole('menuitem', { name: /year ahead/i });
await menuItem.focus();
await menuItem.press('Enter');
Basic Test Structure
import { test, expect } from '@playwright/test';
test.describe('User Authentication', () => {
test('should login with valid credentials', async ({ page }) => {
await page.goto('/login');
await page.getByRole('textbox', { name: 'Email' }).fill('user@example.com');
await page.getByLabel('Password').fill('password123');
await page.getByRole('button', { name: 'Sign in' }).click();
await expect(page).toHaveURL('/dashboard');
await expect(page.getByRole('heading', { name: 'Dashboard' })).toBeVisible();
});
test('should show error for invalid credentials', async ({ page }) => {
await page.goto('/login');
await page.getByRole('textbox', { name: 'Email' }).fill('wrong@example.com');
await page.getByLabel('Password').fill('wrongpassword');
await page.getByRole('button', { name: 'Sign in' }).click();
await expect(page.getByRole('alert')).toContainText('Invalid');
});
});
Page Object Model
// pages/LoginPage.ts
import { Page, Locator, expect } from '@playwright/test';
export class LoginPage {
readonly page: Page;
readonly emailInput: Locator;
readonly passwordInput: Locator;
readonly loginButton: Locator;
readonly errorMessage: Locator;
constructor(page: Page) {
this.page = page;
this.emailInput = page.getByRole('textbox', { name: 'Email' });
this.passwordInput = page.getByLabel('Password');
this.loginButton = 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.loginButton.click();
}
async expectError(message: string) {
await expect(this.errorMessage).toBeVisible();
await expect(this.errorMessage).toContainText(message);
}
}
// tests/login.spec.ts
import { test, expect } from '@playwright/test';
import { LoginPage } from '../pages/LoginPage';
test('login with valid credentials', async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.goto();
await loginPage.login('user@example.com', 'password123');
await expect(page).toHaveURL('/dashboard');
});
Authentication Fixture
// fixtures/auth.fixture.ts
import { test as base, expect } from '@playwright/test';
type AuthFixtures = {
authenticatedPage: Page;
};
export const test = base.extend<AuthFixtures>({
authenticatedPage: async ({ page }, use) => {
// Login before test
await page.goto('/login');
await page.getByRole('textbox', { name: 'Email' }).fill('user@example.com');
await page.getByLabel('Password').fill('password123');
await page.getByRole('button', { name: 'Sign in' }).click();
await expect(page).toHaveURL('/dashboard');
// Use authenticated page in test
await use(page);
},
});
// tests/dashboard.spec.ts
import { test } from '../fixtures/auth.fixture';
import { expect } from '@playwright/test';
test('authenticated user can access dashboard', async ({ authenticatedPage }) => {
await authenticatedPage.goto('/dashboard');
await expect(authenticatedPage.getByRole('heading', { name: 'Dashboard' })).toBeVisible();
});
Common Scenarios
Form Testing
test('should validate form fields', async ({ page }) => {
await page.goto('/signup');
// Submit empty form
await page.click('[data-testid="submit"]');
// Check validation messages
await expect(page.locator('[data-testid="email-error"]')).toHaveText('Email is required');
await expect(page.locator('[data-testid="password-error"]')).toHaveText('Password is required');
// Fill with invalid email
await page.fill('[data-testid="email"]', 'invalid-email');
await page.click('[data-testid="submit"]');
await expect(page.locator('[data-testid="email-error"]')).toHaveText('Invalid email format');
});
API Mocking
test('should handle API errors gracefully', async ({ page }) => {
// Mock API to return error
await page.route('**/api/users', route => {
route.fulfill({
status: 500,
body: JSON.stringify({ error: 'Server error' }),
});
});
await page.goto('/users');
await expect(page.locator('[data-testid="error-banner"]')).toContainText('Failed to load');
});
test('should display user data from API', async ({ page }) => {
// Mock successful response
await page.route('**/api/users', route => {
route.fulfill({
status: 200,
body: JSON.stringify([
{ id: 1, name: 'John Doe' },
{ id: 2, name: 'Jane Smith' },
]),
});
});
await page.goto('/users');
await expect(page.locator('[data-testid="user-list"] li')).toHaveCount(2);
});
Responsive Testing
import { devices } from '@playwright/test';
test.describe('Responsive Design', () => {
test('mobile navigation', async ({ page }) => {
await page.setViewportSize({ width: 375, height: 667 }); // iPhone SE
await page.goto('/');
// Mobile menu should be hidden
await expect(page.locator('[data-testid="desktop-nav"]')).toBeHidden();
// Hamburger menu should be visible
await expect(page.locator('[data-testid="mobile-menu-button"]')).toBeVisible();
// Open mobile menu
await page.click('[data-testid="mobile-menu-button"]');
await expect(page.locator('[data-testid="mobile-nav"]')).toBeVisible();
});
});
// playwright.config.ts - Multiple devices
export default defineConfig({
projects: [
{ name: 'Desktop Chrome', use: { ...devices['Desktop Chrome'] } },
{ name: 'Mobile Safari', use: { ...devices['iPhone 12'] } },
{ name: 'Tablet', use: { ...devices['iPad Pro'] } },
],
});
Visual Regression
test('homepage visual regression', async ({ page }) => {
await page.goto('/');
await expect(page).toHaveScreenshot('homepage.png', {
maxDiffPixels: 100,
});
});
test('button states', async ({ page }) => {
await page.goto('/components');
const button = page.locator('[data-testid="primary-button"]');
// Default state
await expect(button).toHaveScreenshot('button-default.png');
// Hover state
await button.hover();
await expect(button).toHaveScreenshot('button-hover.png');
// Focus state
await button.focus();
await expect(button).toHaveScreenshot('button-focus.png');
});
Configuration
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'],
['junit', { outputFile: 'results.xml' }],
],
use: {
baseURL: 'http://localhost:3000',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
video: 'retain-on-failure',
},
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
{ name: 'firefox', use: { ...devices['Desktop Firefox'] } },
{ name: 'webkit', use: { ...devices['Desktop Safari'] } },
],
webServer: {
command: 'npm run dev',
url: 'http://localhost:3000',
reuseExistingServer: !process.env.CI,
},
});
CI/CD Integration
# .github/workflows/playwright.yml
name: Playwright Tests
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 24
- name: Install dependencies
run: npm ci
- name: Install Playwright Browsers
run: npx playwright install --with-deps
- name: Run Playwright tests
run: npx playwright test
- uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-report
path: playwright-report/
retention-days: 30
Debugging
# Run with UI mode
npx playwright test --ui
# Run in headed mode
npx playwright test --headed
# Run in debug mode (step through)
npx playwright test --debug
# Generate test code
npx playwright codegen http://localhost:3000
# Show trace viewer
npx playwright show-trace trace.zip
Playwright MCP & AI Testing (2026)
Playwright MCP (Model Context Protocol) enables AI-driven browser automation through accessibility tree snapshots rather than screenshots.
Key Capabilities
-
Playwright Agents: Three agent types for LLM-driven workflows
planner: Explores app, produces Markdown test plangenerator: Transforms plans into Playwright test fileshealer: Executes tests and auto-repairs failures
-
Self-healing locators: AI updates broken selectors automatically
-
Natural language test creation: Describe tests in plain English
-
Integration: Claude Desktop, VS Code Copilot, Cursor IDE
Quick Setup (Claude Desktop)
{
"mcpServers": {
"playwright": {
"command": "npx",
"args": ["-y", "@anthropic/mcp-playwright"]
}
}
}
See references/playwright-mcp.md for full configuration and patterns.
MCP Best Practices
Do:
- Use MCP for test scaffolding, then review and harden
- Leverage accessibility tree for stable locators
- Combine with human review for critical tests
Avoid:
- Auto-healing that weakens assertions
- Skipping code review of AI-generated tests
- Blind trust in healer agent fixes
Navigation
Resources
- references/playwright-mcp.md — Playwright MCP & AI-powered testing (2026)
- references/playwright-patterns.md — Advanced testing patterns, visual testing, WebSocket mocking
- references/playwright-ci.md — CI/CD configurations, real device testing, v1.57+ features
- data/sources.json — Playwright documentation links
Templates
- assets/template-playwright-e2e-review-checklist.md — E2E review checklist (selectors, parallelization, flake rules)
Related Skills
- ../qa-testing-strategy/SKILL.md — General testing strategies
- ../software-frontend/SKILL.md — Frontend development
- ../ops-devops-platform/SKILL.md — CI/CD integration
Trend Awareness Protocol
IMPORTANT: When users ask recommendation questions about E2E testing or Playwright, you MUST use WebSearch to check current trends before answering.
Trigger Conditions
- "What's the best E2E testing framework?"
- "What should I use for [browser automation/visual testing]?"
- "What's the latest in Playwright?"
- "Current best practices for [E2E testing/test automation]?"
- "Is [Playwright/Cypress/Selenium] still relevant in 2026?"
- "[Playwright] vs [Cypress] vs [other]?"
- "Best approach for [visual regression/component testing]?"
Required Searches
- Search:
"Playwright best practices 2026" - Search:
"Playwright vs Cypress vs alternatives 2026" - Search:
"E2E testing trends January 2026" - Search:
"Playwright new features 2026"
What to Report
After searching, provide:
- Current landscape: What testing tools/patterns are popular NOW
- Emerging trends: New Playwright features, testing patterns gaining traction
- Deprecated/declining: Approaches or tools losing relevance
- Recommendation: Based on fresh data, not just static knowledge
Example Topics (verify with fresh search)
- Playwright version updates and new features
- Component testing vs E2E testing boundaries
- Visual regression testing tools (Playwright, Percy, Chromatic)
- AI-assisted test generation and maintenance
- Cross-browser testing strategies
- Mobile testing with Playwright
- Playwright vs Cypress vs WebdriverIO comparison
スコア
総合スコア
リポジトリの品質指標に基づく評価
SKILL.mdファイルが含まれている
ライセンスが設定されている
100文字以上の説明がある
GitHub Stars 100以上
3ヶ月以内に更新がある
10回以上フォークされている
オープンIssueが50未満
プログラミング言語が設定されている
1つ以上のタグが設定されている
レビュー
レビュー機能は近日公開予定です