Claude Code Plugins

Community-maintained marketplace

Feedback

Playwright browser automation and E2E testing. Use for browser testing, screenshots, debugging, MCP tools, page objects, and visual verification.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name playwright
description Playwright browser automation and E2E testing. Use for browser testing, screenshots, debugging, MCP tools, page objects, and visual verification.

Playwright Skill

Activation: E2E testing, browser automation, debugging, screenshots Version: 1.57.0

Runs on HOST (Exception to Container-First Rule)

Playwright E2E tests run on the host because they need browser control:

cd playwright && pnpm test           # Run all tests
cd playwright && pnpm test:ui        # Interactive UI mode
cd playwright && pnpm test:headed    # With visible browser

This is an intentional exception. See .claude/rules/container-execution.md.

Timeout Configuration (M3 Max Optimized)

CRITICAL: This configuration uses aggressive timeouts to prevent tests from hanging.

Timeout Type Local (M3 Max) CI
Test timeout 5s 30s
Action timeout 2s 10s
Navigation timeout 3s 15s
Expect timeout 2s 10s
Global timeout 2 min 10 min

Tests will fail fast if services are down. Global setup verifies services before tests run.

Pre-Flight Health Check

Before running any test, the global setup verifies:

  1. Backend HTTP endpoint (/health) responds
  2. Frontend HTTP endpoint responds
  3. Browser can launch and navigate

If any check fails, tests abort with clear troubleshooting instructions.

Overview

Playwright is used for:

  1. E2E Testing - Frontend integration tests
  2. Pipeline Rendering - HTML to PNG for comparison images
  3. Debugging - Visual browser automation

MCP Integration

This project has Playwright MCP available for automated testing and PR verification.

For interactive debugging during development, use Chrome DevTools MCP instead - see chrome-devtools skill.

Task Tool
Development iteration, CSS debugging Chrome DevTools
PR verification, screenshots Playwright
E2E test automation Playwright

Use mcp__playwright__* tools for browser automation:

Navigation

mcp__playwright__browser_navigate - Go to URL
mcp__playwright__browser_navigate_back - Go back
mcp__playwright__browser_snapshot - Get page accessibility tree (preferred over screenshot)
mcp__playwright__browser_take_screenshot - Capture screenshot

Interaction

mcp__playwright__browser_click - Click element
mcp__playwright__browser_type - Type text
mcp__playwright__browser_fill_form - Fill multiple fields
mcp__playwright__browser_select_option - Select dropdown option
mcp__playwright__browser_hover - Hover over element
mcp__playwright__browser_press_key - Press keyboard key

Debugging

mcp__playwright__browser_console_messages - Get console logs
mcp__playwright__browser_network_requests - Get network requests
mcp__playwright__browser_evaluate - Run JavaScript

Debugging Workflow

1. Visual Debugging

# Take snapshot first (shows element refs)
Use mcp__playwright__browser_snapshot

# Identify element by ref from snapshot
Use mcp__playwright__browser_click with ref="element-ref"

# Check console for errors
Use mcp__playwright__browser_console_messages

2. Network Debugging

# Get all network requests
Use mcp__playwright__browser_network_requests

# Filter for API calls
Use mcp__playwright__browser_network_requests with includeStatic=false

3. JavaScript Debugging

# Run arbitrary JS
Use mcp__playwright__browser_evaluate with function="() => document.title"

# Check element state
Use mcp__playwright__browser_evaluate with function="(el) => el.value" and element ref

E2E Test Patterns (Python)

Test Setup

# tests/e2e/conftest.py
import pytest
from playwright.async_api import async_playwright, Page, Browser

@pytest.fixture
async def browser() -> Browser:
    async with async_playwright() as p:
        browser = await p.chromium.launch(headless=True)
        yield browser
        await browser.close()

@pytest.fixture
async def page(browser: Browser) -> Page:
    context = await browser.new_context()
    page = await context.new_page()
    yield page
    await context.close()

