Agent skill

webapp-testing

Interact with and test web applications using Playwright.

6 files · 16.7 KB · Source ↗ · Raw SKILL.md · Markdown directory · Download skill.tgz

Copy the prompt. Paste it into your agent.

Verify the bytes and inspect the files before trusting a skill. A matching hash is not a safety review.

Read the install prompt
Full inline prompt for offline use

Includes SKILL.md. Supporting files still require a download.

Install from a shell

Run this in a terminal. It downloads the pinned archive, checks its SHA-256 digest, and extracts it into ~/.claude/skills, where Claude Code loads skills. For Codex and other agents that read ~/.agents/skills, edit the SKILLS_DIR line. If the skill is already installed, the command stops and changes nothing.

Web Application Testing

Test and interact with web applications using Playwright. Supports ad-hoc browser tasks, Python test scripts, and TypeScript E2E patterns.

Ad-hoc Browser Interaction

For quick one-off tasks — no test framework or codebase required.

from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch(headless=True)
    page = browser.new_page()
    page.goto('https://example.com')
    page.wait_for_load_state('networkidle')

    # Screenshot
    page.screenshot(path='/tmp/screenshot.png', full_page=True)

    # Mobile viewport
    page.set_viewport_size({"width": 390, "height": 844})
    page.screenshot(path='/tmp/mobile.png', full_page=True)

    # Read content
    title = page.title()
    text = page.locator('main').inner_text()

    # Interact
    page.fill('[name="email"]', 'test@example.com')
    page.click('button[type="submit"]')

    browser.close()

Common ad-hoc tasks:

Save scripts to /tmp/ and run with python /tmp/script.py. No project setup needed — just pip install playwright && playwright install chromium.


Language Detection

Project has pyproject.toml / requirements.txt → Python (below)
Project has package.json / bun.lock / pnpm-lock.yaml → TypeScript (below)
Both → Prefer TypeScript; use Python if existing tests are Python

Python Playwright

Helper Scripts Available:

Always run scripts with --help first to see usage. DO NOT read the source until you try running the script first and find that a customized solution is absolutely necessary. These scripts can be very large and thus pollute your context window. They exist to be called directly as black-box scripts rather than ingested into your context window.

Decision Tree

User task → Is it static HTML?
    ├─ Yes → Read HTML file directly to identify selectors
    │         ├─ Success → Write Playwright script using selectors
    │         └─ Fails/Incomplete → Treat as dynamic (below)
    │
    └─ No (dynamic webapp) → Is the server already running?
        ├─ No → Run: python scripts/with_server.py --help
        │        Then use the helper + write simplified Playwright script
        │
        └─ Yes → Reconnaissance-then-action:
            1. Navigate and wait for networkidle
            2. Take screenshot or inspect DOM
            3. Identify selectors from rendered state
            4. Execute actions with discovered selectors

Example: Using with_server.py

Single server:

python scripts/with_server.py --server "npm run dev" --port 5173 -- python your_automation.py

Multiple servers (e.g., backend + frontend):

python scripts/with_server.py \
  --server "cd backend && python server.py" --port 3000 \
  --server "cd frontend && npm run dev" --port 5173 \
  -- python your_automation.py

Automation script (servers managed automatically):

from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch(headless=True)
    page = browser.new_page()
    page.goto('http://localhost:5173')
    page.wait_for_load_state('networkidle')  # CRITICAL: Wait for JS to execute
    # ... your automation logic
    browser.close()

Reference Files


TypeScript Playwright

For projects using npm/pnpm/bun with @playwright/test.

Running Tests

Detect test command from package.json:

grep -E '"test"|"test:e2e"|"test:playwright"' package.json

Common patterns:

Dev Server

Most projects require the dev server running first:

# Terminal 1: Start dev server
bun run dev  # detect from lockfiles

# Terminal 2: Run tests
bun test

Check wrangler.jsonc or dev script for port (default Wrangler: 8787):

const BASE_URL = process.env.BASE_URL || 'http://localhost:8787';
await page.goto(BASE_URL);

Test Structure

Tests live in e2e/, tests/, or tests/e2e/:

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

test('user can complete flow', async ({ page }) => {
  await page.goto('http://localhost:8787');
  await page.waitForLoadState('networkidle');
  await page.click('text=Get Started');
  await page.fill('[name="email"]', 'test@example.com');
  await expect(page.locator('.success')).toBeVisible();
});

Screenshot Capture

await page.screenshot({ path: 'screenshots/step-1.png' });
await page.screenshot({ path: 'screenshots/full.png', fullPage: true });
await page.locator('.component').screenshot({ path: 'screenshots/component.png' });

Debugging

npx playwright test --headed     # visible browser
npx playwright test --ui         # interactive UI mode
npx playwright test --debug tests/auth.spec.ts  # debug specific test

Selectors

Prefer semantic selectors:

page.getByRole('button', { name: 'Submit' })
page.getByLabel('Email')
page.getByText('Welcome')
page.getByTestId('submit-btn')  // fallback
page.locator('.submit-button')  // last resort

Waiting Strategies

await page.waitForLoadState('networkidle');
await page.waitForSelector('.loaded');
await page.waitForResponse(resp => resp.url().includes('/api/'));
await expect(page.locator('.result')).toBeVisible({ timeout: 10000 });

Common Patterns (Both Languages)

Reconnaissance-Then-Action

  1. Navigate and wait for networkidle
  2. Take screenshot or inspect DOM
  3. Identify selectors from rendered state
  4. Execute actions with discovered selectors

Common Pitfall

Don't inspect the DOM before waiting for networkidle on dynamic apps.

Best Practices


Writing Testable Frontend Code

When building UI components, follow these patterns so Playwright tests stay stable across refactors.

Selector priority (most to least resilient):

  1. getByRole / getByLabel — semantic, survives styling changes
  2. getByTestId — stable, explicit contract between code and tests
  3. getByText — readable, but breaks on copy changes
  4. CSS class / XPath — fragile, avoid

Authoring guidelines:

Example — testable form:

<form data-testid="login-form">
  <label for="email">Email</label>
  <input id="email" type="email" aria-label="Email" />
  <button type="submit">Sign In</button>
</form>

Tests can use: getByLabel('Email'), getByRole('button', { name: 'Sign In' }), or getByTestId('login-form').


Visual Analysis (Subagent)

For dispatched visual analysis of test screenshots and UI/UX quality assessment:

Task(
  subagent_type: "general-purpose",
  model: "sonnet",
  prompt: "Use the webapp-testing skill. You are a Playwright
    test engineer and UI/UX analyst. Run the E2E tests, capture screenshots at
    key interaction points, then analyze for:
    - Functionality: Does the UI reflect expected state?
    - Usability: Are interactive elements accessible?
    - Visual hierarchy: Is information organized logically?
    - Consistency: Do elements follow design patterns?
    - Accessibility: Contrast ratios, focus states
    Project: {project}
    Tests: {test command or path}
    Return a structured report: test summary, visual findings, UX observations,
    prioritized recommendations, and screenshot inventory."
)

Files