Claude Code Plugins

Community-maintained marketplace

Feedback

Toolkit for interacting with and testing local web applications using Playwright. Supports verifying frontend functionality, debugging UI behavior, capturing browser screenshots, and viewing browser logs.

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 webapp-testing
version 1.2.0
description Toolkit for interacting with and testing local web applications using Playwright. Supports verifying frontend functionality, debugging UI behavior, capturing browser screenshots, and viewing browser logs.
license Apache-2.0
metadata [object Object]
compatibility Requires Python 3.11+, Playwright installed, Chromium browser
allowed-tools Bash, Read, Write, Edit
dependencies [object Object]

Web Application Testing

To test local web applications, write native Python Playwright scripts.

Helper Scripts Available:

  • scripts/with_server.py - Manages server lifecycle (supports multiple servers)

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.

Browser Tools (Direct Chrome DevTools Control)

The bin/browser-tools utility provides lightweight, context-rot-proof browser automation using the Chrome DevTools Protocol directly (no MCP overhead).

Available Commands:

# Launch browser and get connection details
bin/browser-tools start [--port PORT] [--headless]

# Navigate to URL
bin/browser-tools nav <url> [--wait-for {load|networkidle|domcontentloaded}]

# Evaluate JavaScript
bin/browser-tools eval "<javascript code>"

# Take screenshot
bin/browser-tools screenshot <output.png> [--full-page] [--selector CSS_SELECTOR]

# Interactive element picker (returns selectors)
bin/browser-tools pick

# Get console logs
bin/browser-tools console [--level {log|warn|error|all}]

# Search page content
bin/browser-tools search "<query>" [--case-sensitive]

# Extract page content (markdown, links, text)
bin/browser-tools content [--format {markdown|links|text}]

# Get/set cookies
bin/browser-tools cookies [--set NAME=VALUE] [--domain DOMAIN]

# Inspect element details
bin/browser-tools inspect <css-selector>

# Terminate browser session
bin/browser-tools kill

When to Use Browser-Tools vs Playwright:

Use browser-tools when:

  • Quick inspection/debugging of running apps
  • Need to identify selectors interactively (pick command)
  • Extracting page content for analysis
  • Running simple JavaScript snippets
  • Monitoring console logs in real-time
  • Taking quick screenshots without writing scripts

Use Playwright when:

  • Complex multi-step automation workflows
  • Need full test assertion framework
  • Handling multi-step forms
  • Database integration (Supabase utilities)
  • Comprehensive test coverage
  • Generating test reports

Example Workflow:

# 1. Start browser pointed at your app
bin/browser-tools start --port 9222

# 2. Navigate to the page
bin/browser-tools nav http://localhost:3000

# 3. Use interactive picker to find selectors
bin/browser-tools pick
# Click on elements in the browser, get their selectors

# 4. Inspect specific elements
bin/browser-tools inspect "button.submit"

# 5. Take screenshot for documentation
bin/browser-tools screenshot /tmp/page.png --full-page

# 6. Check console for errors
bin/browser-tools console --level error

# 7. Clean up
bin/browser-tools kill

Benefits:

  • No MCP overhead: Direct Chrome DevTools Protocol communication
  • Context-rot proof: No dependency on evolving MCP specifications
  • Interactive picker: Visual element selection for selector discovery
  • Lightweight: Faster than full Playwright for simple tasks
  • Pre-compiled binary: Ready to use, no compilation needed (auto-compiled via create-rule.js)

Decision Tree: Choosing Your Approach

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

To start a server, run --help first, then use the helper:

Single server:

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

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

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

To create an automation script, include only Playwright logic (servers are managed automatically):

from playwright.sync_api import sync_playwright

APP_PORT = 3000  # Match the port from --port argument

with sync_playwright() as p:
    browser = p.chromium.launch(headless=True) # Always launch chromium in headless mode
    page = browser.new_page()
    page.goto(f'http://localhost:{APP_PORT}') # Server already running and ready
    page.wait_for_load_state('networkidle') # CRITICAL: Wait for JS to execute
    # ... your automation logic
    browser.close()

Reconnaissance-Then-Action Pattern

  1. Inspect rendered DOM:

    page.screenshot(path='/tmp/inspect.png', full_page=True)
    content = page.content()
    page.locator('button').all()
    
  2. Identify selectors from inspection results

  3. Execute actions using discovered selectors

Common Pitfall

Don't inspect the DOM before waiting for networkidle on dynamic apps ✅ Do wait for page.wait_for_load_state('networkidle') before inspection

Best Practices

  • Use bundled scripts as black boxes - To accomplish a task, consider whether one of the scripts available in scripts/ can help. These scripts handle common, complex workflows reliably without cluttering the context window. Use --help to see usage, then invoke directly.
  • Use sync_playwright() for synchronous scripts
  • Always close the browser when done
  • Use descriptive selectors: text=, role=, CSS selectors, or IDs
  • Add appropriate waits: page.wait_for_selector() or page.wait_for_timeout()

Utility Modules

The skill now includes comprehensive utilities for common testing patterns:

