157 lines
4.6 KiB
Markdown
157 lines
4.6 KiB
Markdown
# 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.
|