claw-code/rust/clawcode/claw/skills/browser-harness/SKILL.md

12 KiB
Raw Blame History

name description
browser-harness Use when automating browser interactions (open pages, click, type, screenshot), extracting content from anti-scraping sites (Cloudflare, bot detection), or using remote cloud browsers.

Browser Harness — Browser Automation & Interaction Skill

Operational guide for the browser-harness CLI tool covering web page browsing, screenshots, clicking, form filling, web scraping, remote cloud browsers, and anti-scraping content extraction.

browser-harness is already in PATH (C:\Users\%USERNAME%\.local\bin\browser-harness.exe). Use directly — no installation check needed.

When to Use

Use this skill when any of the following apply:

  1. Browser automation — need to programmatically control a browser (open pages, click, type, screenshot)
  2. Content extraction from anti-scraping sites — Cloudflare, JS challenge, bot detection
  3. UI testing / interaction — need to fill forms, click buttons, handle dialogs via coordinates
  4. Remote cloud browsers — need concurrent or persistent browser sessions
  5. Network monitoring — need to capture network requests made by page

How to Use

Two recommended approaches, neither has quoting conflicts. Quick comparison:

Approach When to Use Speed
A. bash script Script reuse, complex operations Fastest
B. --stdin Ad-hoc, no bash available Zero files

Approach A: bash script (fastest)

Write a .sh file with bash single quotes -c '...' — clean quoting, no conflicts:

# open_news.sh
browser-harness -c '
new_tab("https://news.qq.com")
wait_for_load()
print(js("document.title"))
'
bash open_news.sh

Approach B: --stdin pipe (works in any shell)

Code passes via stdin, no quoting issues on the command line:

# PowerShell
@'
new_tab("https://news.qq.com")
wait_for_load()
print(js("document.title"))
'@ | browser-harness --stdin
# bash / WSL
browser-harness --stdin << 'EOF'
new_tab("https://news.qq.com")
wait_for_load()
print(js("document.title"))
EOF

First page open must use new_tab(url), not goto_url(url). goto_url navigates the current tab; if it's a chrome:// page it will fail.

js() quoting tips (universal)

# CSS selector (avoids quote nesting)
js("document.querySelector('#stepDisplay').textContent")

# Reference page globals directly
js("stepDisp.textContent")
js("state.player")

# JSON.stringify returns a string — safest approach
js("JSON.stringify(state.player)")

# Template literals with backticks
js("`Steps: ${stepDisp.textContent}`")

js('JSON.stringify(...)') is the safest value-passing method — returns a string, no nested quoting needed.

Key Capabilities Overview

  • new_tab / goto_url: Open and navigate pages
  • capture_screenshot: Viewport or full-page screenshots
  • click_at_xy: Coordinate-based clicking (bypasses iframe/Shadow DOM issues)
  • type_text / press_key: Keyboard input
  • js(): Execute arbitrary JavaScript in page context
  • cdp(): Direct Chrome DevTools Protocol access
  • NetworkMonitor: Capture HTTP requests
  • readwebfetch: Extract article content from anti-scraping sites (Cloudflare, etc.)
  • start_remote_daemon: Cloud browser for concurrent tasks
  • PDF export, multi-tab management, alert handling

1. Opening Pages

new_tab("https://news.ycombinator.com")   # Open in new tab
wait_for_load()                            # Wait for page load
print(page_info())                         # Print page info

Effect: Opens a new tab, loads Hacker News, prints title/URL/viewport.

goto_url("https://example.com/page2")     # Navigate current tab

Use new_tab for first open, goto_url for subsequent navigation (no new tab created).


2. Screenshots

capture_screenshot()                       # Capture current viewport, auto-send to AI
capture_screenshot("/tmp/shot.png")        # Save to file
capture_screenshot(max_dim=1800)           # Limit dimensions to avoid model rejection
capture_screenshot(full=True)              # Full page (including below fold)

Effect: Screenshot lets the AI "see" the page. Always screenshot first, then decide.

Screenshots are in device pixels, click coordinates are in CSS pixels. On 2× displays, check js("window.devicePixelRatio") first and scale accordingly.


3. Clicking

# 1. Screenshot first — locate the target
capture_screenshot()

# 2. Calculate coordinates, click
click_at_xy(450, 320)                      # Click at (450, 320)

# 3. Screenshot again — confirm the result
capture_screenshot()

Effect: First screenshot shows the button position → mouse clicks on it → second screenshot confirms the page changed.

Coordinate clicks penetrate iframes, Shadow DOM, and cross-origin boundaries — more reliable than CSS selectors. Only use DOM manipulation for hidden elements (0×0 nodes).


4. Form Filling

# Click into the input field first
click_at_xy(300, 400)
# Then type
type_text("hello world")
# Submit
press_key("Enter")

Effect: Mouse clicks the search box → types "hello world" → presses Enter to search.

# Or fill directly with JS
js("document.querySelector('input').value = 'hello'")

5. Getting Page Text

print(page_info())                         # Title + URL + viewport
print(js("document.body.innerText"))       # All page text
print(js("document.title"))                # Page title

Effect: Get page content directly without needing a screenshot.


6. Executing Arbitrary JavaScript

# Get data
data = js("""
  JSON.stringify({
    title: document.title,
    links: [...document.querySelectorAll('a')].map(a => a.href)
  })
""")