UI Interactions (utils/ui_interactions.py)

Handle common UI patterns automatically:

from utils.ui_interactions import (
    dismiss_cookie_banner,
    dismiss_modal,
    click_with_header_offset,
    force_click_if_needed,
    wait_for_no_overlay,
    wait_for_stable_dom
)

# Dismiss cookie consent
dismiss_cookie_banner(page)

# Close welcome modal
dismiss_modal(page, modal_identifier="Welcome")

# Click button behind fixed header
click_with_header_offset(page, 'button#submit', header_height=100)

# Try click with force fallback
force_click_if_needed(page, 'button#action')

# Wait for loading overlays to disappear
wait_for_no_overlay(page)

# Wait for DOM to stabilize
wait_for_stable_dom(page)

Smart Form Filling (utils/form_helpers.py)

Intelligently handle form variations:

from utils.form_helpers import (
    SmartFormFiller,
    handle_multi_step_form,
    auto_fill_form
)

# Works with both "Full Name" and "First/Last Name" fields
filler = SmartFormFiller()
filler.fill_name_field(page, "Jane Doe")
filler.fill_email_field(page, "jane@example.com")
filler.fill_password_fields(page, "SecurePass123!")
filler.fill_phone_field(page, "+447700900123")
filler.fill_date_field(page, "1990-01-15", field_hint="birth")

# Auto-fill entire form
results = auto_fill_form(page, {
    'email': 'test@example.com',
    'password': 'Pass123!',
    'full_name': 'Test User',
    'phone': '+447700900123',
    'date_of_birth': '1990-01-15'
})

# Handle multi-step forms
steps = [
    {'fields': {'email': 'test@example.com', 'password': 'Pass123!'}, 'checkbox': True},
    {'fields': {'full_name': 'Test User', 'date_of_birth': '1990-01-15'}},
    {'complete': True}
]
handle_multi_step_form(page, steps)

Supabase Testing (utils/supabase.py)

Database operations for Supabase-based apps:

from utils.supabase import SupabaseTestClient, quick_cleanup

# Initialize client
client = SupabaseTestClient(
    url="https://project.supabase.co",
    service_key="your-service-role-key",
    db_password="your-db-password"
)

# Create test user
user_id = client.create_user("test@example.com", "password123")

# Create invite code
client.create_invite_code("TEST2024", code_type="general")

# Bypass email verification
client.confirm_email(user_id)

# Cleanup after test
client.cleanup_related_records(user_id)
client.delete_user(user_id)

# Quick cleanup helper
quick_cleanup("test@example.com", "db_password", "https://project.supabase.co")

Advanced Wait Strategies (utils/wait_strategies.py)

Better alternatives to simple sleep():

from utils.wait_strategies import (
    wait_for_api_call,
    wait_for_element_stable,
    smart_navigation_wait,
    combined_wait
)

# Wait for specific API response
response = wait_for_api_call(page, '**/api/profile**')

# Wait for element to stop moving
wait_for_element_stable(page, '.dropdown-menu', stability_ms=1000)

# Smart navigation with URL check
page.click('button#login')
smart_navigation_wait(page, expected_url_pattern='**/dashboard**')

# Comprehensive wait (network + DOM + overlays)
combined_wait(page)

Smart Selectors (utils/smart_selectors.py) ⭐ NEW

Automatically try multiple selector strategies to find elements, reducing test brittleness:

from utils.smart_selectors import SelectorStrategies

# Find and fill email field (tries 7 different selector strategies)
success = SelectorStrategies.smart_fill(page, 'email', 'test@example.com')
# Output: ✓ Found field via placeholder: input[placeholder*="email" i]
#         ✓ Filled 'email' with value

# Find and click button (tries 8 different strategies)
success = SelectorStrategies.smart_click(page, 'Sign In')
# Output: ✓ Found button via case-insensitive text: button:text-matches("Sign In", "i")
#         ✓ Clicked 'Sign In' button

# Manual control - find input field selector
selector = SelectorStrategies.find_input_field(page, 'password')
if selector:
    page.fill(selector, 'my-password')

# Manual control - find button selector
selector = SelectorStrategies.find_button(page, 'Submit')
if selector:
    page.click(selector)

# Try custom selectors with fallback
selectors = ['button#submit', 'button.submit-btn', 'input[type="submit"]']
selector = SelectorStrategies.find_any_element(page, selectors)

Selector Strategies Tried (in order):

For input fields:

  1. Test IDs: [data-testid*="email"]
  2. ARIA labels: input[aria-label*="email"]
  3. Placeholder: input[placeholder*="email"]
  4. Name attribute: input[name*="email"]
  5. Type attribute: input[type="email"]
  6. ID exact: #email
  7. ID partial: input[id*="email"]

For buttons:

  1. Test IDs: [data-testid*="sign-in"]
  2. Name attribute: button[name*="Sign In"]
  3. Exact text: button:has-text("Sign In")
  4. Case-insensitive: button:text-matches("Sign In", "i")
  5. Link as button: a:has-text("Sign In")
  6. Submit input: input[type="submit"][value*="Sign In"]
  7. Role button: [role="button"]:has-text("Sign In")

