# Playwright Skill - Complete API Reference This document contains the comprehensive Playwright API reference and advanced patterns. For quick-start execution patterns, see [SKILL.md](SKILL.md). ## Table of Contents - [Installation & Setup](#installation--setup) - [Core Patterns](#core-patterns) - [Selectors & Locators](#selectors--locators) - [Common Actions](#common-actions) - [Waiting Strategies](#waiting-strategies) - [Assertions](#assertions) - [Page Object Model](#page-object-model-pom) - [Network & API Testing](#network--api-testing) - [Authentication & Session Management](#authentication--session-management) - [Visual Testing](#visual-testing) - [Mobile Testing](#mobile-testing) - [Debugging](#debugging) - [Performance Testing](#performance-testing) - [Parallel Execution](#parallel-execution) - [Data-Driven Testing](#data-driven-testing) - [Accessibility Testing](#accessibility-testing) - [CI/CD Integration](#cicd-integration) - [Best Practices](#best-practices) - [Common Patterns & Solutions](#common-patterns--solutions) - [Troubleshooting](#troubleshooting) ## Installation & Setup ### Prerequisites Before using this skill, ensure Playwright is available: ```bash # Check if Playwright is installed npm list playwright 2>/dev/null || echo "Playwright not installed" # Install (if needed) cd ~/.claude/skills/playwright-skill npm run setup ``` ### Basic Configuration Create `playwright.config.ts`: ```typescript 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', use: { baseURL: 'http://localhost:3000', trace: 'on-first-retry', screenshot: 'only-on-failure', video: 'retain-on-failure', }, projects: [ { name: 'chromium', use: { ...devices['Desktop Chrome'] }, }, ], webServer: { command: 'npm run start', url: 'http://localhost:3000', reuseExistingServer: !process.env.CI, }, }); ``` ## Core Patterns ### Basic Browser Automation ```javascript const { chromium } = require('playwright'); (async () => { // Launch browser const browser = await chromium.launch({ headless: false, // Set to true for headless mode slowMo: 50 // Slow down operations by 50ms }); const context = await browser.newContext({ viewport: { width: 1280, height: 720 }, userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' }); const page = await context.newPage(); // Navigate await page.goto('https://example.com', { waitUntil: 'networkidle' // Wait for network to be idle }); // Your automation here await browser.close(); })(); ``` ### Test Structure ```typescript import { test, expect } from '@playwright/test'; test.describe('Feature Name', () => { test.beforeEach(async ({ page }) => { await page.goto('/'); }); test('should do something', async ({ page }) => { // Arrange const button = page.locator('button[data-testid="submit"]'); // Act await button.click(); // Assert await expect(page).toHaveURL('/success'); await expect(page.locator('.message')).toHaveText('Success!'); }); }); ``` ## Selectors & Locators ### Best Practices for Selectors ```javascript // PREFERRED: Data attributes (most stable) await page.locator('[data-testid="submit-button"]').click(); await page.locator('[data-cy="user-input"]').fill('text'); // GOOD: Role-based selectors (accessible) await page.getByRole('button', { name: 'Submit' }).click(); await page.getByRole('textbox', { name: 'Email' }).fill('user@example.com'); await page.getByRole('heading', { level: 1 }).click(); // GOOD: Text content (for unique text) await page.getByText('Sign in').click(); await page.getByText(/welcome back/i).click(); // OK: Semantic HTML await page.locator('button[type="submit"]').click(); await page.locator('input[name="email"]').fill('test@test.com'); // AVOID: Classes and IDs (can change frequently) await page.locator('.btn-primary').click(); // Avoid await page.locator('#submit').click(); // Avoid // LAST RESORT: Complex CSS/XPath await page.locator('div.container > form > button').click(); // Fragile ``` ### Advanced Locator Patterns ```javascript // Filter and chain locators const row = page.locator('tr').filter({ hasText: 'John Doe' }); await row.locator('button').click(); // Nth element await page.locator('button').nth(2).click(); // Combining conditions await page.locator('button').and(page.locator('[disabled]')).count(); // Parent/child navigation const cell = page.locator('td').filter({ hasText: 'Active' }); const row = cell.locator('..'); await row.locator('button.edit').click(); ``` ## Common Actions ### Form Interactions ```javascript // Text input await page.getByLabel('Email').fill('user@example.com'); await page.getByPlaceholder('Enter your name').fill('John Doe'); // Clear and type await page.locator('#username').clear(); await page.locator('#username').type('newuser', { delay: 100 }); // Checkbox await page.getByLabel('I agree').check(); await page.getByLabel('Subscribe').uncheck(); // Radio button await page.getByLabel('Option 2').check(); // Select dropdown await page.selectOption('select#country', 'usa'); await page.selectOption('select#country', { label: 'United States' }); await page.selectOption('select#country', { index: 2 }); // Multi-select await page.selectOption('select#colors', ['red', 'blue', 'green']); // File upload await page.setInputFiles('input[type="file"]', 'path/to/file.pdf'); await page.setInputFiles('input[type="file"]', [ 'file1.pdf', 'file2.pdf' ]); ``` ### Mouse Actions ```javascript // Click variations await page.click('button'); // Left click await page.click('button', { button: 'right' }); // Right click await page.dblclick('button'); // Double click await page.click('button', { position: { x: 10, y: 10 } }); // Click at position // Hover await page.hover('.menu-item'); // Drag and drop await page.dragAndDrop('#source', '#target'); // Manual drag await page.locator('#source').hover(); await page.mouse.down(); await page.locator('#target').hover(); await page.mouse.up(); ``` ### Keyboard Actions ```javascript // Type with delay await page.keyboard.type('Hello World', { delay: 100 }); // Key combinations await page.keyboard.press('Control+A'); await page.keyboard.press('Control+C'); await page.keyboard.press('Control+V'); // Special keys await page.keyboard.press('Enter'); await page.keyboard.press('Tab'); await page.keyboard.press('Escape'); await page.keyboard.press('ArrowDown'); ``` ## Waiting Strategies ### Smart Waiting ```javascript // Wait for element states await page.locator('button').waitFor({ state: 'visible' }); await page.locator('.spinner').waitFor({ state: 'hidden' }); await page.locator('button').waitFor({ state: 'attached' }); await page.locator('button').waitFor({ state: 'detached' }); // Wait for specific conditions await page.waitForURL('**/success'); await page.waitForURL(url => url.pathname === '/dashboard'); // Wait for network await page.waitForLoadState('networkidle'); await page.waitForLoadState('domcontentloaded'); // Wait for function await page.waitForFunction(() => document.querySelector('.loaded')); await page.waitForFunction( text => document.body.innerText.includes(text), 'Content loaded' ); // Wait for response const responsePromise = page.waitForResponse('**/api/users'); await page.click('button#load-users'); const response = await responsePromise; // Wait for request await page.waitForRequest(request => request.url().includes('/api/') && request.method() === 'POST' ); // Custom timeout await page.locator('.slow-element').waitFor({ state: 'visible', timeout: 10000 // 10 seconds }); ``` ## Assertions ### Common Assertions ```javascript import { expect } from '@playwright/test'; // Page assertions await expect(page).toHaveTitle('My App'); await expect(page).toHaveURL('https://example.com/dashboard'); await expect(page).toHaveURL(/.*dashboard/); // Element visibility await expect(page.locator('.message')).toBeVisible(); await expect(page.locator('.spinner')).toBeHidden(); await expect(page.locator('button')).toBeEnabled(); await expect(page.locator('input')).toBeDisabled(); // Text content await expect(page.locator('h1')).toHaveText('Welcome'); await expect(page.locator('.message')).toContainText('success'); await expect(page.locator('.items')).toHaveText(['Item 1', 'Item 2']); // Input values await expect(page.locator('input')).toHaveValue('test@example.com'); await expect(page.locator('input')).toBeEmpty(); // Attributes await expect(page.locator('button')).toHaveAttribute('type', 'submit'); await expect(page.locator('img')).toHaveAttribute('src', /.*\.png/); // CSS properties await expect(page.locator('.error')).toHaveCSS('color', 'rgb(255, 0, 0)'); // Count await expect(page.locator('.item')).toHaveCount(5); // Checkbox/Radio state await expect(page.locator('input[type="checkbox"]')).toBeChecked(); ``` ## Page Object Model (POM) ### Basic Page Object ```javascript // pages/LoginPage.js class LoginPage { constructor(page) { this.page = page; this.usernameInput = page.locator('input[name="username"]'); this.passwordInput = page.locator('input[name="password"]'); this.submitButton = page.locator('button[type="submit"]'); this.errorMessage = page.locator('.error-message'); } async navigate() { await this.page.goto('/login'); } async login(username, password) { await this.usernameInput.fill(username); await this.passwordInput.fill(password); await this.submitButton.click(); } async getErrorMessage() { return await this.errorMessage.textContent(); } } // Usage in test test('login with valid credentials', async ({ page }) => { const loginPage = new LoginPage(page); await loginPage.navigate(); await loginPage.login('user@example.com', 'password123'); await expect(page).toHaveURL('/dashboard'); }); ``` ## Network & API Testing ### Intercepting Requests ```javascript // Mock API responses await page.route('**/api/users', route => { route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify([ { id: 1, name: 'John' }, { id: 2, name: 'Jane' } ]) }); }); // Modify requests await page.route('**/api/**', route => { const headers = { ...route.request().headers(), 'X-Custom-Header': 'value' }; route.continue({ headers }); }); // Block resources await page.route('**/*.{png,jpg,jpeg,gif}', route => route.abort()); ``` ### Custom Headers via Environment Variables The skill supports automatic header injection via environment variables: ```bash # Single header (simple) PW_HEADER_NAME=X-Automated-By PW_HEADER_VALUE=playwright-skill # Multiple headers (JSON) PW_EXTRA_HEADERS='{"X-Automated-By":"playwright-skill","X-Request-ID":"123"}' ``` These headers are automatically applied to all requests when using: - `helpers.createContext(browser)` - headers merged automatically - `getContextOptionsWithHeaders(options)` - utility injected by run.js wrapper **Precedence (highest to lowest):** 1. Headers passed directly in `options.extraHTTPHeaders` 2. Environment variable headers 3. Playwright defaults **Use case:** Identify automated traffic so your backend can return LLM-optimized responses (e.g., plain text errors instead of styled HTML). ## Visual Testing ### Screenshots ```javascript // Full page screenshot await page.screenshot({ path: 'screenshot.png', fullPage: true }); // Element screenshot await page.locator('.chart').screenshot({ path: 'chart.png' }); // Visual comparison await expect(page).toHaveScreenshot('homepage.png'); ``` ## Mobile Testing ```javascript // Device emulation const { devices } = require('playwright'); const iPhone = devices['iPhone 12']; const context = await browser.newContext({ ...iPhone, locale: 'en-US', permissions: ['geolocation'], geolocation: { latitude: 37.7749, longitude: -122.4194 } }); ``` ## Debugging ### Debug Mode ```bash # Run with inspector npx playwright test --debug # Headed mode npx playwright test --headed # Slow motion npx playwright test --headed --slowmo=1000 ``` ### In-Code Debugging ```javascript // Pause execution await page.pause(); // Console logs page.on('console', msg => console.log('Browser log:', msg.text())); page.on('pageerror', error => console.log('Page error:', error)); ``` ## Performance Testing ```javascript // Measure page load time const startTime = Date.now(); await page.goto('https://example.com'); const loadTime = Date.now() - startTime; console.log(`Page loaded in ${loadTime}ms`); ``` ## Parallel Execution ```javascript // Run tests in parallel test.describe.parallel('Parallel suite', () => { test('test 1', async ({ page }) => { // Runs in parallel with test 2 }); test('test 2', async ({ page }) => { // Runs in parallel with test 1 }); }); ``` ## Data-Driven Testing ```javascript // Parameterized tests const testData = [ { username: 'user1', password: 'pass1', expected: 'Welcome user1' }, { username: 'user2', password: 'pass2', expected: 'Welcome user2' }, ]; testData.forEach(({ username, password, expected }) => { test(`login with ${username}`, async ({ page }) => { await page.goto('/login'); await page.fill('#username', username); await page.fill('#password', password); await page.click('button[type="submit"]'); await expect(page.locator('.message')).toHaveText(expected); }); }); ``` ## Accessibility Testing ```javascript import { injectAxe, checkA11y } from 'axe-playwright'; test('accessibility check', async ({ page }) => { await page.goto('/'); await injectAxe(page); await checkA11y(page); }); ``` ## CI/CD Integration ### GitHub Actions ```yaml name: Playwright Tests on: push: branches: [main, master] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 - name: Install dependencies run: npm ci - name: Install Playwright Browsers run: npx playwright install --with-deps - name: Run tests run: npx playwright test ``` ## Best Practices 1. **Test Organization** - Use descriptive test names, group related tests 2. **Selector Strategy** - Prefer data-testid attributes, use role-based selectors 3. **Waiting** - Use Playwright's auto-waiting, avoid hard-coded delays 4. **Error Handling** - Add proper error messages, take screenshots on failure 5. **Performance** - Run tests in parallel, reuse authentication state ## Common Patterns & Solutions ### Handling Popups ```javascript const [popup] = await Promise.all([ page.waitForEvent('popup'), page.click('button.open-popup') ]); await popup.waitForLoadState(); ``` ### File Downloads ```javascript const [download] = await Promise.all([ page.waitForEvent('download'), page.click('button.download') ]); await download.saveAs(`./downloads/${download.suggestedFilename()}`); ``` ### iFrames ```javascript const frame = page.frameLocator('#my-iframe'); await frame.locator('button').click(); ``` ### Infinite Scroll ```javascript async function scrollToBottom(page) { await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight)); await page.waitForTimeout(500); } ``` ## Troubleshooting ### Common Issues 1. **Element not found** - Check if element is in iframe, verify visibility 2. **Timeout errors** - Increase timeout, check network conditions 3. **Flaky tests** - Use proper waiting strategies, mock external dependencies 4. **Authentication issues** - Verify auth state is properly saved ## Quick Reference Commands ```bash # Run tests npx playwright test # Run in headed mode npx playwright test --headed # Debug tests npx playwright test --debug # Generate code npx playwright codegen https://example.com # Show report npx playwright show-report ``` ## Additional Resources - [Playwright Documentation](https://playwright.dev/docs/intro) - [API Reference](https://playwright.dev/docs/api/class-playwright) - [Best Practices](https://playwright.dev/docs/best-practices)