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.
Best use case
playwright-testing is best used when you need a repeatable AI agent workflow instead of a one-off prompt. It is especially useful for teams working in multi. 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.
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.
Users should expect a more consistent workflow output, faster repeated execution, and less time spent rewriting prompts from scratch.
Practical example
Example input
Use the "playwright-testing" skill to help with this workflow task. Context: 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.
Example output
A structured workflow result with clearer steps, more consistent formatting, and an output that is easier to reuse in the next run.
When to use this skill
- Use this skill when you want a reusable workflow rather than writing the same prompt again and again.
When not to use this skill
- Do not use this when you only need a one-off answer and do not need a reusable workflow.
- Do not use it if you cannot install or maintain the related files, repository context, or supporting tools.
Installation
Claude Code / Cursor / Codex
Manual Installation
- Download SKILL.md from GitHub
- Place it in
.claude/skills/playwright-testing/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How playwright-testing Compares
| Feature / Agent | playwright-testing | Standard Approach |
|---|---|---|
| Platform Support | Not specified | Limited / Varies |
| Context Awareness | High | Baseline |
| Installation Complexity | Unknown | N/A |
Frequently Asked Questions
What does this skill do?
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.
Where can I find the source code?
You can find the source code on GitHub using the link provided at the top of the page.
Related Guides
SKILL.md Source
# Playwright 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
1. **Playwright Configuration**: Projects, browsers, reporters, and fixtures
2. **Locators & Selectors**: Role-based, text, CSS, and chained locators
3. **Page Object Model**: Organizing tests with page objects
4. **Assertions**: Built-in assertions, custom matchers, auto-waiting
5. **Test Fixtures**: Built-in and custom fixtures, test isolation
6. **Debugging**: Traces, screenshots, videos, and Playwright Inspector
7. **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 `*.spec.ts` in e2e, tests, or playwright directories are encountered
- 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
```typescript
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)
```typescript
// 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
```typescript
page.getByRole('listitem')
.filter({ hasText: 'Product 1' })
.getByRole('button', { name: 'Add' });
```
#### Test IDs (Last Resort)
```typescript
page.getByTestId('submit-button');
```
### Page Object Pattern
```typescript
// 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
```typescript
// 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
```typescript
// 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:
```typescript
// 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:
```typescript
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:
```typescript
// 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:
```typescript
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
```typescript
// 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
```bash
npx playwright test --debug
npx playwright test --ui
```
### Traces
```typescript
// Capture trace on failure
use: {
trace: 'on-first-retry',
}
// View trace
npx playwright show-trace trace.zip
```
### Screenshots
```typescript
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 `networkidle` (which fails with WebSockets, long-polling, analytics):
```typescript
// 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:
1. Use getByLabel for inputs
2. Use fill() instead of type() for speed
3. Submit with button locator
4. Assert on success message or navigation
### Example 2: Table Testing
When testing tables/lists:
1. Use getByRole('row') for table rows
2. Filter by content with `.filter()`
3. Chain to find actions within rows
4. 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:
```bash
npx playwright --version
```
### Upgrading
```bash
# 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 CIRelated Skills
testing-strategies
Design comprehensive testing strategies for software quality assurance. Use when planning test coverage, implementing test pyramids, or setting up testing infrastructure. Handles unit testing, integration testing, E2E testing, TDD, and testing best practices.
backend-testing
Write comprehensive backend tests including unit tests, integration tests, and API tests. Use when testing REST APIs, database operations, authentication flows, or business logic. Handles Jest, Pytest, Mocha, testing strategies, mocking, and test coverage.
wordpress-penetration-testing
This skill should be used when the user asks to "pentest WordPress sites", "scan WordPress for vulnerabilities", "enumerate WordPress users, themes, or plugins", "exploit WordPress vulnerabilities", or "use WPScan". It provides comprehensive WordPress security assessment methodologies.
web3-testing
Test smart contracts comprehensively using Hardhat and Foundry with unit tests, integration tests, and mainnet forking. Use when testing Solidity contracts, setting up blockchain test suites, or validating DeFi protocols.
web-security-testing
Web application security testing workflow for OWASP Top 10 vulnerabilities including injection, XSS, authentication flaws, and access control issues.
unit-testing-test-generate
Generate comprehensive, maintainable unit tests across languages with strong coverage and edge case focus.
testing-qa
Comprehensive testing and QA workflow covering unit testing, integration testing, E2E testing, browser automation, and quality assurance.
temporal-python-testing
Test Temporal workflows with pytest, time-skipping, and mocking strategies. Covers unit testing, integration testing, replay testing, and local development setup. Use when implementing Temporal workflow tests or debugging test failures.
ssh-penetration-testing
This skill should be used when the user asks to "pentest SSH services", "enumerate SSH configurations", "brute force SSH credentials", "exploit SSH vulnerabilities", "perform SSH tunneling", or "audit SSH security". It provides comprehensive SSH penetration testing methodologies and techniques.
sqlmap-database-pentesting
This skill should be used when the user asks to "automate SQL injection testing," "enumerate database structure," "extract database credentials using sqlmap," "dump tables and columns...
sqlmap-database-penetration-testing
This skill should be used when the user asks to "automate SQL injection testing," "enumerate database structure," "extract database credentials using sqlmap," "dump tables and columns from a vulnerable database," or "perform automated database penetration testing." It provides comprehensive guidance for using SQLMap to detect and exploit SQL injection vulnerabilities.
sql-injection-testing
This skill should be used when the user asks to "test for SQL injection vulnerabilities", "perform SQLi attacks", "bypass authentication using SQL injection", "extract database information through injection", "detect SQL injection flaws", or "exploit database query vulnerabilities". It provides comprehensive techniques for identifying, exploiting, and understanding SQL injection attack vectors across different database systems.