---
name: "Playwright Browser Automation"
description: "Skill for Playwright Browser Automation — auto-generated from documentation"
version: "1.0.0"
author: "skynet"
category: "dev"
agents: ["claude-code", "codex", "gemini"]
tags: ["playwright", "dev", "auto-generated"]
---

# Playwright Browser Automation

---
name: Playwright Browser Automation
description: Use when you need to automate web browsers for testing, scraping, or web interaction. Ideal for end-to-end testing, UI automation, and data extraction from dynamic websites.
metadata:
  author: skynet
  version: 1.0.0
category: dev
---

# Playwright Browser Automation

## Installation & Setup

```bash
# Install Playwright
pip install playwright

# Install browser binaries
playwright install

# Install specific browsers only
playwright install chromium firefox webkit

# Install with dependencies (for CI/CD)
playwright install --with-deps
```

## Basic Browser Control

### Launch and Navigate
```python
from playwright.sync_api import sync_playwright

# Basic browser launch
with sync_playwright() as p:
    browser = p.chromium.launch(headless=False)  # Set True for headless
    page = browser.new_page()
    page.goto("https://example.com")
    browser.close()

# Multiple browser contexts
with sync_playwright() as p:
    browser = p.chromium.launch()
    context1 = browser.new_context(viewport={'width': 1920, 'height': 1080})
    context2 = browser.new_context(viewport={'width': 375, 'height': 667})
    page1 = context1.new_page()
    page2 = context2.new_page()
```

### Element Interaction
```python
# Click elements
page.click("button#submit")
page.click("text=Login")  # Click by text content
page.click("[data-testid=login-btn]")  # Click by test ID

# Fill forms
page.fill("input[name=username]", "myuser")
page.fill("input[type=password]", "mypass")
page.select_option("select#country", "US")

# Upload files
page.set_input_files("input[type=file]", "path/to/file.txt")
page.set_input_files("input[type=file]", ["file1.txt", "file2.txt"])  # Multiple files
```

## Wait Strategies

### Explicit Waits
```python
# Wait for element
page.wait_for_selector("div.loading", state="detached")  # Wait for element to disappear
page.wait_for_selector("button#submit", state="visible")  # Wait for visible

# Wait for page events
page.wait_for_load_state("networkidle")  # Wait for network to be idle
page.wait_for_load_state("domcontentloaded")

# Wait with timeout
try:
    page.wait_for_selector("div.result", timeout=10000)  # 10 seconds
except TimeoutError:
    print("Element not found within timeout")
```

### Smart Waiting
```python
# Wait for function result
page.wait_for_function("() => document.readyState === 'complete'")
page.wait_for_function("() => window.myApp && window.myApp.loaded")

# Wait for response
with page.expect_response("**/api/data") as response_info:
    page.click("button#load-data")
response = response_info.value
print(f"Status: {response.status}")
```

## Data Extraction

### Text and Attributes
```python
# Extract text
title = page.inner_text("h1")
all_links = page.query_selector_all("a")
link_texts = [link.inner_text() for link in all_links]

# Extract attributes
src = page.get_attribute("img#logo", "src")
href = page.get_attribute("a.download", "href")

# Extract multiple elements
products = page.query_selector_all(".product")
product_data = []
for product in products:
    name = product.query_selector(".name").inner_text()
    price = product.query_selector(".price").inner_text()
    product_data.append({"name": name, "price": price})
```

### Screenshots and PDFs
```python
# Full page screenshot
page.screenshot(path="full-page.png", full_page=True)

# Element screenshot
page.locator("div.chart").screenshot(path="chart.png")

# PDF generation
page.pdf(path="page.pdf", format="A4")
```

## Decision Trees