@pytest.fixture
async def authenticated_page(page: Page, test_user: User) -> Page:
    """Page with logged-in user session."""
    await page.goto(f"{BASE_URL}/login")
    await page.fill('[name="username"]', test_user.username)
    await page.fill('[name="password"]', test_user.password)
    await page.click('button[type="submit"]')
    await page.wait_for_url("**/dashboard")
    return page

Page Object Pattern

# tests/e2e/pages/dashboard.py
from playwright.async_api import Page

class DashboardPage:
    def __init__(self, page: Page):
        self.page = page
        self.shootout_list = page.locator('[data-testid="shootout-list"]')
        self.create_button = page.get_by_role("button", name="Create Shootout")

    async def goto(self):
        await self.page.goto(f"{BASE_URL}/dashboard")

    async def create_shootout(self, title: str) -> None:
        await self.create_button.click()
        await self.page.fill('[name="title"]', title)
        await self.page.click('button[type="submit"]')

    async def get_shootout_count(self) -> int:
        return await self.shootout_list.locator('[data-testid="shootout-item"]').count()

Test Example

# tests/e2e/test_shootouts.py
import pytest
from tests.e2e.pages.dashboard import DashboardPage

@pytest.mark.asyncio
async def test_create_shootout(authenticated_page: Page):
    """User can create a new shootout."""
    dashboard = DashboardPage(authenticated_page)
    await dashboard.goto()

    initial_count = await dashboard.get_shootout_count()

    await dashboard.create_shootout("Test Tone Comparison")

    # Wait for new item to appear
    await authenticated_page.wait_for_selector('[data-testid="shootout-item"]')

    final_count = await dashboard.get_shootout_count()
    assert final_count == initial_count + 1

Assertions

from playwright.async_api import expect

# Visibility
await expect(page.locator(".error")).to_be_visible()
await expect(page.locator(".loading")).to_be_hidden()

# Text content
await expect(page.locator("h1")).to_have_text("Dashboard")
await expect(page.locator(".message")).to_contain_text("Success")

# Attributes
await expect(page.locator("input")).to_have_value("test@example.com")
await expect(page.locator("button")).to_be_disabled()

# URL
await expect(page).to_have_url("**/dashboard")

Locator Strategies (Priority Order)

  1. Role-based (most resilient)

    page.get_by_role("button", name="Submit")
    page.get_by_role("heading", name="Dashboard")
    page.get_by_role("textbox", name="Email")
    
  2. Test ID (explicit, stable)

    page.get_by_test_id("shootout-list")
    page.locator('[data-testid="create-button"]')
    
  3. Label/Placeholder (user-facing)

    page.get_by_label("Email")
    page.get_by_placeholder("Enter your email")
    
  4. CSS Selectors (last resort)

    page.locator(".submit-btn")
    page.locator("#main-form input[type='email']")
    

Debugging Tips

Console Errors

# Via MCP
Use mcp__playwright__browser_console_messages with level="error"

# In Python
page.on("console", lambda msg: print(f"Console: {msg.text}"))

Network Failures

# Via MCP
Use mcp__playwright__browser_network_requests

# In Python
page.on("requestfailed", lambda req: print(f"Failed: {req.url}"))

Screenshots on Failure

@pytest.fixture
async def page(browser: Browser, request):
    page = await browser.new_page()
    yield page
    if request.node.rep_call.failed:
        await page.screenshot(path=f"screenshots/{request.node.name}.png")

Trace Recording

context = await browser.new_context()
await context.tracing.start(screenshots=True, snapshots=True)

# ... run tests ...

await context.tracing.stop(path="trace.zip")
# Open with: npx playwright show-trace trace.zip

Running E2E Tests

E2E tests are in the playwright/ directory and run on the HOST (not in Docker):

# Navigate to playwright directory first
cd playwright

# All E2E tests
pnpm test

# Interactive UI mode (for debugging)
pnpm test:ui

# With visible browser
pnpm test:headed

# Debug mode (step through)
pnpm test:debug

# Specific test file
pnpm test tests/example.spec.ts

Note: The playwright/ tests use TypeScript Playwright. There are also Python E2E tests in backend/tests/e2e/ that run inside Docker for API-level testing.