mcpField
P
playwright

playwright

github.com/claude-dev-suiteTesting & quality4643 installsUpdated 3d ago
Repository

About playwright

A Playwright skill for developers writing browser-based end-to-end tests. It covers test setup, locators, actions, assertions, and production practices such as visual regression and CI configuration.

Key features

  1. 01Browser test setup and configuration
  2. 02Locators, actions, and assertions
  3. 03Page Object patterns
  4. 04Authentication state reuse
  5. 05Visual regression and CI guidance

Use cases

  1. 01Writing browser-based user-flow tests
  2. 02Checking page elements and interactions
  3. 03Reusing login state across tests
  4. 04Reviewing visual changes in screenshots

MCP support

The skill points to `mcp__documentation__fetch_docs` for comprehensive Playwright documentation.

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/claude-dev-suite/claude-dev-suite --skill playwright

After installing, type/playwrightin Claude Code to confirm it loaded.

Bundled filesquick-ref/locators.mdquick-ref/page-objects.md

Details

From the project README

Playwright Core Knowledge

Deep Knowledge: Use mcp__documentation__fetch_docs with technology: playwright for comprehensive documentation.

When NOT to Use This Skill

  • Unit Testing - Use vitest or jest for isolated function/module tests
  • Component Testing - Use testing-library for React/Vue component tests
  • API-Only Tests - Use framework-specific HTTP clients or REST clients
  • Performance Testing - Use dedicated tools like k6 or Lighthouse
  • Mobile App Testing - Use Appium or Detox for native mobile apps

Basic Test

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

test('user can login', async ({ page }) => {
  await page.goto('/login');
  await page.fill('[name="email"]', '[email protected]');
  await page.fill('[name="password"]', 'password123');
  await page.click('button[type="submit"]');

  await expect(page).toHaveURL('/dashboard');
  await expect(page.locator('h1')).toContainText('Welcome');
});

Locators

// By role (preferred)
page.getByRole('button', { name: 'Submit' });
page.getByRole('link', { name: 'Home' });
page.getByLabel('Email');
page.getByPlaceholder('Enter email');
page.getByText('Welcome');

// By test id
page.getByTestId('submit-button');

// CSS/XPath (fallback)
page.locator('.submit-btn');
page.locator('#user-form');

Assertions

// Visibility
await expect(locator).toBeVisible();
await expect(locator).toBeHidden();

// Text
await expect(locator).toHaveText('Hello');
await expect(locator).toContainText('Hello');

// Attributes
await expect(locator).toHaveAttribute('href', '/home');
await expect(locator).toHaveClass(/active/);

// Input
await expect(locator).toHaveValue('[email protected]');

// Page
await expect(page).toHaveURL(/dashboard/);
await expect(page).toHaveTitle('Dashboard');

Actions

await page.click('button');
await page.fill('input', 'text');
await page.check('input[type="checkbox"]');
await page.selectOption('select', 'option1');
await page.hover('.menu-item');
await page.keyboard.press('Enter');

Page Objects

class LoginPage {
  constructor(private page: Page) {}

  async login(email: string, password: string) {
    await this.page.fill('[name="email"]', email);
    await this.page.fill('[name="password"]', password);
    await this.page.click('button[type="submit"]');
  }
}

Config

// playwright.config.ts
export default defineConfig({
  testDir: './e2e',
  use: { baseURL: 'http://localhost:3000' },
  projects: [
    { name: 'chromium', use: { ...devices['Desktop Chrome'] } }
  ]
});

Production Readiness

Test Configuration
// 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'],
    ['junit', { outputFile: 'test-results/junit.xml' }],
  ],

  use: {
    baseURL: process.env.BASE_URL || 'http://localhost:3000',
    trace: 'on-first-retry',
    screenshot: 'only-on-failure',
    video: 'on-first-retry',
  },

  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,
    timeout: 120000,
  },
});
Authentication State
// Save auth state for reuse
// auth.setup.ts
import { test as setup } from '@playwright/test';

setup('authenticate', async ({ page }) => {
  await page.goto('/login');
  await page.fill('[name="email"]', process.env.TEST_USER_EMAIL!);
  await page.fill('[name="password"]', process.env.TEST_USER_PASSWORD!);
  await page.click('button[type="submit"]');
  await page.waitForURL('/dashboard');

  // Save storage state
  await page.context().storageState({ path: '.auth/user.json' });
});

// Use in tests
import { test } from '@playwright/test';

test.use({ storageState: '.auth/user.json' });

test('authenticated test', async ({ page }) => {
  await page.goto('/dashboard');
  // Already logged in
});
API Testing
test('API test', async ({ request }) => {
  const response = await request.post('/api/users', {
    data: { name: 'John', email: '[email protected]' },
    headers: { Authorization: `Bearer ${token}` },
  });

  expect(response.ok()).toBeTruthy();
  const user = await response.json();
  expect(user.name).toBe('John');
});

// Mock API responses
test('with mocked API', async ({ page }) => {
  await page.route('/api/users', async route => {
    await route.fulfill({
      status: 200,
      body: JSON.stringify([{ id: 1, name: 'Mock User' }]),
    });
  });

  await page.goto('/users');
  await expect(page.getByText('Mock User')).toBeVisible();
});
Visual Regression
test('visual regression', async ({ page }) => {
  await page.goto('/dashboard');
  await expect(page).toHaveScreenshot('dashboard.png', {
    maxDiffPixels: 100,
    threshold: 0.2,
  });
});

// Update snapshots: npx playwright test --update-snapshots
CI Configuration
# GitHub Actions
- name: Install Playwright
  run: npx playwright install --with-deps

- name: Run E2E tests
  run: npx playwright test
  env:
    BASE_URL: ${{ secrets.STAGING_URL }}

- name: Upload report
  uses: actions/upload-artifact@v3
  if: always()
  with:
    name: playwright-report
    path: playwright-report/
Monitoring Metrics
MetricTarget
E2E test pass rate> 99%
Test execution time< 10min
Flaky test rate< 1%
Visual diff failuresReview all
Checklist
  • Multi-browser testing configured
  • Mobile viewport testing
  • Authentication state reused
  • Retry on failure (CI only)
  • Screenshots on failure
  • Trace collection enabled
  • API mocking for isolation
  • Visual regression tests
  • CI/CD integration
  • Test parallelization
  • Page Object pattern used

Anti-Patterns

Anti-PatternWhy It's BadSolution
Testing via CSS selectorsBrittle, breaks on style changesUse getByRole, getByLabel, getByTestId
Not waiting for elementsFlaky testsUse auto-waiting locators, avoid waitForTimeout
Hardcoded waits (page.waitForTimeout)Slow, unreliableUse page.waitForSelector or auto-waiting
No Page Object ModelDuplicated code, hard to maintainExtract common actions into Page Objects
Testing too much in one testHard to debug failuresOne user flow per test
Not reusing auth stateSlow login for every testSave storageState, reuse across tests
Ignoring flaky testsFalse confidenceFix flaky tests, use retries sparingly

Quick Troubleshooting

ProblemLikely CauseSolution
"Timeout waiting for selector"Element not rendered or wrong selectorCheck selector, ensure element exists
Flaky test (passes/fails randomly)Race condition, slow networkUse auto-waiting, avoid waitForTimeout
"Element is not visible"Element hidden or not in viewportScroll into view, check CSS display
"Execution context destroyed"Navigation happened during actionWait for navigation to complete
Screenshot mismatchFont rendering, animationDisable animations, use fixed viewport
Test hangs foreverMissing awaitEnsure all async calls are awaited

Reference Documentation

Comments

Markdown supported