Back to list
vasilyu1983

qa-testing-playwright

by vasilyu1983

25🍴 6📅 Jan 23, 2026

SKILL.md


name: qa-testing-playwright description: "End-to-end web application testing with Playwright: scope control, stable selectors, parallelization/sharding, flake control, network mocking vs real services, visual testing tradeoffs, and CI/CD integration."

QA Testing (Playwright, Jan 2026) — Quick Reference

This skill enables high-signal, cost-aware E2E testing of web applications using Playwright.

Core references: Playwright best practices (https://playwright.dev/docs/best-practices), locators (https://playwright.dev/docs/locators), retries (https://playwright.dev/docs/test-retries), sharding (https://playwright.dev/docs/test-sharding), and the trace viewer (https://playwright.dev/docs/trace-viewer).


Core QA (Default)

Scope Control (What E2E Is For)

  • E2E exists to protect critical user journeys and high-risk integrations.
  • Keep E2E thin; push most coverage down to unit/integration/contract tests.
  • Test the contract and user intent, not CSS structure.

Selector Strategy (Stability First)

  • Prefer user-facing locators: getByRole, getByLabel, getByText (Playwright locators: https://playwright.dev/docs/locators).
  • Use data-testid as a last resort for complex widgets or non-semantic UI.
  • Avoid XPath and brittle CSS selectors.

Flake Control (Make CI Reliable)

CI Economics (Parallelize and Shard)

Do / Avoid

Do:

  • Make tests independent and deterministic (isolated state, stable data).
  • Use network mocking for third-party dependencies; test your integration contract, not their UI.

Avoid:

  • “Test everything E2E” as a default.
  • Weakening assertions to “fix” flakes (silent regressions).

Quick Reference

TaskCommandWhen to Use
Init Playwrightnpm init playwright@latestNew project setup
Run all testsnpx playwright testFull test suite
Run with UInpx playwright test --uiDebugging, visual mode
Run specific testnpx playwright test login.spec.tsTargeted testing
Generate codenpx playwright codegenRecord interactions
Show reportnpx playwright show-reportView test results

When to Use This Skill

Claude should invoke this skill when a user requests:

  • Write E2E tests for web application
  • Test user authentication flows
  • Verify form submissions and validation
  • Test responsive designs across viewports
  • Automate browser interactions
  • Set up Playwright in CI/CD
  • Debug failing E2E tests

Quick Start

Installation

# Initialize Playwright
npm init playwright@latest

# Or add to existing project
npm install -D @playwright/test
npx playwright install

Project Structure

project/
├── playwright.config.ts
├── tests/
│   ├── auth.spec.ts
│   ├── checkout.spec.ts
│   └── fixtures/
│       └── auth.fixture.ts
├── pages/
│   ├── LoginPage.ts
│   └── DashboardPage.ts
└── .github/workflows/
    └── playwright.yml

Core Testing Patterns

Browser Policy (Bundled vs Stable Channels)

  • Default: bundled browsers are designed for reliable automation.
  • When you must test against stable Chrome/Edge (policy/compliance, media codecs), use browser channels (see Playwright “Browsers”: https://playwright.dev/docs/browsers).

Locator Priority (2025 Best Practice)

Use role locators as the primary approach — they test from the user's perspective:

// 1. Role locators (preferred)
await page.getByRole('textbox', { name: 'Email' }).fill('user@example.com');
await page.getByRole('button', { name: 'Sign in' }).click();
await expect(page.getByRole('heading', { name: 'Dashboard' })).toBeVisible();

// 2. Label/text locators
await page.getByLabel('Email').fill('user@example.com');
await page.getByText('Sign in').click();

// 3. Test IDs (fallback for complex scenarios)
await page.getByTestId('user-avatar').click();

Common UI Flake Patterns

  • Overlay intercepts pointer events: use keyboard activation to follow the accessible path.
  • Menus: open via button role, select menuitem role to avoid hidden link clicks.
const menuButton = page.getByRole('button', { name: /open menu/i });
await menuButton.focus();
await menuButton.press('Enter');

const menuItem = page.getByRole('menuitem', { name: /year ahead/i });
await menuItem.focus();
await menuItem.press('Enter');

Basic Test Structure

import { test, expect } from '@playwright/test';

test.describe('User Authentication', () => {
  test('should login with valid credentials', async ({ page }) => {
    await page.goto('/login');

    await page.getByRole('textbox', { name: 'Email' }).fill('user@example.com');
    await page.getByLabel('Password').fill('password123');
    await page.getByRole('button', { name: 'Sign in' }).click();

    await expect(page).toHaveURL('/dashboard');
    await expect(page.getByRole('heading', { name: 'Dashboard' })).toBeVisible();
  });

  test('should show error for invalid credentials', async ({ page }) => {
    await page.goto('/login');

    await page.getByRole('textbox', { name: 'Email' }).fill('wrong@example.com');
    await page.getByLabel('Password').fill('wrongpassword');
    await page.getByRole('button', { name: 'Sign in' }).click();

    await expect(page.getByRole('alert')).toContainText('Invalid');
  });
});

Page Object Model

// pages/LoginPage.ts
import { Page, Locator, expect } from '@playwright/test';

export class LoginPage {
  readonly page: Page;
  readonly emailInput: Locator;
  readonly passwordInput: Locator;
  readonly loginButton: Locator;
  readonly errorMessage: Locator;

  constructor(page: Page) {
    this.page = page;
    this.emailInput = page.getByRole('textbox', { name: 'Email' });
    this.passwordInput = page.getByLabel('Password');
    this.loginButton = page.getByRole('button', { name: 'Sign in' });
    this.errorMessage = page.getByRole('alert');
  }

  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.loginButton.click();
  }

  async expectError(message: string) {
    await expect(this.errorMessage).toBeVisible();
    await expect(this.errorMessage).toContainText(message);
  }
}

// tests/login.spec.ts
import { test, expect } from '@playwright/test';
import { LoginPage } from '../pages/LoginPage';

test('login with valid credentials', async ({ page }) => {
  const loginPage = new LoginPage(page);
  await loginPage.goto();
  await loginPage.login('user@example.com', 'password123');
  await expect(page).toHaveURL('/dashboard');
});

Authentication Fixture

// fixtures/auth.fixture.ts
import { test as base, expect } from '@playwright/test';

type AuthFixtures = {
  authenticatedPage: Page;
};

export const test = base.extend<AuthFixtures>({
  authenticatedPage: async ({ page }, use) => {
    // Login before test
    await page.goto('/login');
    await page.getByRole('textbox', { name: 'Email' }).fill('user@example.com');
    await page.getByLabel('Password').fill('password123');
    await page.getByRole('button', { name: 'Sign in' }).click();
    await expect(page).toHaveURL('/dashboard');

    // Use authenticated page in test
    await use(page);
  },
});

// tests/dashboard.spec.ts
import { test } from '../fixtures/auth.fixture';
import { expect } from '@playwright/test';

test('authenticated user can access dashboard', async ({ authenticatedPage }) => {
  await authenticatedPage.goto('/dashboard');
  await expect(authenticatedPage.getByRole('heading', { name: 'Dashboard' })).toBeVisible();
});

Common Scenarios

Form Testing

test('should validate form fields', async ({ page }) => {
  await page.goto('/signup');

  // Submit empty form
  await page.click('[data-testid="submit"]');

  // Check validation messages
  await expect(page.locator('[data-testid="email-error"]')).toHaveText('Email is required');
  await expect(page.locator('[data-testid="password-error"]')).toHaveText('Password is required');

  // Fill with invalid email
  await page.fill('[data-testid="email"]', 'invalid-email');
  await page.click('[data-testid="submit"]');
  await expect(page.locator('[data-testid="email-error"]')).toHaveText('Invalid email format');
});

API Mocking

test('should handle API errors gracefully', async ({ page }) => {
  // Mock API to return error
  await page.route('**/api/users', route => {
    route.fulfill({
      status: 500,
      body: JSON.stringify({ error: 'Server error' }),
    });
  });

  await page.goto('/users');
  await expect(page.locator('[data-testid="error-banner"]')).toContainText('Failed to load');
});

test('should display user data from API', async ({ page }) => {
  // Mock successful response
  await page.route('**/api/users', route => {
    route.fulfill({
      status: 200,
      body: JSON.stringify([
        { id: 1, name: 'John Doe' },
        { id: 2, name: 'Jane Smith' },
      ]),
    });
  });

  await page.goto('/users');
  await expect(page.locator('[data-testid="user-list"] li')).toHaveCount(2);
});

Responsive Testing

import { devices } from '@playwright/test';

test.describe('Responsive Design', () => {
  test('mobile navigation', async ({ page }) => {
    await page.setViewportSize({ width: 375, height: 667 }); // iPhone SE
    await page.goto('/');

    // Mobile menu should be hidden
    await expect(page.locator('[data-testid="desktop-nav"]')).toBeHidden();

    // Hamburger menu should be visible
    await expect(page.locator('[data-testid="mobile-menu-button"]')).toBeVisible();

    // Open mobile menu
    await page.click('[data-testid="mobile-menu-button"]');
    await expect(page.locator('[data-testid="mobile-nav"]')).toBeVisible();
  });
});

// playwright.config.ts - Multiple devices
export default defineConfig({
  projects: [
    { name: 'Desktop Chrome', use: { ...devices['Desktop Chrome'] } },
    { name: 'Mobile Safari', use: { ...devices['iPhone 12'] } },
    { name: 'Tablet', use: { ...devices['iPad Pro'] } },
  ],
});

Visual Regression

test('homepage visual regression', async ({ page }) => {
  await page.goto('/');
  await expect(page).toHaveScreenshot('homepage.png', {
    maxDiffPixels: 100,
  });
});

test('button states', async ({ page }) => {
  await page.goto('/components');

  const button = page.locator('[data-testid="primary-button"]');

  // Default state
  await expect(button).toHaveScreenshot('button-default.png');

  // Hover state
  await button.hover();
  await expect(button).toHaveScreenshot('button-hover.png');

  // Focus state
  await button.focus();
  await expect(button).toHaveScreenshot('button-focus.png');
});

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'],
    ['junit', { outputFile: 'results.xml' }],
  ],
  use: {
    baseURL: 'http://localhost:3000',
    trace: 'on-first-retry',
    screenshot: 'only-on-failure',
    video: 'retain-on-failure',
  },
  projects: [
    { name: 'chromium', use: { ...devices['Desktop Chrome'] } },
    { name: 'firefox', use: { ...devices['Desktop Firefox'] } },
    { name: 'webkit', use: { ...devices['Desktop Safari'] } },
  ],
  webServer: {
    command: 'npm run dev',
    url: 'http://localhost:3000',
    reuseExistingServer: !process.env.CI,
  },
});

