diff options
| author | Craig Jennings <c@cjennings.net> | 2026-04-19 15:24:51 -0500 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-04-19 15:24:51 -0500 |
| commit | 4ffa7417a359ef4eae09f61d7da4de06539462ca (patch) | |
| tree | b8eeb8aa5ec2344216c0f0cdcdcc82d0df307ce3 /playwright-skill/API_REFERENCE.md | |
| parent | 11f5f003eef12bff9633ca8190e3c43c7dab6708 (diff) | |
| download | rulesets-4ffa7417a359ef4eae09f61d7da4de06539462ca.tar.gz rulesets-4ffa7417a359ef4eae09f61d7da4de06539462ca.zip | |
refactor(playwright): split into playwright-js + playwright-py variants
Rename `playwright-skill/` → `playwright-js/` and add `playwright-py/`
as a verbatim fork of Anthropic's official `webapp-testing` skill
(Apache-2.0). Cross-pollinate: each skill gains patterns and helpers
inspired by the other's strengths, with upstream semantics preserved.
## playwright-js (JS/TS stack)
Renamed from playwright-skill; upstream lackeyjb MIT content untouched.
New sections added (clearly marked, preserving upstream semantics):
- Static HTML vs Dynamic Webapp decision tree (core Anthropic methodology)
- Reconnaissance-Then-Action pattern (navigate → networkidle → inspect → act)
- Console Log Capture snippet (page.on console/pageerror/requestfailed)
Description updated to clarify JS/TS stack fit (React/Next/Vue/Svelte/Node)
and reference `/playwright-py` as the Python sibling.
## playwright-py (Python stack)
Verbatim fork of anthropics/skills/skills/webapp-testing; upstream SKILL.md
and bundled `scripts/with_server.py` + examples kept intact. New scripts
and examples added (all lackeyjb-style conveniences in Python):
Scripts:
scripts/detect_dev_servers.py Probe common localhost ports for HTTP
servers; outputs JSON of found services.
scripts/safe_actions.py safe_click, safe_type (retry-wrapped),
handle_cookie_banner (common selectors),
build_context_with_headers (env-var-
driven: PW_HEADER_NAME / PW_HEADER_VALUE /
PW_EXTRA_HEADERS='{…json…}').
Examples:
examples/login_flow.py Login form + wait_for_url.
examples/broken_links.py Scan visible external hrefs via HEAD.
examples/responsive_sweep.py Multi-viewport screenshots to /tmp.
SKILL.md gains 5 "Added:" sections documenting the new scripts, retry
helpers, env-header injection, and /tmp script discipline. Attribution
notes explicitly mark upstream vs local additions.
## Makefile
SKILLS: playwright-skill → playwright-js + playwright-py
deps target: extended Playwright step to install Python package +
Chromium via `python3 -m pip install --user playwright && python3 -m
playwright install chromium` when playwright-py/ is present. Idempotent
(detected via `python3 -c "import playwright"`).
## Usage
Both skills symlinked globally via `make install`. Invoke whichever
matches the project stack — cross-references in descriptions route you
to the right one. Run `make deps` once to install both runtimes.
Diffstat (limited to 'playwright-skill/API_REFERENCE.md')
| -rw-r--r-- | playwright-skill/API_REFERENCE.md | 653 |
1 files changed, 0 insertions, 653 deletions
diff --git a/playwright-skill/API_REFERENCE.md b/playwright-skill/API_REFERENCE.md deleted file mode 100644 index 9ee2975..0000000 --- a/playwright-skill/API_REFERENCE.md +++ /dev/null @@ -1,653 +0,0 @@ -# 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) |