# Modify page
js("document.querySelector('.ad-banner')?.remove()")
js("document.body.style.background = 'white'")

# Call APIs
result = js("""
  (async () => {
    const r = await fetch('/api/data');
    return r.json();
  })()
""")

Effect: Run JS in the page context — read data, modify styles, call APIs, just like DevTools Console.


7. Dialog Handling

# Scenario: clicking a button triggers alert
click_at_xy(200, 300)
# Dialog appears, JS is frozen
cdp("Page.handleJavaScriptDialog", accept=True)   # Click "OK"

Effect: When alert() / confirm() / beforeunload dialogs appear, dismiss them at the CDP level — invisible to the user, undetectable by anti-bot.

To suppress all dialogs preemptively:

js("""
window.alert=m=>{};           # Silence alerts
window.confirm=m=>true;       # Auto-confirm
window.onbeforeunload=null;   # Disable leave confirmation
""")

8. Multi-tab Management

# Scenario: switching between multiple pages
tab1 = new_tab("https://a.com")            # Open first
tab2 = new_tab("https://b.com")            # Open second
switch_tab(tab1)                            # Switch back to first
cdp("Target.activateTarget", targetId=tab1) # Bring to foreground (optional)

# List all tabs
for t in list_tabs():
    print(t["url"][:60])

9. Waiting for Page Load

wait_for_load()                            # Wait for page to finish loading
wait_for_text("Login")                     # Wait for text to appear (max 10s)

10. Network Request Capture

# Scenario: verify backend received form submission
from browser_harness.helpers import NetworkMonitor
monitor = NetworkMonitor()

fill_form({"name": "Zhang San", "email": "a@b.com"})
click_at_xy(500, 600)

requests = monitor.get_requests()          # Get captured network requests

11. Scrolling

# Scenario: long page, scroll to bottom to load more
js("window.scrollTo(0, document.body.scrollHeight)")
wait_for_load()
capture_screenshot()                       # Confirm new content appeared

12. PDF Export

# Scenario: save current page as PDF
cdp("Page.printToPDF", landscape=False, printBackground=True)

13. Keyboard Operations

press_key("Enter")                         # Enter
press_key("Tab")                           # Tab
press_key("Escape")                        # Escape
type_text("search keyword")                # Type text sequentially

14. Debugging Tips

# Stuck and don't know the state
print(page_info())                         # Check title/URL/viewport
print(current_tab())                       # Check which tab is attached
tabs = list_tabs()                         # List all tabs
ensure_real_tab()                          # Fix attachment to phantom tab

Common Issues Quick Reference:

Symptom Cause Solution
Blank screenshot Attached to omnibox phantom tab ensure_real_tab()
Click does nothing Wrong coordinates / missed target Re-screenshot, recalculate, or use js
Page frozen Dialog blocking JS cdp("Page.handleJavaScriptDialog", accept=True)
Link click no navigation beforeunload blocking cdp("Page.handleJavaScriptDialog", accept=True)
Can't get data Login required Ask user to login, or sync_local_profile
js() SyntaxError PowerShell ate the double quotes Use --stdin or bash script approach
page_info() title has emoji browser-harness auto-injection, normal Ignore
Sequential moves don't work Wall/box blocking print(js('JSON.stringify(state)')) check state
steps-- goes negative Won't happen — undo() has history.length guard But undo doesn't trigger win state reset

15. Remote Cloud Browsers

For Browser Use Cloud only — suitable for concurrent subtasks or maintenance-free operation.

start_remote_daemon("work")                # Start a cloud browser
start_remote_daemon("work", proxyCountryCode=None)  # Disable proxy
BU_NAME=work browser-harness -c '
new_tab("https://example.com")
print(page_info())
'
stop_remote_daemon("work")                 # Stop, billing stops

Start with login state:

list_cloud_profiles()                      # List stored cloud profiles
sync_local_profile("My Chrome Profile")    # Upload local cookies
start_remote_daemon("work", profileName="My Chrome Profile")

16. readwebfetch — Bypass Anti-Scraping

Scenario: Site has anti-scraping (Cloudflare, JS challenge, bot detection), regular HTTP requests fail.
How it works: Extracts content via Readability.js in a real browser — no HTTP request, anti-bot can't detect it.

Prerequisite: browser-harness auto-loads the read_webfetch extension when launching Chromium (--load-extension).

d = readwebfetch("https://blog-link.com")
print(d["title"])
print(d["text"][:500])

Return structure:

Field Description
url Page URL
title Page title
text Readability-extracted plain text
excerpt Summary
byline Author

Execution:

# bash script
browser-harness -c '
d = readwebfetch("https://blog.csdn.net/...")
print(d["title"])
print("Total " + str(len(d["text"])) + " chars")
'
# PowerShell
@'
d = readwebfetch("https://blog.csdn.net/...")
print(d["title"])
print("Total " + str(len(d["text"])) + " chars")
'@ | browser-harness --stdin

Windows PowerShell Notes

  • Use double quotes "..." for -c argument, single quotes '...' inside Python
  • Prefer querySelector('#id') over getElementById("id") to avoid quote nesting
  • Use JSON.stringify(...) for safe data transfer from js()
  • For complex scripts, write a .py file and pipe via Get-Content