Add 260+ Claude Code skills from skills.sh
Complete collection of AI agent skills including: - Frontend Development (Vue, React, Next.js, Three.js) - Backend Development (NestJS, FastAPI, Node.js) - Mobile Development (React Native, Expo) - Testing (E2E, frontend, webapp) - DevOps (GitHub Actions, CI/CD) - Marketing (SEO, copywriting, analytics) - Security (binary analysis, vulnerability scanning) - And many more... Synchronized from: https://skills.sh/ Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
653
playwright-skill/skills/playwright-skill/API_REFERENCE.md
Normal file
653
playwright-skill/skills/playwright-skill/API_REFERENCE.md
Normal file
@@ -0,0 +1,653 @@
|
||||
# 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)
|
||||
Reference in New Issue
Block a user