webapp-testing

Web Application Testing

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "webapp-testing" with this command: npx skills add 5dlabs/cto/5dlabs-cto-webapp-testing

Web Application Testing

Test local web applications using Playwright with a systematic approach.

Functional vs Visual Testing

CRITICAL: Choose the right approach based on what you're verifying.

Testing Type Method Returns Use For

Functional Accessibility tree / DOM inspection Text (parseable) Button exists, text appears, form works, element state

Visual Screenshot Image (not parseable) Layout, colors, styling, animations, visual regression

Functional Testing (Checking Behavior)

Use DOM inspection or accessibility tree queries when verifying behavior:

Get all interactive elements

buttons = page.locator('button').all() for btn in buttons: print(btn.text_content()) # Agent CAN read and verify this

Verify element exists and has correct state

assert page.locator('text=Submit').is_visible() assert page.locator('#email').input_value() == 'test@example.com'

For MCP browser tools: Use take_snapshot which returns the accessibility tree as text.

Visual Testing (Checking Appearance)

Use screenshots ONLY when verifying appearance:

Capture for visual comparison

page.screenshot(path='dashboard.png', full_page=True)

For MCP browser tools: Use take_screenshot when checking layout, colors, or styling.

Common Mistake

BAD: Taking screenshot to verify button exists

page.screenshot(path='check.png') # Agent cannot "read" this image!

GOOD: Use DOM/accessibility to verify button exists

assert page.locator('text=Submit').is_visible()

Decision Tree: Choose Your Approach

User task → Is it static HTML? │ ├─ Yes → Read HTML file directly to identify selectors │ └─ Write Playwright script using discovered selectors │ └─ No (dynamic webapp) → Is the server already running? │ ├─ No → Start server first, then test │ (use process management or test framework) │ └─ Yes → Use Reconnaissance-Then-Action pattern: 1. Navigate and wait for networkidle 2. Take screenshot or inspect DOM 3. Identify selectors from rendered state 4. Execute actions with discovered selectors

Reconnaissance-Then-Action Pattern

For dynamic apps, always inspect before acting:

Step 1: Navigate and Wait

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')

# CRITICAL: Wait for JS to execute
page.wait_for_load_state('networkidle')

Step 2: Inspect Rendered DOM

Option A: Screenshot for visual inspection

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

Option B: Get page content

content = page.content()

Option C: Find specific elements

buttons = page.locator('button').all() for btn in buttons: print(btn.text_content())

Step 3: Identify Selectors

From inspection results, determine the right selectors:

Selector Type Example When to Use

Text text=Submit

Visible button/link text

Role role=button[name="Submit"]

Accessibility-friendly

CSS #submit-btn , .primary-action

Unique IDs or classes

Data attributes [data-testid="submit"]

Test-specific attributes

Step 4: Execute Actions

Now interact with discovered selectors

page.locator('text=Submit').click() page.locator('#email').fill('test@example.com') page.locator('form').press('Enter')

Common Pitfall

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

Without this wait, you'll see the initial HTML before JavaScript renders the actual UI.

Multi-Server Testing

When testing apps with separate frontend and backend:

import subprocess import time

Start backend

backend = subprocess.Popen(['python', 'server.py'], cwd='backend/')

Start frontend

frontend = subprocess.Popen(['npm', 'run', 'dev'], cwd='frontend/')

Wait for servers to be ready

time.sleep(5) # Or use health check polling

try: # Run your tests with sync_playwright() as p: browser = p.chromium.launch(headless=True) page = browser.new_page() page.goto('http://localhost:5173') # ... test logic finally: backend.terminate() frontend.terminate()

Testing Patterns

Form Testing

Fill form

page.locator('#name').fill('Test User') page.locator('#email').fill('test@example.com') page.locator('select#country').select_option('US') page.locator('input[type="checkbox"]').check()

Submit and verify

page.locator('button[type="submit"]').click() page.wait_for_selector('.success-message') assert page.locator('.success-message').is_visible()

Navigation Testing

Click link and verify navigation

page.locator('text=About').click() page.wait_for_url('**/about') assert 'About' in page.title()

API Response Testing

Intercept network requests

with page.expect_response('**/api/users') as response_info: page.locator('button.load-users').click()

response = response_info.value assert response.status == 200 data = response.json() assert len(data['users']) > 0

Visual Regression

Compare screenshots

page.goto('http://localhost:5173/dashboard') page.wait_for_load_state('networkidle')

Take screenshot for comparison

page.screenshot(path='dashboard-current.png', full_page=True)

Compare with baseline (use image diff tool)

Console & Error Monitoring

Capture console messages

console_messages = [] page.on('console', lambda msg: console_messages.append(msg.text))

Capture errors

errors = [] page.on('pageerror', lambda err: errors.append(str(err)))

Run test

page.goto('http://localhost:5173') page.wait_for_load_state('networkidle')

Check for issues

assert len(errors) == 0, f"Page errors: {errors}" assert not any('error' in msg.lower() for msg in console_messages)

Wait Strategies

Method Use When

wait_for_load_state('networkidle')

Initial page load, SPA navigation

wait_for_selector('.element')

Waiting for specific element to appear

wait_for_url('**/path')

After navigation actions

wait_for_response('/api/')

After triggering API calls

wait_for_timeout(1000)

Last resort for timing-dependent UIs

Best Practices

  • Always launch headless: browser = p.chromium.launch(headless=True)

  • Always close browser: Use with context or explicit browser.close()

  • Use descriptive selectors: Prefer text= , role= , or data-testid= over fragile CSS

  • Add appropriate waits: Don't assume elements are immediately available

  • Capture evidence: Screenshot on failures for debugging

  • Isolate tests: Each test should set up its own state

Debugging Tips

Slow down for debugging

browser = p.chromium.launch(headless=False, slow_mo=500)

Pause for manual inspection

page.pause()

Get element state

element = page.locator('#my-button') print(f"Visible: {element.is_visible()}") print(f"Enabled: {element.is_enabled()}") print(f"Text: {element.text_content()}")

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

expo-patterns

No summary provided by upstream source.

Repository SourceNeeds Review
General

elysia-llm-docs

No summary provided by upstream source.

Repository SourceNeeds Review
General

better-auth-expo

No summary provided by upstream source.

Repository SourceNeeds Review
General

anime-js

No summary provided by upstream source.

Repository SourceNeeds Review