# Playwright Python for QA Testing

## Environment Setup

Playwright Python is available on this system:
- **Package**: `/home/ubuntu/.hermes/hermes-agent/venv/lib/python3.11/site-packages/playwright/`
- **Chromium**: `~/.cache/ms-playwright/chromium-1217/`
- **Import**: `from playwright.sync_api import sync_playwright`
- **Run**: `python3 script.py` (not `node` — Playwright Node module may not be installed)

## Basic Pattern

```python
from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch(headless=True)
    context = browser.new_context()
    page = context.new_page()

    # Login
    page.goto('https://auth.example.com/login')
    page.wait_for_load_state('networkidle')
    page.fill('#username', 'admin_user')
    page.fill('#password', 'password')
    page.click('button[type="submit"]')
    page.wait_for_url('**/login-success**', timeout=10000)

    # Navigate to target
    page.goto('https://www.example.com/admin')
    page.wait_for_load_state('networkidle')

    # Interact and test...

    browser.close()
```

## Event Monitoring (Critical for QA)

### Console Messages and JS Errors
```python
console_msgs = []
page_errors = []
page.on("console", lambda m: console_msgs.append(f"[{m.type}] {m.text}"))
page.on("pageerror", lambda e: page_errors.append(str(e)))

# After interactions:
for m in console_msgs:
    print(f"  {m}")
for e in page_errors:
    print(f"  ERROR: {e}")
```

### Network Requests and Responses
```python
api_responses = []
def on_response(r):
    if '/api/' in r.url or '/admin/' in r.url:
        api_responses.append({"url": r.url, "status": r.status})
page.on("response", on_response)

# After interactions:
for r in api_responses:
    print(f"  {r['url']} -> {r['status']}")
```

### Failed Resource Loads
```python
failed_resources = []
page.on("requestfailed", lambda r: failed_resources.append({"url": r.url, "error": r.failure}))
```

## Element Query Patterns

```python
# By text content
btn = page.query_selector('button:has-text("Save")')
links = page.query_selector_all('a:has-text("Login")')

# By CSS selector with attribute
input_el = page.query_selector('input[name="csrf_token"]')
form = page.query_selector('#contentForm')

# By role
submit = page.query_selector('button[type="submit"]')

# Get all buttons (for debugging)
all_btns = page.query_selector_all('button')
btn_texts = [b.inner_text().strip() for b in all_btns]
```

## JavaScript Evaluation

```python
# Check if function is defined
is_defined = page.evaluate("typeof saveDraft === 'function'")

# Get element properties
val = page.evaluate("document.getElementById('contentJson').value")

# Get page content
page_html = page.content()
body_text = page.inner_text('body')

# Execute arbitrary JS
result = page.evaluate("() => { return document.title; }")
```

## Screenshots

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

# Then analyze with vision tool
```

## Cookie Inspection

```python
cookies = context.cookies()
for c in cookies:
    print(f"{c['name']}: domain={c['domain']}, httpOnly={c['httpOnly']}, "
          f"secure={c['secure']}, sameSite={c.get('sameSite','N/A')}")
```

## Testing with Custom Headers (e.g., Bearer Token)

```python
# Create separate context with extra headers
context2 = browser.new_context(extra_http_headers={"Authorization": "Bearer fake_token"})
page2 = context2.new_page()
page2.goto('https://www.example.com/admin')
# Check if redirected to login
print(f"URL: {page2.url}")
page2.close()
context2.close()
```

## CSP Bug Detection Pattern

When buttons with `onclick="fnName()"` do nothing:

1. Check console for CSP violation: `"Executing inline script violates the following Content Security Policy directive 'script-src-elem'"`
2. Verify function availability: `page.evaluate("typeof fnName")` returns `"undefined"`
3. Confirm script tag exists but is blocked by CSP
4. Check CSP header: `curl -sI URL | grep content-security-policy`
5. Look for `script-src-elem` directive that overrides `script-src`

## Pitfalls

- **Use Python, not Node.js**: The `playwright` npm module may not be installed. Python works.
- **`expect_response` timeout**: Don't use broad URL patterns. Use specific path matches or handle timeout gracefully.
- **`expect_navigation` for SPA**: Single-page apps may not trigger navigation events. Use `wait_for_timeout` or check state changes instead.
- **Rate limit testing**: Don't try to trigger rate limits via Playwright — too slow. Use curl for rate limit tests.
- **`page.on("console")` misses CSP errors**: CSP violations appear as `pageerror` events, not console messages. Listen to both.