### Browser Selection
```python
def choose_browser(need_webkit=False, need_mobile=False, need_speed=False):
    with sync_playwright() as p:
        if need_webkit:
            browser = p.webkit.launch()  # For Safari testing
        elif need_mobile:
            browser = p.chromium.launch()
            context = browser.new_context(**p.devices["iPhone 13"])
        elif need_speed:
            browser = p.chromium.launch(headless=True)  # Fastest option
        else:
            browser = p.firefox.launch()  # Most compatible
        return browser
```

### Element Location Strategy
```python
def find_element(page, text=None, test_id=None, css_selector=None, xpath=None):
    if test_id:
        return page.locator(f"[data-testid='{test_id}']")  # Most reliable
    elif text:
        return page.locator(f"text={text}")  # Good for buttons/links
    elif css_selector:
        return page.locator(css_selector)  # Fast and specific
    elif xpath:
        return page.locator(f"xpath={xpath}")  # Last resort
    else:
        raise ValueError("Must provide at least one locator strategy")
```

## Testing Patterns

### Page Object Model
```python
class LoginPage:
    def __init__(self, page):
        self.page = page
        self.username_input = page.locator("input[name=username]")
        self.password_input = page.locator("input[name=password]")
        self.login_button = page.locator("button[type=submit]")

    def login(self, username, password):
        self.username_input.fill(username)
        self.password_input.fill(password)
        self.login_button.click()

# Usage
login_page = LoginPage(page)
login_page.login("testuser", "testpass")
```

### API Mocking
```python
# Mock API responses
page.route("**/api/users", lambda route: route.fulfill(
    status=200,
    content_type="application/json",
    body='[{"id": 1, "name": "Test User"}]'
))

# Block resources
page.route("**/*.{png,jpg,jpeg}", lambda route: route.abort())
```

## CLI Commands

```bash
# Run tests
playwright test

# Run in headed mode
playwright test --headed

# Run specific browser
playwright test --browser=firefox

# Generate code
playwright codegen https://example.com

# Debug mode
playwright test --debug

# Generate test report
playwright show-report

# Record video
playwright test --video=on

# Trace viewer
playwright test --trace=on
```

## Troubleshooting

### Common Errors and Fixes

**Error: `Target page, context or browser has been closed`**
```python
# Fix: Check browser/context lifecycle
try:
    page.click("button")
except Error as e:
    if "closed" in str(e):
        # Recreate browser context
        browser = p.chromium.launch()
        page = browser.new_page()
```

**Error: `Timeout 30000ms exceeded`**
```python
# Fix: Increase timeout or improve selector
page.click("button", timeout=60000)  # Increase timeout
page.wait_for_selector("button", state="visible")  # Ensure element is ready
page.click("button")
```

**Error: `Element is not attached to the DOM`**
```python
# Fix: Re-query element or use locator
# Bad
element = page.query_selector("button")
page.wait_for_timeout(1000)
element.click()  # May fail if DOM changed

# Good
page.locator("button").click()  # Always fresh query
```

**Error: `Element is outside of the viewport`**
```python
# Fix: Scroll into view
page.locator("button").scroll_into_view_if_needed()
page.locator("button").click()
```

### Performance Issues
```python
# Disable images and CSS for faster loading
context = browser.new_context()
context.route("**/*.{png,jpg,jpeg,css}", lambda route: route.abort())

# Use network idle for dynamic content
page.goto("https://spa-app.com", wait_until="networkidle")

# Parallel execution
import asyncio
from playwright.async_api import async_playwright

async def run_parallel():
    async with async_playwright() as p:
        browser = await p.chromium.launch()
        tasks = []
        for url in urls:
            tasks.append(scrape_page(browser, url))
        results = await asyncio.gather(*tasks)
```

### Debug Strategies
```python
# Visual debugging
page.pause()  # Opens debugger

# Console logging
page.on("console", lambda msg: print(f"Console: {msg.text}"))

# Network monitoring
page.on("response", lambda response: print(f"Response: {response.url} - {response.status}"))

# Slow motion for debugging
browser = p.chromium.launch(slow_mo=1000)  # 1 second delay between actions
```