CI/CD Integration

# .github/workflows/playwright.yml
name: Playwright Tests
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 24
      - name: Install dependencies
        run: npm ci
      - name: Install Playwright Browsers
        run: npx playwright install --with-deps
      - name: Run Playwright tests
        run: npx playwright test
      - uses: actions/upload-artifact@v4
        if: always()
        with:
          name: playwright-report
          path: playwright-report/
          retention-days: 30

Debugging

# Run with UI mode
npx playwright test --ui

# Run in headed mode
npx playwright test --headed

# Run in debug mode (step through)
npx playwright test --debug

# Generate test code
npx playwright codegen http://localhost:3000

# Show trace viewer
npx playwright show-trace trace.zip

Playwright MCP & AI Testing (2026)

Playwright MCP (Model Context Protocol) enables AI-driven browser automation through accessibility tree snapshots rather than screenshots.

Key Capabilities

  • Playwright Agents: Three agent types for LLM-driven workflows

    • planner: Explores app, produces Markdown test plan
    • generator: Transforms plans into Playwright test files
    • healer: Executes tests and auto-repairs failures
  • Self-healing locators: AI updates broken selectors automatically

  • Natural language test creation: Describe tests in plain English

  • Integration: Claude Desktop, VS Code Copilot, Cursor IDE

