browser-automation
Use when the user asks to automate browser tasks, scrape websites, fill forms, capture screenshots, extract structured data from web pages, or build web automation workflows. NOT for testing — use playwright-pro for that.
Overview
Use when the user asks to automate browser tasks, scrape websites, fill forms, capture screenshots, extract structured data from web pages, or build web automation workflows. NOT for testing — use playwright-pro for that.
Source: https://github.com/alirezarezvani/claude-skills · MIT · © 2025 Alireza Rezvani
System prompt
The instruction block that defines the agent's role, rules and output format.
# Browser Automation - POWERFUL
## Overview
The Browser Automation skill provides comprehensive tools and knowledge for building production-grade web automation workflows using Playwright. This skill covers data extraction, form filling, screenshot capture, session management, and anti-detection patterns for reliable browser automation at scale.
**When to use this skill:**
- Scraping structured data from websites (tables, listings, search results)
- Automating multi-step browser workflows (login, fill forms, download files)
- Capturing screenshots or PDFs of web pages
- Extracting data from SPAs and JavaScript-heavy sites
- Building repeatable browser-based data pipelines
**When NOT to use this skill:**
- Writing browser tests or E2E test suites — use **playwright-pro** instead
- Testing API endpoints — use **api-test-suite-builder** instead
- Load testing or performance benchmarking — use **performance-profiler** instead
**Why Playwright over Selenium or Puppeteer:**
- **Auto-wait built in** — no explicit `sleep()` or `waitForElement()` needed for most actions
- **Multi-browser from one API** — Chromium, Firefox, WebKit with zero config changes
- **Network interception** — block ads, mock responses, capture API calls natively
- **Browser contexts** — isolated sessions without spinning up new browser instances
- **Codegen** — `playwright codegen` records your actions and generates scripts
- **Async-first** — Python async/await for high-throughput scraping
## Core Competencies
### 1. Web Scraping Patterns
**Selector priority (most to least reliable):**
1. `data-testid`, `data-id`, or custom data attributes — stable across redesigns
2. `#id` selectors — unique but may change between deploys
3. Semantic selectors: `article`, `nav`, `main`, `section` — resilient to CSS changes
4. Class-based: `.product-card`, `.price` — brittle if classes are generated (e.g., CSS modules)
5. Positional: `nth-child()`, `nth-of-type()` — last resort, breaks on layout changes
Use XPath only when CSS cannot express the relationship (e.g., ancestor traversal, text-based selection).
**Pagination strategies:** next-button, URL-based (`?page=N`), infinite scroll, load-more button. See [data_extraction_recipes.md](references/data_extraction_recipes.md) for complete pagination handlers and scroll patterns.
### 2. Form Filling & Multi-Step Workflows
Break multi-step forms into discrete functions per step. Each function fills fields, clicks "Next"/"Continue", and waits for the next step to load (URL change or DOM element).
Key patterns: login flows, multi-page forms, file uploads (including drag-and-drop zones), native and custom dropdown handling. See [playwright_browser_api.md](references/playwright_browser_api.md) for complete API reference on `fill()`, `select_option()`, `set_input_files()`, and `expect_file_chooser()`.
### 3. Screenshot & PDF Capture
- **Full page:** `await page.screenshot(path="full.png", full_page=True)`
- **Element:** `await page.locator("div.chart").screenshot(path="chart.png")`
- **PDF (Chromium only):** `await page.pdf(path="out.pdf", format="A4", print_background=True)`
- **Visual regression:** Take screenshots at known states, store baselines in version control with naming: `{page}_{viewport}_{state}.png`
See [playwright_browser_api.md](references/playwright_browser_api.md) for full screenshot/PDF options.
### 4. Structured Data Extraction
Core extraction patterns:
- **Tables to JSON** — Extract `<thead>` headers and `<tbody>` rows into dictionaries
- **Listings to arrays** — Map repeating card elements using a field-selector map (supports `::attr()` for attributes)
- **Nested/threaded data** — Recursive extraction for comments with replies, category trees
See [data_extraction_recipes.md](references/data_extraction_recipes.md) for complete extraction functions, price parsing, data cleaning utilities, and output format helpers (JSON, CSV, JSONL).
### 5. Cookie & Session Management
- **Save/restore cookies:** `context.cookies()` and `context.add_cookies()`
- **Full storage state** (cookies + localStorage): `context.storage_state(path="state.json")` to save, `browser.new_context(storage_state="state.json")` to restore
**Best practice:** Save state after login, reuse across scraping sessions. Check session validity before starting a long job — make a lightweight request to a protected page and verify you are not redirected to login. See [playwright_browser_api.md](references/playwright_browser_api.md) for cookie and storage state API details.
### 6. Anti-Detection Patterns
Modern websites detect automation through multiple vectors. Apply these in priority order:
1. **WebDriver flag removal** — Remove `navigator.webdriver = true` via init script (critical)
2. **Custom user agent** — Rotate through real browser UAs; never use the default headless UA
3. **Realistic viewport** — Set 1920x1080 or similar real-world dimensions (default 800x600 is a red flag)
4. **Request throttling** — Add `random.uniform()` delays between actions
5. **Proxy support** — Per-browser or per-context proxy configuration
See [anti_detection_patterns.md](references/anti_detection_patterns.md) for the complete stealth stack: navigator property hardening, WebGL/canvas fingerprint evasion, behavioral simulation (mouse movement, typing speed, scroll patterns), proxy rotation strategies, and detection self-test URLs.
### 7. Dynamic Content Handling
- **SPA rendering:** Wait for content selectors (`wait_for_selector`), not the page load event
- **AJAX/Fetch waiting:** Use `page.expect_response("**/api/data*")` to intercept and wait for specific API calls
- **Shadow DOM:** Playwright pierces open Shadow DOM with `>>` operator: `page.locator("custom-element >> .inner-class")`
- **Lazy-loaded images:** Scroll elements into view with `scroll_into_view_if_needed()` to trigger loading
See [playwright_browser_api.md](references/playwright_browser_api.md) for wait strategies, network interception, and Shadow DOM details.
### 8. Error Handling & Retry Logic
- **Retry with backoff:** Wrap page interactions in retry logic with exponential backoff (e.g., 1s, 2s, 4s)
- **Fallback selectors:** On `TimeoutError`, try alternative selectors before failing
- **Error-state screenshots:** Capture `page.screenshot(path="error-state.png")` on unexpected failures for debugging
- **Rate limit detection:** Check for HTTP 429 responses and respect `Retry-After` headers
See [anti_detection_patterns.md](references/anti_detection_patterns.md) for the complete exponential backoff implementation and rate limiter class.
## Workflows
### Workflow 1: Single-Page Data Extraction
**Scenario:** Extract product data from a single page with JavaScript-rendered content.
**Steps:**
1. Launch browser in headed mode during development (`headless=False`), switch to headless for production
2. Navigate to URL and wait for content selector
3. Extract data using `query_selector_all` with field mapping
4. Validate extracted data (check for nulls, expected types)
5. Output as JSON
```python
async def extract_single_page(url, selectors):
async with async_playwright() as p:
browser = await p.chromium.launch(headless=True)
context = await browser.new_context(
viewport={"width": 1920, "height": 1080},
user_agent="Mozilla/5.0 ..."
)
page = await context.new_page()
await page.goto(url, wait_until="networkidle")
data = await extract_listings(page, selectors["container"], selectors["fields"])
await browser.close()
return data
```
### Workflow 2: Multi-Page Scraping with Pagination
**Scenario:** Scrape search results across 50+ pages.
**Steps:**
1. Launch browser with anti-detection settings
2. Navigate to first page
3. Extract data from current page
4. Check if "Next" button exists and is enabled
5. Click next, wait for new content to load (not just navigation)
6. Repeat until no next page or max pages reached
7. Deduplicate results by unique key
8. Write output incrementally (don't hold everything in memory)
```python
async def scrape_paginated(base_url, selectors, max_pages=100):
all_data = []
async with async_playwright() as p:
browser = await p.chromium.launch(headless=True)
page = await (await browser.new_context()).new_page()
await page.goto(base_url)
for page_num in range(max_pages):
items = await extract_listings(page, selectors["container"], selectors["fields"])
all_data.extend(items)
next_btn = page.locator(selectors["next_button"])
if await next_btn.count() == 0 or await next_btn.is_disabled():
break
await next_btn.click()
await page.wait_for_selector(selectors["container"])
await human_delay(800, 2000)
await browser.close()
return all_data
```
### Workflow 3: Authenticated Workflow Automation
**Scenario:** Log into a portal, navigate a multi-step form, download a report.
**Steps:**
1. Check for existing session state file
2. If no session, perform login and save state
3. Navigate to target page using saved session
4. Fill multi-step form with provided data
5. Wait for download to trigger
6. Save downloaded file to target directory
```python
async def authenticated_workflow(credentials, form_data, download_dir):
async with async_playwright() as p:
browser = await p.chromium.launch(headless=True)
state_file = "session_state.json"
# Restore or create session
if os.path.exists(state_file):
context = await browser.new_context(storage_state=state_file)
else:
context = await browser.new_context()
page = await context.new_page()
await login(page, credentials["url"], credentials["user"], credentials["pass"])
await context.storage_state(path=state_file)
page = await context.new_page()
await page.goto(form_data["target_url"])
# Fill form steps
for step_fn in [fill_step_1, fill_step_2]:
await step_fn(page, form_data)
# Handle download
async with page.expect_download() as dl_info:
await page.click("button:has-text('Download Report')")
download = await dl_info.value
await download.save_as(os.path.join(download_dir, download.suggested_filename))
await browser.close()
```
## Tools Reference
| Script | Purpose | Key Flags | Output |
|--------|---------|-----------|--------|
| `scraping_toolkit.py` | Generate Playwright scraping script skeleton | `--url`, `--selectors`, `--paginate`, `--output` | Python script or JSON config |
| `form_automation_builder.py` | Generate form-fill automation script from field spec | `--fields`, `--url`, `--output` | Python automation script |
| `anti_detection_checker.py` | Audit a Playwright script for detection vectors | `--file`, `--verbose` | Risk report with score |
All scripts are stdlib-only. Run `python3 <script> --help` for full usage.
## Anti-Patterns
### Hardcoded Waits
**Bad:** `await page.wait_for_timeout(5000)` before every action.
**Good:** Use `wait_for_selector`, `wait_for_url`, `expect_response`, or `wait_for_load_state`. Hardcoded waits are flaky and slow.
### No Error Recovery
**Bad:** Linear script that crashes on first failure.
**Good:** Wrap each page interaction in try/except. Take error-state screenshots. Implement retry with exponential backoff.
### Ignoring robots.txt
**Bad:** Scraping without checking robots.txt directives.
**Good:** Fetch and parse robots.txt before scraping. Respect `Crawl-delay`. Skip disallowed paths. Add your bot name to User-Agent if running at scale.
### Storing Credentials in Scripts
**Bad:** Hardcoding usernames and passwords in Python files.
**Good:** Use environment variables, `.env` files (gitignored), or a secrets manager. Pass credentials via CLI arguments.
### No Rate Limiting
**Bad:** Hammering a site with 100 requests/second.
**Good:** Add random delays between requests (1-3s for polite scraping). Monitor for 429 responses. Implement exponential backoff.
### Selector Fragility
**Bad:** Relying on auto-generated class names (`.css-1a2b3c`) or deep nesting (`div > div > div > span:nth-child(3)`).
**Good:** Use data attributes, semantic HTML, or text-based locators. Test selectors in browser DevTools first.
### Not Cleaning Up Browser Instances
**Bad:** Launching browsers without closing them, leading to resource leaks.
**Good:** Always use `try/finally` or async context managers to ensure `browser.close()` is called.
### Running Headed in Production
**Bad:** Using `headless=False` in production/CI.
**Good:** Develop with headed mode for debugging, deploy with `headless=True`. Use environment variable to toggle: `headless = os.environ.get("HEADLESS", "true") == "true"`.
## Cross-References
- **playwright-pro** — Browser testing skill. Use for E2E tests, test assertions, test fixtures. Browser Automation is for data extraction and workflow automation, not testing.
- **api-test-suite-builder** — When the website has a public API, hit the API directly instead of scraping the rendered page. Faster, more reliable, less detectable.
- **performance-profiler** — If your automation scripts are slow, profile the bottlenecks before adding concurrency.
- **env-secrets-manager** — For securely managing credentials used in authenticated automation workflows. User prompt template
Fill in the variables and send it to run the skill: {{variables}}
Variables
| Name | Required | Description | Example |
|---|
Full skill configuration
The standard .aiskill file in JSON and YAML.
{
"format": "aiskill/v1",
"skill": {
"name": "browser-automation",
"slug": "browser-automation",
"version": "1.0.0",
"description": "Use when the user asks to automate browser tasks, scrape websites, fill forms, capture screenshots, extract structured data from web pages, or build web automation workflows. NOT for testing — use playwright-pro for that.",
"category": "Development",
"tags": [
"engineering",
"testing",
"automation"
],
"platforms": [
"chatgpt",
"claude-code",
"cursor"
],
"tested_environment": "Agent Skills convention (SKILL.md) — works with Claude Code, Cursor, ChatGPT/Codex and compatible agents",
"license": "MIT"
},
"prompts": {
"system": "# Browser Automation - POWERFUL\r\n\r\n## Overview\r\n\r\nThe Browser Automation skill provides comprehensive tools and knowledge for building production-grade web automation workflows using Playwright. This skill covers data extraction, form filling, screenshot capture, session management, and anti-detection patterns for reliable browser automation at scale.\r\n\r\n**When to use this skill:**\r\n- Scraping structured data from websites (tables, listings, search results)\r\n- Automating multi-step browser workflows (login, fill forms, download files)\r\n- Capturing screenshots or PDFs of web pages\r\n- Extracting data from SPAs and JavaScript-heavy sites\r\n- Building repeatable browser-based data pipelines\r\n\r\n**When NOT to use this skill:**\r\n- Writing browser tests or E2E test suites — use **playwright-pro** instead\r\n- Testing API endpoints — use **api-test-suite-builder** instead\r\n- Load testing or performance benchmarking — use **performance-profiler** instead\r\n\r\n**Why Playwright over Selenium or Puppeteer:**\r\n- **Auto-wait built in** — no explicit `sleep()` or `waitForElement()` needed for most actions\r\n- **Multi-browser from one API** — Chromium, Firefox, WebKit with zero config changes\r\n- **Network interception** — block ads, mock responses, capture API calls natively\r\n- **Browser contexts** — isolated sessions without spinning up new browser instances\r\n- **Codegen** — `playwright codegen` records your actions and generates scripts\r\n- **Async-first** — Python async/await for high-throughput scraping\r\n\r\n## Core Competencies\r\n\r\n### 1. Web Scraping Patterns\r\n\r\n**Selector priority (most to least reliable):**\r\n1. `data-testid`, `data-id`, or custom data attributes — stable across redesigns\r\n2. `#id` selectors — unique but may change between deploys\r\n3. Semantic selectors: `article`, `nav`, `main`, `section` — resilient to CSS changes\r\n4. Class-based: `.product-card`, `.price` — brittle if classes are generated (e.g., CSS modules)\r\n5. Positional: `nth-child()`, `nth-of-type()` — last resort, breaks on layout changes\r\n\r\nUse XPath only when CSS cannot express the relationship (e.g., ancestor traversal, text-based selection).\r\n\r\n**Pagination strategies:** next-button, URL-based (`?page=N`), infinite scroll, load-more button. See [data_extraction_recipes.md](references/data_extraction_recipes.md) for complete pagination handlers and scroll patterns.\r\n\r\n### 2. Form Filling & Multi-Step Workflows\r\n\r\nBreak multi-step forms into discrete functions per step. Each function fills fields, clicks \"Next\"/\"Continue\", and waits for the next step to load (URL change or DOM element).\r\n\r\nKey patterns: login flows, multi-page forms, file uploads (including drag-and-drop zones), native and custom dropdown handling. See [playwright_browser_api.md](references/playwright_browser_api.md) for complete API reference on `fill()`, `select_option()`, `set_input_files()`, and `expect_file_chooser()`.\r\n\r\n### 3. Screenshot & PDF Capture\r\n\r\n- **Full page:** `await page.screenshot(path=\"full.png\", full_page=True)`\r\n- **Element:** `await page.locator(\"div.chart\").screenshot(path=\"chart.png\")`\r\n- **PDF (Chromium only):** `await page.pdf(path=\"out.pdf\", format=\"A4\", print_background=True)`\r\n- **Visual regression:** Take screenshots at known states, store baselines in version control with naming: `{page}_{viewport}_{state}.png`\r\n\r\nSee [playwright_browser_api.md](references/playwright_browser_api.md) for full screenshot/PDF options.\r\n\r\n### 4. Structured Data Extraction\r\n\r\nCore extraction patterns:\r\n- **Tables to JSON** — Extract `<thead>` headers and `<tbody>` rows into dictionaries\r\n- **Listings to arrays** — Map repeating card elements using a field-selector map (supports `::attr()` for attributes)\r\n- **Nested/threaded data** — Recursive extraction for comments with replies, category trees\r\n\r\nSee [data_extraction_recipes.md](references/data_extraction_recipes.md) for complete extraction functions, price parsing, data cleaning utilities, and output format helpers (JSON, CSV, JSONL).\r\n\r\n### 5. Cookie & Session Management\r\n\r\n- **Save/restore cookies:** `context.cookies()` and `context.add_cookies()`\r\n- **Full storage state** (cookies + localStorage): `context.storage_state(path=\"state.json\")` to save, `browser.new_context(storage_state=\"state.json\")` to restore\r\n\r\n**Best practice:** Save state after login, reuse across scraping sessions. Check session validity before starting a long job — make a lightweight request to a protected page and verify you are not redirected to login. See [playwright_browser_api.md](references/playwright_browser_api.md) for cookie and storage state API details.\r\n\r\n### 6. Anti-Detection Patterns\r\n\r\nModern websites detect automation through multiple vectors. Apply these in priority order:\r\n\r\n1. **WebDriver flag removal** — Remove `navigator.webdriver = true` via init script (critical)\r\n2. **Custom user agent** — Rotate through real browser UAs; never use the default headless UA\r\n3. **Realistic viewport** — Set 1920x1080 or similar real-world dimensions (default 800x600 is a red flag)\r\n4. **Request throttling** — Add `random.uniform()` delays between actions\r\n5. **Proxy support** — Per-browser or per-context proxy configuration\r\n\r\nSee [anti_detection_patterns.md](references/anti_detection_patterns.md) for the complete stealth stack: navigator property hardening, WebGL/canvas fingerprint evasion, behavioral simulation (mouse movement, typing speed, scroll patterns), proxy rotation strategies, and detection self-test URLs.\r\n\r\n### 7. Dynamic Content Handling\r\n\r\n- **SPA rendering:** Wait for content selectors (`wait_for_selector`), not the page load event\r\n- **AJAX/Fetch waiting:** Use `page.expect_response(\"**/api/data*\")` to intercept and wait for specific API calls\r\n- **Shadow DOM:** Playwright pierces open Shadow DOM with `>>` operator: `page.locator(\"custom-element >> .inner-class\")`\r\n- **Lazy-loaded images:** Scroll elements into view with `scroll_into_view_if_needed()` to trigger loading\r\n\r\nSee [playwright_browser_api.md](references/playwright_browser_api.md) for wait strategies, network interception, and Shadow DOM details.\r\n\r\n### 8. Error Handling & Retry Logic\r\n\r\n- **Retry with backoff:** Wrap page interactions in retry logic with exponential backoff (e.g., 1s, 2s, 4s)\r\n- **Fallback selectors:** On `TimeoutError`, try alternative selectors before failing\r\n- **Error-state screenshots:** Capture `page.screenshot(path=\"error-state.png\")` on unexpected failures for debugging\r\n- **Rate limit detection:** Check for HTTP 429 responses and respect `Retry-After` headers\r\n\r\nSee [anti_detection_patterns.md](references/anti_detection_patterns.md) for the complete exponential backoff implementation and rate limiter class.\r\n\r\n## Workflows\r\n\r\n### Workflow 1: Single-Page Data Extraction\r\n\r\n**Scenario:** Extract product data from a single page with JavaScript-rendered content.\r\n\r\n**Steps:**\r\n1. Launch browser in headed mode during development (`headless=False`), switch to headless for production\r\n2. Navigate to URL and wait for content selector\r\n3. Extract data using `query_selector_all` with field mapping\r\n4. Validate extracted data (check for nulls, expected types)\r\n5. Output as JSON\r\n\r\n```python\r\nasync def extract_single_page(url, selectors):\r\n async with async_playwright() as p:\r\n browser = await p.chromium.launch(headless=True)\r\n context = await browser.new_context(\r\n viewport={\"width\": 1920, \"height\": 1080},\r\n user_agent=\"Mozilla/5.0 ...\"\r\n )\r\n page = await context.new_page()\r\n await page.goto(url, wait_until=\"networkidle\")\r\n data = await extract_listings(page, selectors[\"container\"], selectors[\"fields\"])\r\n await browser.close()\r\n return data\r\n```\r\n\r\n### Workflow 2: Multi-Page Scraping with Pagination\r\n\r\n**Scenario:** Scrape search results across 50+ pages.\r\n\r\n**Steps:**\r\n1. Launch browser with anti-detection settings\r\n2. Navigate to first page\r\n3. Extract data from current page\r\n4. Check if \"Next\" button exists and is enabled\r\n5. Click next, wait for new content to load (not just navigation)\r\n6. Repeat until no next page or max pages reached\r\n7. Deduplicate results by unique key\r\n8. Write output incrementally (don't hold everything in memory)\r\n\r\n```python\r\nasync def scrape_paginated(base_url, selectors, max_pages=100):\r\n all_data = []\r\n async with async_playwright() as p:\r\n browser = await p.chromium.launch(headless=True)\r\n page = await (await browser.new_context()).new_page()\r\n await page.goto(base_url)\r\n\r\n for page_num in range(max_pages):\r\n items = await extract_listings(page, selectors[\"container\"], selectors[\"fields\"])\r\n all_data.extend(items)\r\n\r\n next_btn = page.locator(selectors[\"next_button\"])\r\n if await next_btn.count() == 0 or await next_btn.is_disabled():\r\n break\r\n\r\n await next_btn.click()\r\n await page.wait_for_selector(selectors[\"container\"])\r\n await human_delay(800, 2000)\r\n\r\n await browser.close()\r\n return all_data\r\n```\r\n\r\n### Workflow 3: Authenticated Workflow Automation\r\n\r\n**Scenario:** Log into a portal, navigate a multi-step form, download a report.\r\n\r\n**Steps:**\r\n1. Check for existing session state file\r\n2. If no session, perform login and save state\r\n3. Navigate to target page using saved session\r\n4. Fill multi-step form with provided data\r\n5. Wait for download to trigger\r\n6. Save downloaded file to target directory\r\n\r\n```python\r\nasync def authenticated_workflow(credentials, form_data, download_dir):\r\n async with async_playwright() as p:\r\n browser = await p.chromium.launch(headless=True)\r\n state_file = \"session_state.json\"\r\n\r\n # Restore or create session\r\n if os.path.exists(state_file):\r\n context = await browser.new_context(storage_state=state_file)\r\n else:\r\n context = await browser.new_context()\r\n page = await context.new_page()\r\n await login(page, credentials[\"url\"], credentials[\"user\"], credentials[\"pass\"])\r\n await context.storage_state(path=state_file)\r\n\r\n page = await context.new_page()\r\n await page.goto(form_data[\"target_url\"])\r\n\r\n # Fill form steps\r\n for step_fn in [fill_step_1, fill_step_2]:\r\n await step_fn(page, form_data)\r\n\r\n # Handle download\r\n async with page.expect_download() as dl_info:\r\n await page.click(\"button:has-text('Download Report')\")\r\n download = await dl_info.value\r\n await download.save_as(os.path.join(download_dir, download.suggested_filename))\r\n\r\n await browser.close()\r\n```\r\n\r\n## Tools Reference\r\n\r\n| Script | Purpose | Key Flags | Output |\r\n|--------|---------|-----------|--------|\r\n| `scraping_toolkit.py` | Generate Playwright scraping script skeleton | `--url`, `--selectors`, `--paginate`, `--output` | Python script or JSON config |\r\n| `form_automation_builder.py` | Generate form-fill automation script from field spec | `--fields`, `--url`, `--output` | Python automation script |\r\n| `anti_detection_checker.py` | Audit a Playwright script for detection vectors | `--file`, `--verbose` | Risk report with score |\r\n\r\nAll scripts are stdlib-only. Run `python3 <script> --help` for full usage.\r\n\r\n## Anti-Patterns\r\n\r\n### Hardcoded Waits\r\n**Bad:** `await page.wait_for_timeout(5000)` before every action.\r\n**Good:** Use `wait_for_selector`, `wait_for_url`, `expect_response`, or `wait_for_load_state`. Hardcoded waits are flaky and slow.\r\n\r\n### No Error Recovery\r\n**Bad:** Linear script that crashes on first failure.\r\n**Good:** Wrap each page interaction in try/except. Take error-state screenshots. Implement retry with exponential backoff.\r\n\r\n### Ignoring robots.txt\r\n**Bad:** Scraping without checking robots.txt directives.\r\n**Good:** Fetch and parse robots.txt before scraping. Respect `Crawl-delay`. Skip disallowed paths. Add your bot name to User-Agent if running at scale.\r\n\r\n### Storing Credentials in Scripts\r\n**Bad:** Hardcoding usernames and passwords in Python files.\r\n**Good:** Use environment variables, `.env` files (gitignored), or a secrets manager. Pass credentials via CLI arguments.\r\n\r\n### No Rate Limiting\r\n**Bad:** Hammering a site with 100 requests/second.\r\n**Good:** Add random delays between requests (1-3s for polite scraping). Monitor for 429 responses. Implement exponential backoff.\r\n\r\n### Selector Fragility\r\n**Bad:** Relying on auto-generated class names (`.css-1a2b3c`) or deep nesting (`div > div > div > span:nth-child(3)`).\r\n**Good:** Use data attributes, semantic HTML, or text-based locators. Test selectors in browser DevTools first.\r\n\r\n### Not Cleaning Up Browser Instances\r\n**Bad:** Launching browsers without closing them, leading to resource leaks.\r\n**Good:** Always use `try/finally` or async context managers to ensure `browser.close()` is called.\r\n\r\n### Running Headed in Production\r\n**Bad:** Using `headless=False` in production/CI.\r\n**Good:** Develop with headed mode for debugging, deploy with `headless=True`. Use environment variable to toggle: `headless = os.environ.get(\"HEADLESS\", \"true\") == \"true\"`.\r\n\r\n## Cross-References\r\n\r\n- **playwright-pro** — Browser testing skill. Use for E2E tests, test assertions, test fixtures. Browser Automation is for data extraction and workflow automation, not testing.\r\n- **api-test-suite-builder** — When the website has a public API, hit the API directly instead of scraping the rendered page. Faster, more reliable, less detectable.\r\n- **performance-profiler** — If your automation scripts are slow, profile the bottlenecks before adding concurrency.\r\n- **env-secrets-manager** — For securely managing credentials used in authenticated automation workflows.",
"user_template": ""
},
"variables": [],
"install": {
"steps": [
{
"title": "Download & unzip",
"detail": "Download the .zip package from SkillVault and unzip it. The browser-automation/ folder contains SKILL.md plus any companion scripts, references and assets."
},
{
"title": "Copy into your agent skills directory",
"detail": "Move the folder into your agent's skills directory — e.g. ~/.claude/skills/ (Claude Code), .codex/skills/, .cursor/skills/ or your project's .claude/skills/ folder — then restart or reload the session."
},
{
"title": "Use it",
"detail": "Describe the task in natural language; the agent loads the skill automatically when the request matches the skill description."
}
]
},
"usage": {
"steps": []
},
"metadata": {
"author": "SkillVault",
"source": "https://skillvault.top/skills/browser-automation",
"exported_at": "2026-11-03T07:42:30.246Z"
}
} format: aiskill/v1
skill:
name: browser-automation
slug: browser-automation
version: 1.0.0
description: >-
Use when the user asks to automate browser tasks, scrape websites, fill forms, capture
screenshots, extract structured data from web pages, or build web automation workflows. NOT for
testing — use playwright-pro for that.
category: Development
tags:
- engineering
- testing
- automation
platforms:
- chatgpt
- claude-code
- cursor
tested_environment: >-
Agent Skills convention (SKILL.md) — works with Claude Code, Cursor, ChatGPT/Codex and
compatible agents
license: MIT
prompts:
system: "# Browser Automation - POWERFUL\r\n\r\n## Overview\r\n\r\nThe Browser Automation skill provides comprehensive tools and knowledge for building production-grade web automation workflows using Playwright. This skill covers data extraction, form filling, screenshot capture, session management, and anti-detection patterns for reliable browser automation at scale.\r\n\r\n**When to use this skill:**\r\n- Scraping structured data from websites (tables, listings, search results)\r\n- Automating multi-step browser workflows (login, fill forms, download files)\r\n- Capturing screenshots or PDFs of web pages\r\n- Extracting data from SPAs and JavaScript-heavy sites\r\n- Building repeatable browser-based data pipelines\r\n\r\n**When NOT to use this skill:**\r\n- Writing browser tests or E2E test suites — use **playwright-pro** instead\r\n- Testing API endpoints — use **api-test-suite-builder** instead\r\n- Load testing or performance benchmarking — use **performance-profiler** instead\r\n\r\n**Why Playwright over Selenium or Puppeteer:**\r\n- **Auto-wait built in** — no explicit `sleep()` or `waitForElement()` needed for most actions\r\n- **Multi-browser from one API** — Chromium, Firefox, WebKit with zero config changes\r\n- **Network interception** — block ads, mock responses, capture API calls natively\r\n- **Browser contexts** — isolated sessions without spinning up new browser instances\r\n- **Codegen** — `playwright codegen` records your actions and generates scripts\r\n- **Async-first** — Python async/await for high-throughput scraping\r\n\r\n## Core Competencies\r\n\r\n### 1. Web Scraping Patterns\r\n\r\n**Selector priority (most to least reliable):**\r\n1. `data-testid`, `data-id`, or custom data attributes — stable across redesigns\r\n2. `#id` selectors — unique but may change between deploys\r\n3. Semantic selectors: `article`, `nav`, `main`, `section` — resilient to CSS changes\r\n4. Class-based: `.product-card`, `.price` — brittle if classes are generated (e.g., CSS modules)\r\n5. Positional: `nth-child()`, `nth-of-type()` — last resort, breaks on layout changes\r\n\r\nUse XPath only when CSS cannot express the relationship (e.g., ancestor traversal, text-based selection).\r\n\r\n**Pagination strategies:** next-button, URL-based (`?page=N`), infinite scroll, load-more button. See [data_extraction_recipes.md](references/data_extraction_recipes.md) for complete pagination handlers and scroll patterns.\r\n\r\n### 2. Form Filling & Multi-Step Workflows\r\n\r\nBreak multi-step forms into discrete functions per step. Each function fills fields, clicks \"Next\"/\"Continue\", and waits for the next step to load (URL change or DOM element).\r\n\r\nKey patterns: login flows, multi-page forms, file uploads (including drag-and-drop zones), native and custom dropdown handling. See [playwright_browser_api.md](references/playwright_browser_api.md) for complete API reference on `fill()`, `select_option()`, `set_input_files()`, and `expect_file_chooser()`.\r\n\r\n### 3. Screenshot & PDF Capture\r\n\r\n- **Full page:** `await page.screenshot(path=\"full.png\", full_page=True)`\r\n- **Element:** `await page.locator(\"div.chart\").screenshot(path=\"chart.png\")`\r\n- **PDF (Chromium only):** `await page.pdf(path=\"out.pdf\", format=\"A4\", print_background=True)`\r\n- **Visual regression:** Take screenshots at known states, store baselines in version control with naming: `{page}_{viewport}_{state}.png`\r\n\r\nSee [playwright_browser_api.md](references/playwright_browser_api.md) for full screenshot/PDF options.\r\n\r\n### 4. Structured Data Extraction\r\n\r\nCore extraction patterns:\r\n- **Tables to JSON** — Extract `<thead>` headers and `<tbody>` rows into dictionaries\r\n- **Listings to arrays** — Map repeating card elements using a field-selector map (supports `::attr()` for attributes)\r\n- **Nested/threaded data** — Recursive extraction for comments with replies, category trees\r\n\r\nSee [data_extraction_recipes.md](references/data_extraction_recipes.md) for complete extraction functions, price parsing, data cleaning utilities, and output format helpers (JSON, CSV, JSONL).\r\n\r\n### 5. Cookie & Session Management\r\n\r\n- **Save/restore cookies:** `context.cookies()` and `context.add_cookies()`\r\n- **Full storage state** (cookies + localStorage): `context.storage_state(path=\"state.json\")` to save, `browser.new_context(storage_state=\"state.json\")` to restore\r\n\r\n**Best practice:** Save state after login, reuse across scraping sessions. Check session validity before starting a long job — make a lightweight request to a protected page and verify you are not redirected to login. See [playwright_browser_api.md](references/playwright_browser_api.md) for cookie and storage state API details.\r\n\r\n### 6. Anti-Detection Patterns\r\n\r\nModern websites detect automation through multiple vectors. Apply these in priority order:\r\n\r\n1. **WebDriver flag removal** — Remove `navigator.webdriver = true` via init script (critical)\r\n2. **Custom user agent** — Rotate through real browser UAs; never use the default headless UA\r\n3. **Realistic viewport** — Set 1920x1080 or similar real-world dimensions (default 800x600 is a red flag)\r\n4. **Request throttling** — Add `random.uniform()` delays between actions\r\n5. **Proxy support** — Per-browser or per-context proxy configuration\r\n\r\nSee [anti_detection_patterns.md](references/anti_detection_patterns.md) for the complete stealth stack: navigator property hardening, WebGL/canvas fingerprint evasion, behavioral simulation (mouse movement, typing speed, scroll patterns), proxy rotation strategies, and detection self-test URLs.\r\n\r\n### 7. Dynamic Content Handling\r\n\r\n- **SPA rendering:** Wait for content selectors (`wait_for_selector`), not the page load event\r\n- **AJAX/Fetch waiting:** Use `page.expect_response(\"**/api/data*\")` to intercept and wait for specific API calls\r\n- **Shadow DOM:** Playwright pierces open Shadow DOM with `>>` operator: `page.locator(\"custom-element >> .inner-class\")`\r\n- **Lazy-loaded images:** Scroll elements into view with `scroll_into_view_if_needed()` to trigger loading\r\n\r\nSee [playwright_browser_api.md](references/playwright_browser_api.md) for wait strategies, network interception, and Shadow DOM details.\r\n\r\n### 8. Error Handling & Retry Logic\r\n\r\n- **Retry with backoff:** Wrap page interactions in retry logic with exponential backoff (e.g., 1s, 2s, 4s)\r\n- **Fallback selectors:** On `TimeoutError`, try alternative selectors before failing\r\n- **Error-state screenshots:** Capture `page.screenshot(path=\"error-state.png\")` on unexpected failures for debugging\r\n- **Rate limit detection:** Check for HTTP 429 responses and respect `Retry-After` headers\r\n\r\nSee [anti_detection_patterns.md](references/anti_detection_patterns.md) for the complete exponential backoff implementation and rate limiter class.\r\n\r\n## Workflows\r\n\r\n### Workflow 1: Single-Page Data Extraction\r\n\r\n**Scenario:** Extract product data from a single page with JavaScript-rendered content.\r\n\r\n**Steps:**\r\n1. Launch browser in headed mode during development (`headless=False`), switch to headless for production\r\n2. Navigate to URL and wait for content selector\r\n3. Extract data using `query_selector_all` with field mapping\r\n4. Validate extracted data (check for nulls, expected types)\r\n5. Output as JSON\r\n\r\n```python\r\nasync def extract_single_page(url, selectors):\r\n async with async_playwright() as p:\r\n browser = await p.chromium.launch(headless=True)\r\n context = await browser.new_context(\r\n viewport={\"width\": 1920, \"height\": 1080},\r\n user_agent=\"Mozilla/5.0 ...\"\r\n )\r\n page = await context.new_page()\r\n await page.goto(url, wait_until=\"networkidle\")\r\n data = await extract_listings(page, selectors[\"container\"], selectors[\"fields\"])\r\n await browser.close()\r\n return data\r\n```\r\n\r\n### Workflow 2: Multi-Page Scraping with Pagination\r\n\r\n**Scenario:** Scrape search results across 50+ pages.\r\n\r\n**Steps:**\r\n1. Launch browser with anti-detection settings\r\n2. Navigate to first page\r\n3. Extract data from current page\r\n4. Check if \"Next\" button exists and is enabled\r\n5. Click next, wait for new content to load (not just navigation)\r\n6. Repeat until no next page or max pages reached\r\n7. Deduplicate results by unique key\r\n8. Write output incrementally (don't hold everything in memory)\r\n\r\n```python\r\nasync def scrape_paginated(base_url, selectors, max_pages=100):\r\n all_data = []\r\n async with async_playwright() as p:\r\n browser = await p.chromium.launch(headless=True)\r\n page = await (await browser.new_context()).new_page()\r\n await page.goto(base_url)\r\n\r\n for page_num in range(max_pages):\r\n items = await extract_listings(page, selectors[\"container\"], selectors[\"fields\"])\r\n all_data.extend(items)\r\n\r\n next_btn = page.locator(selectors[\"next_button\"])\r\n if await next_btn.count() == 0 or await next_btn.is_disabled():\r\n break\r\n\r\n await next_btn.click()\r\n await page.wait_for_selector(selectors[\"container\"])\r\n await human_delay(800, 2000)\r\n\r\n await browser.close()\r\n return all_data\r\n```\r\n\r\n### Workflow 3: Authenticated Workflow Automation\r\n\r\n**Scenario:** Log into a portal, navigate a multi-step form, download a report.\r\n\r\n**Steps:**\r\n1. Check for existing session state file\r\n2. If no session, perform login and save state\r\n3. Navigate to target page using saved session\r\n4. Fill multi-step form with provided data\r\n5. Wait for download to trigger\r\n6. Save downloaded file to target directory\r\n\r\n```python\r\nasync def authenticated_workflow(credentials, form_data, download_dir):\r\n async with async_playwright() as p:\r\n browser = await p.chromium.launch(headless=True)\r\n state_file = \"session_state.json\"\r\n\r\n # Restore or create session\r\n if os.path.exists(state_file):\r\n context = await browser.new_context(storage_state=state_file)\r\n else:\r\n context = await browser.new_context()\r\n page = await context.new_page()\r\n await login(page, credentials[\"url\"], credentials[\"user\"], credentials[\"pass\"])\r\n await context.storage_state(path=state_file)\r\n\r\n page = await context.new_page()\r\n await page.goto(form_data[\"target_url\"])\r\n\r\n # Fill form steps\r\n for step_fn in [fill_step_1, fill_step_2]:\r\n await step_fn(page, form_data)\r\n\r\n # Handle download\r\n async with page.expect_download() as dl_info:\r\n await page.click(\"button:has-text('Download Report')\")\r\n download = await dl_info.value\r\n await download.save_as(os.path.join(download_dir, download.suggested_filename))\r\n\r\n await browser.close()\r\n```\r\n\r\n## Tools Reference\r\n\r\n| Script | Purpose | Key Flags | Output |\r\n|--------|---------|-----------|--------|\r\n| `scraping_toolkit.py` | Generate Playwright scraping script skeleton | `--url`, `--selectors`, `--paginate`, `--output` | Python script or JSON config |\r\n| `form_automation_builder.py` | Generate form-fill automation script from field spec | `--fields`, `--url`, `--output` | Python automation script |\r\n| `anti_detection_checker.py` | Audit a Playwright script for detection vectors | `--file`, `--verbose` | Risk report with score |\r\n\r\nAll scripts are stdlib-only. Run `python3 <script> --help` for full usage.\r\n\r\n## Anti-Patterns\r\n\r\n### Hardcoded Waits\r\n**Bad:** `await page.wait_for_timeout(5000)` before every action.\r\n**Good:** Use `wait_for_selector`, `wait_for_url`, `expect_response`, or `wait_for_load_state`. Hardcoded waits are flaky and slow.\r\n\r\n### No Error Recovery\r\n**Bad:** Linear script that crashes on first failure.\r\n**Good:** Wrap each page interaction in try/except. Take error-state screenshots. Implement retry with exponential backoff.\r\n\r\n### Ignoring robots.txt\r\n**Bad:** Scraping without checking robots.txt directives.\r\n**Good:** Fetch and parse robots.txt before scraping. Respect `Crawl-delay`. Skip disallowed paths. Add your bot name to User-Agent if running at scale.\r\n\r\n### Storing Credentials in Scripts\r\n**Bad:** Hardcoding usernames and passwords in Python files.\r\n**Good:** Use environment variables, `.env` files (gitignored), or a secrets manager. Pass credentials via CLI arguments.\r\n\r\n### No Rate Limiting\r\n**Bad:** Hammering a site with 100 requests/second.\r\n**Good:** Add random delays between requests (1-3s for polite scraping). Monitor for 429 responses. Implement exponential backoff.\r\n\r\n### Selector Fragility\r\n**Bad:** Relying on auto-generated class names (`.css-1a2b3c`) or deep nesting (`div > div > div > span:nth-child(3)`).\r\n**Good:** Use data attributes, semantic HTML, or text-based locators. Test selectors in browser DevTools first.\r\n\r\n### Not Cleaning Up Browser Instances\r\n**Bad:** Launching browsers without closing them, leading to resource leaks.\r\n**Good:** Always use `try/finally` or async context managers to ensure `browser.close()` is called.\r\n\r\n### Running Headed in Production\r\n**Bad:** Using `headless=False` in production/CI.\r\n**Good:** Develop with headed mode for debugging, deploy with `headless=True`. Use environment variable to toggle: `headless = os.environ.get(\"HEADLESS\", \"true\") == \"true\"`.\r\n\r\n## Cross-References\r\n\r\n- **playwright-pro** — Browser testing skill. Use for E2E tests, test assertions, test fixtures. Browser Automation is for data extraction and workflow automation, not testing.\r\n- **api-test-suite-builder** — When the website has a public API, hit the API directly instead of scraping the rendered page. Faster, more reliable, less detectable.\r\n- **performance-profiler** — If your automation scripts are slow, profile the bottlenecks before adding concurrency.\r\n- **env-secrets-manager** — For securely managing credentials used in authenticated automation workflows."
user_template: ""
variables: []
install:
steps:
- title: Download & unzip
detail: >-
Download the .zip package from SkillVault and unzip it. The browser-automation/ folder
contains SKILL.md plus any companion scripts, references and assets.
- title: Copy into your agent skills directory
detail: >-
Move the folder into your agent's skills directory — e.g. ~/.claude/skills/ (Claude Code),
.codex/skills/, .cursor/skills/ or your project's .claude/skills/ folder — then restart or
reload the session.
- title: Use it
detail: >-
Describe the task in natural language; the agent loads the skill automatically when the
request matches the skill description.
usage:
steps: []
metadata:
author: SkillVault
source: https://skillvault.top/skills/browser-automation
exported_at: "2026-11-03T07:42:30.246Z"
Installation guide
- 1
Download & unzip
Download the .zip package from SkillVault and unzip it. The browser-automation/ folder contains SKILL.md plus any companion scripts, references and assets.
- 2
Copy into your agent skills directory
Move the folder into your agent's skills directory — e.g. ~/.claude/skills/ (Claude Code), .codex/skills/, .cursor/skills/ or your project's .claude/skills/ folder — then restart or reload the session.
- 3
Use it
Describe the task in natural language; the agent loads the skill automatically when the request matches the skill description.
How to use
Platform compatibility
ChatGPT GPTs
OpenAI
Paste the system prompt into the GPT "Instructions" field and keep the user template as your starter message.
Official docsClaude Code
Anthropic
Save the exported .aiskill JSON as a skill file in your project or paste the system prompt into CLAUDE.md / a slash-command markdown file.
Official docsCursor
Anysphere
Copy the system prompt into .cursor/rules/*.mdc (or Project Rules in Settings) and reference the user template in your agent prompts.
Official docsRelated skills
helm-chart-builder
Helm chart development agent skill and plugin for Claude Code, Codex, Gemini CLI, Cursor, OpenClaw — chart scaffolding, values design, template patterns, dependency management, security hardening, and chart testing. Use when: user wants to create or improve Helm charts, design values.yaml files, implement template helpers, audit chart security (RBAC, network policies, pod security), manage subcharts, or run helm lint/test.
senior-devops
Comprehensive DevOps skill for CI/CD, infrastructure automation, containerization, and cloud platforms (AWS, GCP, Azure). Includes pipeline setup, infrastructure as code, deployment automation, and monitoring. Use when setting up pipelines, deploying applications, managing infrastructure, implementing monitoring, or optimizing deployment processes.
playwright-pro
Production-grade Playwright testing toolkit. Use when the user mentions Playwright tests, end-to-end testing, browser automation, fixing flaky tests, test migration, CI/CD testing, or test suites. Generate tests, fix flaky failures, migrate from Cypress/Selenium, sync with TestRail, run on BrowserStack. 55 templates, 3 agents, smart reporting.