ARM-142: add vera-browser CLI for headless browsing - #81
Conversation
Gives Vera a real headless browser so she can render websites, take screenshots at specific viewports, extract post-JS-execution DOM, and evaluate JS in page context — things web_fetch can't do. - scripts/vera-browser.ts: bun CLI with screenshot/dom/eval subcommands and five viewport presets (iphone-se, iphone-14-pro, pixel-7, desktop-1280, desktop-1920). Mobile presets set the iOS/Android UA and isMobile/hasTouch. 30s default timeout, --no-sandbox launch args, full-page screenshots by default. Navigation waits for networkidle unless --wait-for is supplied (then domcontentloaded + waitForSelector). Output path prints on stdout so it can be piped into pi's read tool. - Dockerfile: install playwright@1.59.1 into /opt/vera/scripts and run 'playwright install --with-deps chromium' as root before the USER vera switch. PLAYWRIGHT_BROWSERS_PATH=/opt/ms-playwright so the browser lives in a shared world-readable path. Symlinks the script to /usr/local/bin/vera-browser. Adds ~400–500 MB to the final image.
ARM-142 Give Vera a headless browser so she can actually render websites
ProblemWhen asked to "look at" a website, Vera can currently:
She cannot:
This came up today during a mobile-layout review of agent-vera.com — Vera had to caveat every finding with "I can't actually render the page, this is CSS reasoning only." That's a real gap for any frontend, QA, design-review, or visual-regression work. ProposalAdd a headless browser capability to Vera's environment, exposed as a CLI she can drive via RequirementsMust have:
Nice to have:
Implementation sketchPlaywright CLI wrapper Vera calls it from Constraints / watch-outs
Success criteriaAfter this lands, Vera can redo the agent-vera.com mobile audit with real screenshots at 375/393/768/1280 px viewports attached to her review — not just CSS reasoning. She can also close ARM-XXX (mobile fixes) by visually verifying the result rather than caveating. Related
🛠️ Prep — Playwright CLI wrapperSizingWell-sized for one coding session. The work is a Dockerfile change + one script + a smoke test. No splitting. Research Notes
Entry Points
Subcommand surface (must-have for this ticket)Match the sketch in the ticket body:
Nice-to-haves deferred (do NOT do in this ticket — spin off follow-ups if needed): lighthouse, interactions (click/type/scroll), console/network capture, video recording, prefers-color-scheme emulation. The ticket body correctly marks these as "nice to have" and this ticket should not balloon. Definition of Done
Test cases
Edge cases / gotchas
In-flight work scan
Implementation promptAdd a headless browser to Vera: in |
Without a skill the headless browser CLI lands dark — the agent has no keyword-triggered guidance to use it and falls back to web_fetch + CSS reasoning like before. This adds .vera/skills/vera-browser/SKILL.md so 'mobile layout', 'screenshot', 'render', 'visual review', etc. routes to the new tool with concrete patterns: - read-back flow (vera-browser screenshot → read <path>) - mobile-audit loop (sweep iphone-se / iphone-14-pro / pixel-7 / desktop-1280) - SPA post-hydration DOM via vera-browser dom --wait-for - page state probing via vera-browser eval Skill is auto-loaded by seedStateDir → loadSkillSummaries; no code change needed. Description front-loads triggers so they survive the 120-char in-prompt truncation in buildSkillsPrompt.
Summary
Gives Vera a real headless browser via a new
vera-browserCLI so she can render websites, capture screenshots at specific viewports, extract post-JS-execution DOM, and evaluate JS in page context — thingsweb_fetch(raw HTML only) can't do.Closes ARM-142.
What's in the box
scripts/vera-browser.ts— bun CLI with three subcommands:Viewport presets (width × height, default =
desktop-1280):iphone-seiphone-14-propixel-7desktop-1280desktop-1920Mobile presets set
isMobile: true,hasTouch: true, and a real iOS / Android UA string. Desktop presets use a Linux Chrome UA.Behavior:
--no-sandbox --disable-dev-shm-usage(we trust the URLs the agent visits).networkidleby default; if--wait-for <selector>is given, waits fordomcontentloaded+ the selector instead (many SPAs never go fully idle).--no-full-pageclips to the viewport./tmp/vera-browser/<ts>-<slug>.png. The path prints on stdout so it can be piped straight into pi'sreadtool.domprintsdocument.documentElement.outerHTMLafter JS runs (differs fromweb_fetchwhich gets raw HTML).evalruns the user script viapage.evaluate(new Function("return (" + script + ");"))and prints the JSON-stringified result. Subject to page CSP (fine fordocument.title-type probes).Dockerfile — installs
playwright@1.59.1into/opt/vera/scriptsand runsplaywright install --with-deps chromiumas root before theUSER veraswitch, so Chromium + system libs + fonts land in a shared, world-readable/opt/ms-playwright.PLAYWRIGHT_BROWSERS_PATHis set viaENVso it persists to runtime. A symlink at/usr/local/bin/vera-browserputs the script on PATH..vera/skills/vera-browser/SKILL.md— without a skill the CLI lands dark, so the agent has no keyword-triggered guidance to reach for it and falls back toweb_fetch+ CSS reasoning like before. The skill front-loads triggers (screenshot,mobile layout,responsive,viewport,visual review,frontend QA,SPA,headless browser, …) so they survive the 120-char in-prompt truncation inbuildSkillsPrompt, and documents the concrete patterns:vera-browser screenshot → read <path>)vera-browser dom --wait-forvera-browser eval--wait-forfor SPAs that never go idle)Auto-loaded by
seedStateDir→loadSkillSummaries→buildSkillsPromptForAgent. Zero code change required.Image size impact: ≈ 400–500 MB (Chromium ≈ 200 MB, system libs + fonts ≈ 200 MB). Documented in the ticket; not a blocker.
Smoke tests (local simulation)
I reproduced the runtime stage layout locally (bookworm-slim userland, non-root
verauser, samenpm install playwright@1.59.1+npx playwright install chromiumsteps, system libs and fonts backfilled viaapt-get download+dpkg-deb -xintoLD_LIBRARY_PATH/FONTCONFIG_FILE) and ran every DoD check against the real built script invoked via the/usr/local/bin/vera-browsersymlink:vera-browser --helpvera-browser screenshot https://example.com --viewport iphone-se→ PNGvera-browser eval https://example.com --script "document.title""Example Domain"vera-browser dom https://example.comExample Domain, exits 0--wait-for h1on example.com--wait-for "#definitely-not-here"on example.comvera-browser: forSelector: Timeout 3000ms exceeded., exit 1--no-full-pageclips to exact viewporthttps://this-domain-does-not-exist.invalid)vera-browser: goto: net::ERR_NAME_NOT_RESOLVED …, exit 1, < 5 spython3 -m http.server 8765→vera-browser eval)"Directory listing for /"react.dev): rendered DOM bytes > rawcurlHTML bytesreadtool (png attachment)readthe iphone-se PNG and saw real rendered text ("Example Domain" heading + paragraph + "Learn more" link)quick_validate.pyon the new skillProof of end-to-end render attached as a comment on ARM-142.
What's not in this PR
Per the ticket, these nice-to-haves are deferred (spin off follow-ups if/when needed):
prefers-color-scheme/reduced-motion/ geolocation emulationFollow-up ops notes
--ipc=hostis Playwright's recommendation for Chromium memory (docker-run flag, not Dockerfile). If the host harness doesn't already set it, a runaway page could OOM the agent container. Out of scope here; worth a follow-up.1.59.1so image rebuilds are reproducible. Browser version is implicitly pinned via Playwright.Test plan for reviewer
Expected: help text, a 750 × 1334 PNG on the host,
"Example Domain".