---
name: webapp-testing
description: Toolkit for testing web applications using Playwright. Supports verifying frontend functionality, debugging UI behavior, capturing screenshots, running end-to-end tests, and viewing browser logs. Use when user asks to test a web app, verify UI behavior, or debug frontend issues.
triggers:
  - test the web app
  - verify the UI
  - run e2e tests
  - check if this works
  - debug the frontend
  - test this page
  - screenshot the app
---

# Web Application Testing

Test local and deployed web applications using Python Playwright scripts. Verify functionality, debug UI behavior, capture screenshots, and run end-to-end tests.

## When to Use

Trigger when user wants to:
- Test a web application's functionality
- Verify UI behavior after changes
- Debug frontend issues
- Capture screenshots for documentation or verification
- Run end-to-end test scenarios
- Check responsive design across viewports

## Decision Tree

```
User task → Is it static HTML?
├─ Yes → Read HTML file directly to identify selectors
│         ├─ Success → Write Playwright script using selectors
│         └─ Fails → Treat as dynamic (below)
│
└─ No (dynamic webapp) → Is the server already running?
    ├─ No → Start the server first, then test
    └─ 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 Pattern: Reconnaissance-Then-Action

Always inspect before acting. Never assume selectors exist.

```python
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:3000')
    page.wait_for_load_state('networkidle')  # CRITICAL: Wait for JS
    
    # 1. Inspect rendered DOM
    page.screenshot(path='/tmp/inspect.png', full_page=True)
    
    # 2. Discover elements
    buttons = page.locator('button').all()
    inputs = page.locator('input').all()
    
    # 3. Execute actions using discovered selectors
    page.locator('text=Submit').click()
    page.wait_for_selector('.success-message')
    
    browser.close()
```

## Testing Patterns

### Functional Test
```python
def test_login_flow():
    with sync_playwright() as p:
        browser = p.chromium.launch(headless=True)
        page = browser.new_page()
        page.goto('http://localhost:3000/login')
        page.wait_for_load_state('networkidle')
        
        page.fill('input[name="email"]', 'test@example.com')
        page.fill('input[name="password"]', 'password123')
        page.click('button[type="submit"]')
        
        page.wait_for_url('**/dashboard')
        assert page.url.endswith('/dashboard')
        
        browser.close()
```

### Visual Regression
```python
def capture_screenshots():
    with sync_playwright() as p:
        browser = p.chromium.launch(headless=True)
        
        viewports = [
            {"width": 1920, "height": 1080, "name": "desktop"},
            {"width": 768, "height": 1024, "name": "tablet"},
            {"width": 375, "height": 812, "name": "mobile"},
        ]
        
        for vp in viewports:
            page = browser.new_page(viewport_size={"width": vp["width"], "height": vp["height"]})
            page.goto('http://localhost:3000')
            page.wait_for_load_state('networkidle')
            page.screenshot(path=f'/tmp/{vp["name"]}.png', full_page=True)
            page.close()
        
        browser.close()
```

### Console Error Check
```python
def check_console_errors():
    errors = []
    with sync_playwright() as p:
        browser = p.chromium.launch(headless=True)
        page = browser.new_page()
        page.on("console", lambda msg: errors.append(msg.text) if msg.type == "error" else None)
        
        page.goto('http://localhost:3000')
        page.wait_for_load_state('networkidle')
        
        # Navigate through key pages
        for path in ['/', '/about', '/dashboard']:
            page.goto(f'http://localhost:3000{path}')
            page.wait_for_load_state('networkidle')
        
        browser.close()
    
    if errors:
        print(f"Found {len(errors)} console errors:")
        for e in errors:
            print(f"  - {e}")
    else:
        print("No console errors found.")
```

## Common Pitfalls

| Pitfall | Fix |
|---------|-----|
| Acting before page loads | Always `wait_for_load_state('networkidle')` |
| Assuming selectors exist | Inspect DOM first, then act |
| Not waiting for navigation | Use `wait_for_url()` after clicks that navigate |
| Ignoring async rendering | Use `wait_for_selector()` for dynamic content |
| Testing in headed mode | Always `headless=True` for CI/automation |

## Setup

Install Playwright if not available:
```bash
sudo pip3 install playwright
playwright install chromium
```

## Output

After testing, deliver:
1. Test results (pass/fail with details)
2. Screenshots of key states
3. Console errors found (if any)
4. Specific recommendations for fixes
