web-browser
Web Browser
When to use
- Navigate or interact with live websites.
- Click buttons, fill forms, or extract page content.
- Evaluate JavaScript in a real browser context.
- Capture screenshots for debugging or review.
- UI validation and smoke testing in a real browser.
- Measure and gate latency regressions in web-browser tools with the local benchmark scripts.
Requirements
- Run commands from
codex/skills/web-browser/(so./tools/*.jsresolves). - Node.js (ESM + top-level
await) and repo deps installed (notablypuppeteer-core). - Chrome/Chromium installed, or set
CODEX_CHROME_PATH.
Quick start
# Start Chrome with CDP on :9222
./tools/start.js
# Safer (recommended on your laptop): avoid killing an existing Chrome session
./tools/start.js --no-kill
# Open a deterministic tab, then operate on it
./tools/nav.js https://example.com --new
./tools/eval.js 'document.title'
./tools/screenshot.js
Loop: take small steps → inspect state (eval.js / screenshot.js) → repeat.
Performance pass ($lift)
Use this when optimizing or validating web-browser tool latency.
./tools/start.js --no-kill
./tools/nav.js https://example.com --new
cd tools
npm run -s bench:all -- --warmup 5 --samples 30 --screenshot-samples 20
For stricter CI-style gating, add p95 budgets:
cd tools
npm run -s bench:all -- \
--warmup 5 \
--samples 30 \
--screenshot-samples 20 \
--budget-nav-p95-ms 310 \
--budget-eval-p95-ms 220 \
--budget-screenshot-p95-ms 320 \
--budget-start-p95-ms 140
Configuration
- Flags
start.js:--port,--user-data-dir,--chrome-path,--profile,--no-killnav.js/eval.js/screenshot.js/pick.js:--port,--browser-url
- Environment
CODEX_BROWSER_URL: CDP URL (used when no CLI--port/--browser-url)CODEX_BROWSER_PORT(alias:CODEX_CDP_PORT): CDP port for localhostCODEX_BROWSER_USER_DATA_DIR: Chrome user data dir (used bystart.js)CODEX_CHROME_PATH(aliases:CHROME_PATH,PUPPETEER_EXECUTABLE_PATH): Chrome/Chromium executable
Targeting (active tab)
All tools operate on the “active tab”, defined as the last page returned by puppeteer.pages() (roughly: the most recently opened tab).
- Prefer
./tools/nav.js <url> --newwhen you want deterministic targeting. - If actions hit the “wrong” tab, close extra tabs or open a fresh one.
Common commands
# See all options (safe; does not start/kill Chrome)
./tools/start.js --help
# Use a non-default port
./tools/start.js --port 9223 --no-kill
./tools/nav.js --port 9223 https://example.com --new
# Or configure once via env
export CODEX_BROWSER_URL=http://localhost:9223
./tools/eval.js 'document.title'
# Wait for a selector (polling with timeout; good for SPAs)
./tools/eval.js '(async () => { for (let i = 0; i < 50; i++) { const el = document.querySelector("button[type=submit]"); if (el) return true; await new Promise(r => setTimeout(r, 100)); } return false; })()'
# Screenshot current viewport
# Prints a PNG filepath in your system temp dir
./tools/screenshot.js
# Pick elements interactively
# Prints tag/id/class/text/html/parents for one element (or many via Cmd/Ctrl+click)
./tools/pick.js "Click the submit button"
Tip: use pick.js to inspect attributes/text, then craft a selector for eval.js.
Recipes
# Scrape structured data (return an array of objects for readable output)
./tools/eval.js 'Array.from(document.querySelectorAll("a"), a => ({ href: a.href, text: a.textContent?.trim() }))'
- Login flows
- Dedicated automation profile: run
./tools/start.js, log in once, then reuse the persisted profile in your user-data-dir (default:~/.cache/scraping). - Default-profile bootstrap: run
./tools/start.js --profilewhen you truly need existing cookies/logins.
- Dedicated automation profile: run
Security & privacy
./tools/start.js --profileusesrsync -a --deleteto copy your default Chrome profile into your user-data-dir (cookies/sessions/PII).- Treat your user-data-dir (default:
~/.cache/scraping) as sensitive; avoid on shared machines and delete it when done if needed.
Troubleshooting
✗ Failed to connect to Chrome via CDP: run./tools/start.js(or setCODEX_BROWSER_URL/--portto match your Chrome).✗ No active tab found: open a page first (e.g.,./tools/nav.js https://example.com --new).- Port
9222is busy: pick a new one (./tools/start.js --port 9223). - Selector flakiness: use the polling pattern above; SPAs often need a wait after
domcontentloaded. - Chrome path not found: set
CODEX_CHROME_PATHor pass--chrome-path.
Pitfalls
./tools/start.jsdefaults to killing Chrome processes (use--no-killto avoid this).- Use single quotes around JS to avoid shell-escaping issues.
Prove $lift uses Zig
When you need objective proof that the local $lift tooling is Zig-based:
bench_stats --help 2>&1 | sed -n '1,6p'
perf_report --help 2>&1 | sed -n '1,6p'
brew cat tkersey/tap/lift | rg -n 'depends_on \"zig\" => :build|build-exe|bench_stats.zig|perf_report.zig'
References
codex/skills/web-browser/tools/start.jscodex/skills/web-browser/tools/nav.jscodex/skills/web-browser/tools/eval.jscodex/skills/web-browser/tools/screenshot.jscodex/skills/web-browser/tools/pick.jscodex/skills/web-browser/tools/bench-eval.jscodex/skills/web-browser/tools/bench-all.js
More from tkersey/dotfiles
grill-me
>
99complexity-mitigator
Mitigate incidental complexity in existing code when control flow is tangled, nesting is deep, names are hard to parse, or reasoning requires cross-file hops. Use when a review stalls on readability, you need an analysis-first refactor plan before edits, or you want essential-vs-incidental verdicts, dominant-risk triage, ranked simplification steps, one visibility artifact, and a TRACE assessment. Do not use for greenfield requirements discovery, architecture selection, or delivery planning.
59creative-problem-solver
Lateral-thinking playbook that always returns a five-tier strategy portfolio (Quick Win through Moonshot). Use when you need options, alternatives, or trade-offs; when progress is stalled or failing repeatedly; or when you ask to think creatively, reframe constraints, and choose a strategic path before execution.
59mesh
Use `$mesh` for homogeneous leaf-batch execution over `spawn_agents_on_csv`: once planning has shaped repeated independent units, prefer one substantive row per unit with structured results and explicit concurrency.
47invariant-ace
Turn 'should never happen' into 'cannot happen' by defining owned inductive invariants and enforcing them at parse/construct/API/DB/lock/txn boundaries with a verification signal. Use when prompts mention invariants, impossible states, validation sprawl, cache/index drift, idempotency/versioning, retries/duplicates/out-of-order events, race/linearization bugs, loop correctness, or hardening another implementation workflow with invariant checks first.
31refine
Refine an existing Codex skill in place with minimal diffs, then validate with quick_validate. Trigger when asked to improve a skill's trigger description/frontmatter, workflow text, metadata, scripts/references/assets, or agents/openai.yaml; also for requests to iterate, refactor, rename, or fix a skill using usage/session-mining evidence (for example from $seq).
29