Marketplace playwright-testing
Automatically activated when user works with Playwright tests, mentions Playwright configuration, asks about selectors/locators/page objects, or has files matching *.spec.ts in e2e or tests directories. Provides Playwright-specific expertise for E2E and integration testing.
git clone https://github.com/aiskillstore/marketplace
T=$(mktemp -d) && git clone --depth=1 https://github.com/aiskillstore/marketplace "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/c0ntr0lledcha0s/playwright-testing" ~/.claude/skills/aiskillstore-marketplace-playwright-testing && rm -rf "$T"
skills/c0ntr0lledcha0s/playwright-testing/SKILL.mdPlaywright 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
in e2e, tests, or playwright directories are encountered*.spec.ts - 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
(which fails with WebSockets, long-polling, analytics):networkidle// 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