
Playwright Testing
- 94 installs
- 49 repo stars
- Updated August 4, 2026
- laurigates/claude-plugins
Helps with testing & qa tasks.
About
playwright-testing is a Claude Code skill for testing & qa. It helps solo builders move faster with AI-assisted development.
- playwright-testing
- Testing & QA
- AI-coding skill
Playwright Testing by the numbers
- 94 all-time installs (skills.sh)
- Ranked #1,019 of 2,153 Testing & QA skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/laurigates/claude-plugins --skill playwright-testingAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 94 |
|---|---|
| repo stars | ★ 49 |
| Last updated | August 4, 2026 |
| Repository | laurigates/claude-plugins ↗ |
What it does
Helps with testing & qa tasks.
Files
Playwright Testing
Playwright is a modern end-to-end testing framework for web applications. It provides reliable, fast, and cross-browser testing with excellent developer experience.
When to Use This Skill
| Use this skill when... | Use another skill instead when... |
|---|---|
| Writing E2E browser tests | Writing unit tests (use vitest-testing) |
| Testing across Chromium, Firefox, WebKit | Testing Python code (use python-testing) |
| Setting up visual regression testing | Analyzing test quality (use test-quality-analysis) |
| Mocking network requests in E2E tests | Generating property-based tests (use property-based-testing) |
| Testing mobile viewports | Testing API contracts only (use api-testing) |
Core Expertise
- Cross-browser: Test on Chromium, Firefox, WebKit (Safari)
- Reliable: Auto-wait, auto-retry, no flaky tests
- Fast: Parallel execution, browser context isolation
- Modern: TypeScript-first, async/await, auto-complete
- Multi-platform: Windows, macOS, Linux
Installation
bun create playwright # Initialize (recommended)
bun add --dev @playwright/test # Or install manually
bunx playwright install # Install browsers
bunx playwright install --with-deps # With system deps (Linux)
bunx playwright --version # VerifyConfiguration (playwright.config.ts)
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests',
timeout: 30000,
use: {
baseURL: 'http://localhost:3000',
trace: 'on-first-retry',
},
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
],
});Essential Commands
bunx playwright test # Run all tests
bunx playwright test tests/login.spec.ts # Specific file
bunx playwright test --headed # See browser
bunx playwright test --debug # Debug mode
bunx playwright test --project=chromium # Specific browser
bunx playwright test --ui # UI mode
bunx playwright codegen http://localhost:3000 # Record tests
bunx playwright show-report # Last report
bunx playwright show-trace trace.zip # Trace viewer
bunx playwright test --update-snapshots # Update snapshotsWriting Tests
Basic Test Structure
import { test, expect } from '@playwright/test';
test('basic test', async ({ page }) => {
await page.goto('https://example.com');
await expect(page).toHaveTitle(/Example/);
});
test.describe('login flow', () => {
test('should login successfully', async ({ page }) => {
await page.goto('/login');
await page.fill('input[name="email"]', 'user@example.com');
await page.fill('input[name="password"]', 'password123');
await page.click('button[type="submit"]');
await expect(page).toHaveURL('/dashboard');
});
});Selectors and Locators
// Text/Role selectors (recommended)
await page.getByText('Sign in').click();
await page.getByRole('button', { name: 'Submit' }).click();
await page.getByLabel('Email').fill('user@example.com');
await page.getByPlaceholder('Enter your name').fill('John');
await page.getByTestId('login-button').click();
// CSS/XPath selectors
await page.locator('.button-primary').click();
await page.locator('xpath=//button[text()="Submit"]').click();
// Chaining
await page.locator('.card').filter({ hasText: 'Product' }).getByRole('button').click();Key Assertions
| Assertion | Description |
|---|---|
toHaveTitle(title) | Page title |
toHaveURL(url) | Page URL |
toBeVisible() | Element visible |
toBeEnabled() | Element enabled |
toHaveText(text) | Element text |
toContainText(text) | Partial text |
toHaveAttribute(name, value) | Attribute value |
toHaveClass(class) | CSS class |
toHaveValue(value) | Input value |
toBeEmpty() | Empty input |
toHaveCount(n) | Element count |
not.toBeDisabled() | Negation |
Agentic Optimizations
| Context | Command |
|---|---|
| Quick test | bunx playwright test --reporter=line --bail |
| CI test | bunx playwright test --reporter=github |
| Single browser | bunx playwright test --project=chromium --reporter=line |
| Debug failing | bunx playwright test --trace on --reporter=line |
| Headed debug | bunx playwright test --headed --debug |
For detailed examples, advanced patterns, and best practices, see REFERENCE.md.
References
- Official docs: https://playwright.dev
- Configuration: https://playwright.dev/docs/test-configuration
- API reference: https://playwright.dev/docs/api/class-test
- Best practices: https://playwright.dev/docs/best-practices
- CI/CD: https://playwright.dev/docs/ci
- Trace viewer: https://playwright.dev/docs/trace-viewer
Playwright Testing - Reference
Detailed reference material for Playwright E2E testing configuration, patterns, and CI/CD integration.
Recommended Production Configuration
// playwright.config.ts
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'],
['json', { outputFile: 'test-results/results.json' }],
['junit', { outputFile: 'test-results/junit.xml' }],
],
timeout: 30000,
expect: {
timeout: 5000,
},
use: {
baseURL: process.env.BASE_URL || 'http://localhost:3000',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
video: 'retain-on-failure',
actionTimeout: 10000,
navigationTimeout: 30000,
},
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
{
name: 'firefox',
use: { ...devices['Desktop Firefox'] },
},
{
name: 'webkit',
use: { ...devices['Desktop Safari'] },
},
{
name: 'mobile-chrome',
use: { ...devices['Pixel 5'] },
},
{
name: 'mobile-safari',
use: { ...devices['iPhone 13'] },
},
],
webServer: {
command: 'bun run dev',
url: 'http://localhost:3000',
reuseExistingServer: !process.env.CI,
timeout: 120000,
},
});Page Object Model
// page-objects/login-page.ts
import { Page, Locator } from '@playwright/test';
export class LoginPage {
readonly page: Page;
readonly emailInput: Locator;
readonly passwordInput: Locator;
readonly submitButton: 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' });
}
async goto() {
await this.page.goto('/login');
}
async login(email: string, password: string) {
await this.emailInput.fill(email);
await this.passwordInput.fill(password);
await this.submitButton.click();
}
}
// tests/login.spec.ts
import { test, expect } from '@playwright/test';
import { LoginPage } from '../page-objects/login-page';
test('login with page object', async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.goto();
await loginPage.login('user@example.com', 'password123');
await expect(page).toHaveURL('/dashboard');
});Fixtures
Built-in Fixtures
import { test } from '@playwright/test';
test('built-in fixtures', async ({ page, context, browser }) => {
// page - isolated browser page
await page.goto('/');
// context - browser context (cookies, storage)
await context.clearCookies();
// browser - browser instance
const newPage = await browser.newPage();
});Custom Fixtures
// fixtures/auth-fixture.ts
import { test as base } from '@playwright/test';
import { LoginPage } from '../page-objects/login-page';
type AuthFixtures = {
authenticatedPage: Page;
};
export const test = base.extend<AuthFixtures>({
authenticatedPage: async ({ page }, use) => {
const loginPage = new LoginPage(page);
await loginPage.goto();
await loginPage.login('user@example.com', 'password123');
await use(page);
},
});
// tests/dashboard.spec.ts
import { test } from '../fixtures/auth-fixture';
test('access dashboard', async ({ authenticatedPage }) => {
await authenticatedPage.goto('/dashboard');
// User is already logged in
});Authentication State
Save Authentication State
// tests/auth.setup.ts
import { test as setup } from '@playwright/test';
const authFile = 'playwright/.auth/user.json';
setup('authenticate', async ({ page }) => {
await page.goto('/login');
await page.getByLabel('Email').fill('user@example.com');
await page.getByLabel('Password').fill('password123');
await page.getByRole('button', { name: 'Sign in' }).click();
await page.waitForURL('/dashboard');
await page.context().storageState({ path: authFile });
});Use Saved State
// playwright.config.ts
export default defineConfig({
projects: [
{ name: 'setup', testMatch: /.*\.setup\.ts/ },
{
name: 'chromium',
use: {
...devices['Desktop Chrome'],
storageState: 'playwright/.auth/user.json',
},
dependencies: ['setup'],
},
],
});API Testing
import { test, expect } from '@playwright/test';
test('api testing', async ({ request }) => {
// GET request
const response = await request.get('https://api.example.com/users');
expect(response.ok()).toBeTruthy();
expect(response.status()).toBe(200);
const users = await response.json();
expect(users).toHaveLength(10);
// POST request
const createResponse = await request.post('https://api.example.com/users', {
data: {
name: 'John Doe',
email: 'john@example.com',
},
});
expect(createResponse.ok()).toBeTruthy();
// With authentication
const authResponse = await request.get('https://api.example.com/profile', {
headers: {
Authorization: 'Bearer token123',
},
});
expect(authResponse.ok()).toBeTruthy();
});Network Interception
import { test, expect } from '@playwright/test';
test('mock api response', async ({ page }) => {
// Mock API response
await page.route('**/api/users', (route) => {
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify([
{ id: 1, name: 'John' },
{ id: 2, name: 'Jane' },
]),
});
});
await page.goto('/users');
await expect(page.getByText('John')).toBeVisible();
});
test('block images', async ({ page }) => {
// Block image requests
await page.route('**/*.{png,jpg,jpeg}', (route) => route.abort());
await page.goto('/');
// Page loads faster without images
});
test('intercept and modify', async ({ page }) => {
await page.route('**/api/config', async (route) => {
const response = await route.fetch();
const json = await response.json();
json.feature_flag = true;
await route.fulfill({ json });
});
await page.goto('/');
});Visual Regression Testing
import { test, expect } from '@playwright/test';
test('visual regression', async ({ page }) => {
await page.goto('/');
// Full page screenshot
await expect(page).toHaveScreenshot('homepage.png');
// Element screenshot
const header = page.locator('header');
await expect(header).toHaveScreenshot('header.png');
// With options
await expect(page).toHaveScreenshot('homepage-mobile.png', {
fullPage: true,
maxDiffPixels: 100,
});
});
// Update snapshots with:
// bunx playwright test --update-snapshotsMobile Emulation
import { test, devices } from '@playwright/test';
test.use({
...devices['iPhone 13'],
});
test('mobile test', async ({ page }) => {
await page.goto('/');
// Test runs in iPhone 13 viewport
});
// Or configure in playwright.config.ts:
// projects: [
// {
// name: 'mobile',
// use: { ...devices['Pixel 5'] },
// },
// ]Parallel Execution
// playwright.config.ts
export default defineConfig({
fullyParallel: true,
workers: process.env.CI ? 1 : undefined, // All cores locally, 1 in CI
});
// Force serial execution for specific tests
test.describe.serial('checkout flow', () => {
test('add to cart', async ({ page }) => {
// ...
});
test('proceed to checkout', async ({ page }) => {
// Runs after previous test
});
});Debugging
Debug Mode
# Run with debugger
bunx playwright test --debug
# Debug specific test
bunx playwright test tests/login.spec.ts --debug
# Pause on failure
bunx playwright test --headed --pause-on-failureTrace Viewer
// playwright.config.ts
export default defineConfig({
use: {
trace: 'on-first-retry', // or 'on', 'off', 'retain-on-failure'
},
});# View trace
bunx playwright show-trace trace.zipTrace viewer shows:
- Timeline of actions
- Screenshots at each step
- Network activity
- Console logs
- Source code
CI/CD Integration
GitHub Actions
name: Playwright Tests
on:
push:
branches: [main]
pull_request:
jobs:
test:
timeout-minutes: 60
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- name: Install dependencies
run: bun install --frozen-lockfile
- name: Install Playwright Browsers
run: bunx playwright install --with-deps
- name: Run Playwright tests
run: bunx playwright test
- name: Upload test results
uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-report
path: playwright-report/
retention-days: 30GitLab CI
playwright:
image: mcr.microsoft.com/playwright:v1.40.0-jammy
stage: test
script:
- bun install --frozen-lockfile
- bunx playwright test
artifacts:
when: always
paths:
- playwright-report/
- test-results/
expire_in: 1 weekBest Practices
Use Built-in Locators
// Good: Resilient to UI changes
await page.getByRole('button', { name: 'Submit' }).click();
await page.getByLabel('Email').fill('user@example.com');
await page.getByText('Welcome').click();
// Bad: Fragile selectors
await page.locator('#submit-btn-123').click();
await page.locator('div > div > input').fill('user@example.com');Auto-waiting
// Good: Playwright waits automatically
await page.getByRole('button').click();
// Bad: Manual waits
await page.waitForTimeout(1000);
await page.getByRole('button').click();Isolate Tests
// Good: Independent tests
test('test 1', async ({ page }) => {
await page.goto('/');
// Test logic
});
test('test 2', async ({ page }) => {
await page.goto('/');
// Independent test logic
});
// Bad: Tests depend on each otherUse Test IDs for Dynamic Content
<!-- HTML -->
<button data-testid="submit-btn">Submit</button>// Test
await page.getByTestId('submit-btn').click();Troubleshooting
Tests Timing Out
// Increase timeout for specific test
test('slow test', async ({ page }) => {
test.setTimeout(60000);
await page.goto('/slow-page');
});
// Or in config:
export default defineConfig({
timeout: 60000,
expect: {
timeout: 10000,
},
});Flaky Tests
# Run test multiple times
bunx playwright test --repeat-each=10
# Retry failed tests
bunx playwright test --retries=3Element Not Found
// Wait for element explicitly
await page.waitForSelector('.element');
// Or use auto-wait assertions
await expect(page.locator('.element')).toBeVisible();Browser Not Found
# Reinstall browsers
bunx playwright install
# Verify installation
bunx playwright install --helpRelated skills
Testing & QAtesting