When to use:

  • ✅ Forms where HTML structure changes frequently
  • ✅ Third-party components with unpredictable selectors
  • ✅ Multi-application test suites
  • ❌ Performance-critical tests (adds 5s timeout per strategy)

Performance:

  • Timeout per strategy: 5 seconds (configurable)
  • Max timeout: 10 seconds across all strategies
  • Typical: First strategy works (1-2 seconds)

Browser Configuration (utils/browser_config.py) ⭐ NEW

Auto-configure browser context for testing environments:

from utils.browser_config import BrowserConfig

# Auto-detect CSP bypass for localhost
context = BrowserConfig.create_test_context(
    browser,
    'http://localhost:3000'
)
# Output:
# ============================================================
#   Browser Context Configuration
# ============================================================
#   Base URL: http://localhost:3000
#   🔓 CSP bypass: ENABLED (testing on localhost)
#   ⚠️  HTTPS errors: IGNORED (self-signed certs OK)
#   📐 Viewport: 1280x720
# ============================================================

# Production testing (no CSP bypass)
context = BrowserConfig.create_test_context(
    browser,
    'https://production.example.com'
)
# Output: 🔒 CSP bypass: DISABLED (production mode)

# Mobile device emulation
context = BrowserConfig.create_mobile_context(
    browser,
    device='iPhone 12',
    base_url='http://localhost:3000'
)
# Output: 📱 Mobile context: iPhone 12
#         Viewport: {'width': 390, 'height': 844}
#         🔓 CSP bypass: ENABLED

# Manual override
context = BrowserConfig.create_test_context(
    browser,
    'http://localhost:3000',
    bypass_csp=False,  # Force disable even for localhost
    record_video=True,  # Record test session
    extra_http_headers={'Authorization': 'Bearer token'}
)

Features:

  • Auto CSP detection: Enables bypass for localhost, disables for production
  • Self-signed certs: Ignores HTTPS errors by default
  • Consistent viewport: 1280x720 default for reproducible tests
  • Mobile emulation: Built-in device profiles
  • Video recording: Optional session recording
  • Verbose logging: See exactly what's configured

When to use:

  • ✅ Every test (replaces manual browser.new_context())
  • ✅ Testing on localhost with CSP restrictions
  • ✅ Mobile-responsive testing
  • ✅ Need consistent browser configuration across tests

CSP Monitoring (utils/ui_interactions.py) ⭐ NEW

Auto-detect and suggest fixes for Content Security Policy violations:

from utils.ui_interactions import setup_page_with_csp_handling
from utils.browser_config import BrowserConfig

context = BrowserConfig.create_test_context(browser, 'http://localhost:7160')
page = context.new_page()

# Enable CSP violation monitoring
setup_page_with_csp_handling(page)

page.goto('http://localhost:7160')

# If CSP violation occurs, you'll see:
# ======================================================================
# ⚠️  CSP VIOLATION DETECTED
# ======================================================================
# Message: Refused to execute inline script because it violates...
#
# 💡 SUGGESTION:
#    For localhost testing, use:
#
#    from utils.browser_config import BrowserConfig
#    context = BrowserConfig.create_test_context(
#        browser, 'http://localhost:3000'
#    )
#    # Auto-enables CSP bypass for localhost
#
#    Or manually:
#    context = browser.new_context(bypass_csp=True)
# ======================================================================

When to use:

  • ✅ Debugging why tests fail on localhost
  • ✅ Identifying CSP configuration issues
  • ✅ Verifying CSP bypass is working
  • ❌ Production testing (CSP should be enforced)

Complete Examples

Multi-Step Registration

See examples/multi_step_registration.py for a complete example showing:

  • Database setup (invite codes)
  • Cookie banner dismissal
  • Multi-step form automation
  • Email verification bypass
  • Login flow
  • Dashboard verification
  • Cleanup

Run it:

python examples/multi_step_registration.py

Using the Webapp-Testing Subagent

A specialized subagent is available for testing automation. Use it to keep your main conversation focused on development:

You: "Use webapp-testing agent to register test@example.com and verify the parent role switch works"

Main Agent: [Launches webapp-testing subagent]

Webapp-Testing Agent: [Runs complete automation, returns results]

Benefits:

  • Keeps main context clean
  • Specialized for Playwright automation
  • Access to all skill utilities
  • Automatic screenshot capture
  • Clear result reporting

Reference Files

  • examples/ - Examples showing common patterns:

    • element_discovery.py - Discovering buttons, links, and inputs on a page
    • static_html_automation.py - Using file:// URLs for local HTML
    • console_logging.py - Capturing console logs during automation
    • multi_step_registration.py - Complete registration flow example (NEW)
  • utils/ - Reusable utility modules (NEW):

    • ui_interactions.py - Cookie banners, modals, overlays, stable waits
    • form_helpers.py - Smart form filling, multi-step automation
    • supabase.py - Database operations for Supabase apps
    • wait_strategies.py - Advanced waiting patterns