| name | agent-browser |
| description | Guide for browser automation with AI agents using Playwright — navigation, scraping, forms. |
Agent Browser
Automate browser interactions programmatically with Playwright. Use this guide for scripted, deterministic automation tasks: form filling, scraping, screenshot capture, and test flows.
For autonomous, goal-driven browsing with adaptive decision-making, see /pocket-knife:agentic-browser.
For gathering data from the web, combine this guide with /pocket-knife:web-search.
Setup
npm install playwright
npx playwright install chromium
Python:
pip install playwright
python -m playwright install chromium
Page Navigation
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
page = browser.new_page()
page.goto("https://example.com")
page.go_back()
page.go_forward()
page.reload()
browser.close()
Set a custom viewport and user agent:
page = browser.new_page(
viewport={"width": 1280, "height": 800},
user_agent="Mozilla/5.0 (compatible; MyBot/1.0)"
)
Element Selection
CSS Selectors
page.locator("button.submit-btn").click()
page.locator('input[name="email"]').fill("user@example.com")
page.get_by_text("Sign in").click()
page.get_by_role("button", name="Submit").click()
page.get_by_label("Password").fill("secret")
page.get_by_placeholder("Search…").fill("query")
XPath
page.locator('xpath=//div[@data-testid="card"][1]').click()
Prefer get_by_role, get_by_label, get_by_text over XPath — they are more resilient to DOM changes.
Chaining Locators
form = page.locator("form#login")
form.get_by_label("Username").fill("alice")
form.get_by_label("Password").fill("hunter2")
Form Filling
page.get_by_label("First name").fill("Alice")
page.get_by_label("Country").select_option("BR")
page.get_by_label("I agree").check()
page.get_by_label("Upload CSV").set_input_files("/path/to/file.csv")
page.get_by_role("button", name="Submit").click()
page.wait_for_url("**/dashboard")
Screenshot Capture
page.screenshot(path="full.png", full_page=True)
page.locator(".hero-section").screenshot(path="hero.png")
page.screenshot(path="clip.png", clip={"x": 0, "y": 0, "width": 800, "height": 600})
Waiting Strategies
Never use fixed time.sleep(). Use Playwright's built-in waits.
page.wait_for_selector(".results-list")
page.locator(".spinner").wait_for(state="hidden")
page.wait_for_load_state("networkidle")
page.wait_for_url("**/success")
with page.expect_response("**/api/data") as resp:
page.get_by_role("button", name="Load").click()
data = resp.value.json()
Handling Single-Page Applications (SPAs)
SPAs update the DOM without full navigation events. Adjust your approach:
page.get_by_role("link", name="Dashboard").click()
page.wait_for_selector('[data-page="dashboard"]')
with page.expect_response(lambda r: "/api/items" in r.url) as resp_info:
page.get_by_role("button", name="Load Items").click()
items = resp_info.value.json()
Cookie and Auth Management
Save and Reuse Session
context = browser.new_context()
page = context.new_page()
page.goto("https://example.com/login")
context.storage_state(path="auth.json")
context = browser.new_context(storage_state="auth.json")
page = context.new_page()
page.goto("https://example.com/dashboard")
Set Cookies Manually
context.add_cookies([{
"name": "session",
"value": "abc123",
"domain": "example.com",
"path": "/"
}])
HTTP Auth
context = browser.new_context(http_credentials={"username": "user", "password": "pass"})
Scraping Patterns
Extract a List of Items
items = page.locator(".product-card").all()
data = []
for item in items:
data.append({
"title": item.locator(".title").inner_text(),
"price": item.locator(".price").inner_text(),
"link": item.locator("a").get_attribute("href"),
})
Paginate
results = []
while True:
results.extend(scrape_current_page(page))
next_btn = page.locator('a[rel="next"]')
if not next_btn.is_visible():
break
next_btn.click()
page.wait_for_load_state("networkidle")
Intercept Network Requests
def handle_response(response):
if "/api/products" in response.url:
print(response.json())
page.on("response", handle_response)
page.goto("https://example.com/shop")
Error Recovery
from playwright.sync_api import TimeoutError as PlaywrightTimeout
def safe_click(page, selector: str, retries: int = 3):
for attempt in range(retries):
try:
page.locator(selector).click(timeout=5000)
return
except PlaywrightTimeout:
if attempt == retries - 1:
raise
page.reload()
Common issues and fixes:
| Problem | Fix |
|---|
| Element not found | Increase timeout; wait for network idle first |
| Stale element | Re-query after navigation or DOM mutation |
| CORS / CSP blocking | Use route to intercept and modify headers |
| Modal blocking click | Dismiss modal before interacting with page |
| Captcha | Use a logged-in session (see Cookie section) or rotate user agents |
Parallel Execution
from playwright.sync_api import sync_playwright
from concurrent.futures import ThreadPoolExecutor
def scrape_url(url: str) -> dict:
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page()
page.goto(url)
result = {"url": url, "title": page.title()}
browser.close()
return result
urls = ["https://example.com/1", "https://example.com/2"]
with ThreadPoolExecutor(max_workers=4) as pool:
results = list(pool.map(scrape_url, urls))