Now liveThe Skillselion MCP - thousands of ranked skills, loaded into your agent mid-task. No install.Get it →
erichowens avatar

Webapp Testing

  • 119 installs
  • 178 repo stars
  • Updated July 14, 2026
  • erichowens/some_claude_skills

Run structured browser, UI, and regression checks on web apps before release or after frontend changes.

About

Webapp-testing guides Claude through systematic verification of browser-based applications, covering smoke tests, UI regressions, interaction flows, and release-readiness checks for SaaS and content sites.

  • UI regression coverage
  • Cross-browser sanity checks
  • Form and routing validation
  • Staging smoke test playbooks
  • Pre-release defect triage

Webapp Testing by the numbers

  • 119 all-time installs (skills.sh)
  • Ranked #948 of 2,153 Testing & QA skills by installs in the Skillselion catalog
  • Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/erichowens/some_claude_skills --skill webapp-testing

Add your badge

Show developers this skill is listed on Skillselion. Paste this into your README.

Listed on Skillselion
Installs119
repo stars178
Last updatedJuly 14, 2026
Repositoryerichowens/some_claude_skills

What it does

Run structured browser, UI, and regression checks on web apps before release or after frontend changes.

Files

SKILL.mdMarkdownGitHub ↗

Web Application Testing

Write native Python Playwright scripts to test local web applications.

When to Use

Use for:

  • E2E testing of web applications
  • UI automation and interaction testing
  • Visual regression testing
  • Browser log capture and debugging
  • Screenshot capture for verification
  • Form submission and validation testing

NOT for:

  • API-only testing without a browser (use requests/httpx)
  • Unit testing of individual functions
  • Mobile app testing (use Appium)
  • Load/performance testing (use k6/Locust)

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 → Start server first, then run Playwright
        │
        └─ 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

Core Playwright Patterns

Basic Test Structure

from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch(headless=True)  # Always headless
    page = browser.new_page()
    page.goto('http://localhost:5173')
    page.wait_for_load_state('networkidle')  # CRITICAL for SPAs

    # ... your test logic

    browser.close()

Reconnaissance-Then-Action Pattern

Step 1: Inspect rendered DOM

page.screenshot(path='/tmp/inspect.png', full_page=True)
content = page.content()
buttons = page.locator('button').all()

Step 2: Identify selectors from inspection results

Step 3: Execute actions using discovered selectors

Selector Strategy (Priority Order)

1. Role-based (best for accessibility):

   page.get_by_role("button", name="Submit")
   page.get_by_role("textbox", name="Email")

2. Text-based (readable, but fragile to copy changes):

   page.get_by_text("Sign In")
   page.get_by_label("Password")

3. Test IDs (stable, explicit):

   page.get_by_test_id("login-button")

4. CSS selectors (last resort):

   page.locator(".btn-primary")
   page.locator("#submit-form")

Common Anti-Patterns

Anti-Pattern: Not Waiting for Network Idle

Symptom: Tests pass locally, fail in CI; elements not found

Problem: Modern SPAs load content dynamically after initial page load

Solution:

# ❌ Wrong
page.goto('http://localhost:3000')
page.click('button')  # Element may not exist yet

# ✅ Correct
page.goto('http://localhost:3000')
page.wait_for_load_state('networkidle')
page.click('button')

Anti-Pattern: Hardcoded Waits

Symptom: time.sleep(3) scattered throughout tests

Problem: Slow, unreliable, doesn't adapt to actual page state

Solution:

# ❌ Wrong
time.sleep(5)
page.click('.dynamic-button')

# ✅ Correct
page.wait_for_selector('.dynamic-button', state='visible')
page.click('.dynamic-button')

Anti-Pattern: Inspecting DOM Before JavaScript Executes

Symptom: Empty page content, missing elements in static analysis

Problem: Reading HTML before client-side rendering completes

Solution: Always wait for networkidle on dynamic apps before inspection

Waiting Strategies

# Wait for element to appear
page.wait_for_selector('#my-element')

# Wait for element to be visible
page.wait_for_selector('#my-element', state='visible')

# Wait for element to be hidden
page.wait_for_selector('#my-element', state='hidden')

# Wait for navigation
page.wait_for_url('**/dashboard')

# Wait for network idle (all requests complete)
page.wait_for_load_state('networkidle')

# Custom wait with timeout
page.wait_for_function('document.querySelector(".loaded")')

Screenshot Patterns

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

# Element screenshot
page.locator('#header').screenshot(path='/tmp/header.png')

# Before/after comparison
page.screenshot(path='/tmp/before.png')
# ... perform action ...
page.screenshot(path='/tmp/after.png')

Console Log Capture

# Capture all console messages
messages = []
page.on('console', lambda msg: messages.append({
    'type': msg.type,
    'text': msg.text
}))

# Filter errors only
page.on('console', lambda msg:
    print(f'ERROR: {msg.text}') if msg.type == 'error' else None
)

Form Testing

# Fill form fields
page.fill('#email', 'test@example.com')
page.fill('#password', 'secret123')

# Select dropdown
page.select_option('#country', 'US')

# Check checkbox
page.check('#terms')

# Submit form
page.click('button[type="submit"]')

# Verify submission
page.wait_for_url('**/success')

Assertions

from playwright.sync_api import expect

# Element assertions
expect(page.locator('#title')).to_have_text('Welcome')
expect(page.locator('#count')).to_have_text('5')
expect(page.locator('.error')).to_be_hidden()
expect(page.locator('#submit')).to_be_enabled()

# Page assertions
expect(page).to_have_url('http://localhost:3000/dashboard')
expect(page).to_have_title('My App')

Multi-Page Scenarios

# Handle popup windows
with page.expect_popup() as popup_info:
    page.click('#open-popup')
popup = popup_info.value
popup.wait_for_load_state()

# Handle new tabs
with context.expect_page() as new_page_info:
    page.click('a[target="_blank"]')
new_page = new_page_info.value

Test File Organization

tests/
├── conftest.py          # Shared fixtures
├── test_login.py        # Login flows
├── test_dashboard.py    # Dashboard features
├── test_forms.py        # Form submissions
└── screenshots/         # Visual artifacts

Running Tests

# Run single test file
python -m pytest tests/test_login.py

# Run with browser visible (debugging)
PWDEBUG=1 python -m pytest tests/test_login.py

# Generate trace for debugging
python -m pytest --tracing=on tests/test_login.py

Best Practices

1. Use `sync_playwright()` for synchronous scripts 2. Always close the browser when done 3. Use descriptive selectors: role, text, test-id over CSS 4. Add appropriate waits: wait_for_selector(), wait_for_load_state() 5. Capture screenshots on failure for debugging 6. Keep tests independent - each test should set up its own state

---

This skill encodes: Playwright best practices | Selector strategies | Wait patterns | Anti-pattern prevention | E2E testing workflows

Related skills

Testing & QAtestingfrontend

This week in AI coding

Five minutes, every Monday - the tools, releases and tactics for developers.

unsubscribe anytime.