Install any skill in seconds. Free to start, no credit card required.
Get Started Free →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.
| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 108% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 5% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 45% | 0% |
| case-04 | ✗→✓ | ▲ Improved | 86% | 0% |
| case-07 | ✗→✓ | ▲ Improved | 112% | 0% |
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:
When NOT to use this skill:
Why Playwright over Selenium or Puppeteer:
sleep() or waitForElement() needed for most actionsplaywright codegen records your actions and generates scriptsSelector priority (most to least reliable):
data-testid, data-id, or custom data attributes — stable across redesigns#id selectors — unique but may change between deploysarticle, nav, main, section — resilient to CSS changes.product-card, .price — brittle if classes are generated (e.g., CSS modules)nth-child(), nth-of-type() — last resort, breaks on layout changesUse 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 for complete pagination handlers and scroll patterns.
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 for complete API reference on fill(), select_option(), set_input_files(), and expect_file_chooser().
await page.screenshot(path="full.png", full_page=True)await page.locator("div.chart").screenshot(path="chart.png")await page.pdf(path="out.pdf", format="A4", print_background=True){page}_{viewport}_{state}.pngSee playwright_browser_api.md for full screenshot/PDF options.
Core extraction patterns:
<thead> headers and <tbody> rows into dictionaries::attr() for attributes)See data_extraction_recipes.md for complete extraction functions, price parsing, data cleaning utilities, and output format helpers (JSON, CSV, JSONL).
context.cookies() and context.add_cookies()context.storage_state(path="state.json") to save, browser.new_context(storage_state="state.json") to restoreBest 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 for cookie and storage state API details.
Modern websites detect automation through multiple vectors. Apply these in priority order:
navigator.webdriver = true via init script (critical)random.uniform() delays between actionsSee 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.
wait_for_selector), not the page load eventpage.expect_response("**/api/data*") to intercept and wait for specific API calls>> operator: page.locator("custom-element >> .inner-class")scroll_into_view_if_needed() to trigger loadingSee playwright_browser_api.md for wait strategies, network interception, and Shadow DOM details.
TimeoutError, try alternative selectors before failingpage.screenshot(path="error-state.png") on unexpected failures for debuggingRetry-After headersSee anti_detection_patterns.md for the complete exponential backoff implementation and rate limiter class.
Scenario: Extract product data from a single page with JavaScript-rendered content.
Steps:
headless=False), switch to headless for productionquery_selector_all with field mappingpythonasync 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
Scenario: Scrape search results across 50+ pages.
Steps:
pythonasync 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
Scenario: Log into a portal, navigate a multi-step form, download a report.
Steps:
pythonasync 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()
| 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.
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.
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.
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.
Bad: Hardcoding usernames and passwords in Python files. Good: Use environment variables, .env files (gitignored), or a secrets manager. Pass credentials via CLI arguments.
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.
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.
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.
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".
Other measured skills in the registry, with their headline benchmark lift.