Quick Setup (Claude Desktop)

{
  "mcpServers": {
    "playwright": {
      "command": "npx",
      "args": ["-y", "@anthropic/mcp-playwright"]
    }
  }
}

See references/playwright-mcp.md for full configuration and patterns.

MCP Best Practices

Do:

  • Use MCP for test scaffolding, then review and harden
  • Leverage accessibility tree for stable locators
  • Combine with human review for critical tests

Avoid:

  • Auto-healing that weakens assertions
  • Skipping code review of AI-generated tests
  • Blind trust in healer agent fixes

Resources

Templates


Trend Awareness Protocol

IMPORTANT: When users ask recommendation questions about E2E testing or Playwright, you MUST use WebSearch to check current trends before answering.

Trigger Conditions

  • "What's the best E2E testing framework?"
  • "What should I use for [browser automation/visual testing]?"
  • "What's the latest in Playwright?"
  • "Current best practices for [E2E testing/test automation]?"
  • "Is [Playwright/Cypress/Selenium] still relevant in 2026?"
  • "[Playwright] vs [Cypress] vs [other]?"
  • "Best approach for [visual regression/component testing]?"

Required Searches

  1. Search: "Playwright best practices 2026"
  2. Search: "Playwright vs Cypress vs alternatives 2026"
  3. Search: "E2E testing trends January 2026"
  4. Search: "Playwright new features 2026"

What to Report

After searching, provide:

  • Current landscape: What testing tools/patterns are popular NOW
  • Emerging trends: New Playwright features, testing patterns gaining traction
  • Deprecated/declining: Approaches or tools losing relevance
  • Recommendation: Based on fresh data, not just static knowledge
  • Playwright version updates and new features
  • Component testing vs E2E testing boundaries
  • Visual regression testing tools (Playwright, Percy, Chromatic)
  • AI-assisted test generation and maintenance
  • Cross-browser testing strategies
  • Mobile testing with Playwright
  • Playwright vs Cypress vs WebdriverIO comparison

Score

Total Score

60/100

Based on repository quality metrics

SKILL.md

SKILL.mdファイルが含まれている

+20
LICENSE

ライセンスが設定されている

+10
説明文

100文字以上の説明がある

0/10
人気

GitHub Stars 100以上

0/15
最近の活動

3ヶ月以内に更新がある

0/10
フォーク

10回以上フォークされている

0/5
Issue管理

オープンIssueが50未満

+5
言語

プログラミング言語が設定されている

+5
タグ

1つ以上のタグが設定されている

0/5

Reviews

💬

Reviews coming soon