Files
paseo/docs/browser-capture-harness.md
Mohamed Boudra 9c220c2451 Browser tools: full accessibility snapshot, trusted input, dialogs, evaluate, and tab controls (#1881)
* feat(browser): replace flat snapshot with ARIA tree and page-side refs

* feat(browser): trusted CDP input with actionability waits

* feat(browser): auto-handle javascript dialogs

* feat(browser): add browser_evaluate

* feat(browser): add scroll, resize, and close_tab tools

* fix(browser): review fixes for snapshot/input/dialog seams

* fix(browser): address review findings on dialog capture, truncation, and keypress targets

* fix(harness): drop resize check that cannot run under parked compositor surfaces

* fix(browser): second review round — snapshot fidelity, key input, dialog scoping
2026-07-04 01:38:07 +02:00

3.4 KiB

Browser Capture Harness

The desktop capture harness is the real-Electron verification path for browser screenshots. It validates the compositor behavior that unit tests cannot see:

  • the resident automation <webview> starts in the production parking state;
  • the parked guest remains paintable and has a copyable viewport frame;
  • the resident webview guest is sized to 1280x800 logical pixels;
  • multiple resident webviews are parked as an overlapping stack without per-capture stacking changes;
  • a newly attached resident webview whose first useful frame is delayed can be captured by retrying until the frame appears;
  • both viewport capturePage and full-page CDP screenshots return real pixels from the permanent production parking state;
  • guest background throttling can be disabled once at attach without per-capture renderer coordination.

Run it with the repo Electron:

npm run capture-harness --workspace=@getpaseo/desktop

Run the browser automation fixture with:

PASEO_CAPTURE_HARNESS_GROUP=automation npm run capture-harness --workspace=@getpaseo/desktop

The automation group uses a real guest webview to verify the page-side ref contract: ARIA-like snapshot text includes headings, static text, and controls; refs survive pushState when the element still matches; same-URL rerenders stale old refs; and a file-input ref can be resolved to a CDP backend node id for upload. It also verifies page-context evaluation, including passing a resolved ref element as the function argument.

On macOS the harness process must set app.setActivationPolicy("accessory") and hide the Dock icon before creating any window. showInactive() only prevents window focus; a normal Electron app launch can still activate the app and steal focus. Harness windows are then created hidden, positioned in a screen corner, skipped from the taskbar where Electron supports it, and revealed with showInactive() from ready-to-show. Do not replace this with show(), focus(), or app.focus(): the compositor only needs visible inactive windows, and harness runs must not steal focus from the person using the machine.

The harness writes PNG evidence and results.json to:

packages/desktop/capture-harness/out/

A passing run prints PASS lines for the production P1 attach-off parking state, including fresh, settled, 75-second soak, multi-tab, viewport, and full-page checks. The PNG sizes may be device-pixel scaled; on a Retina display the 1280x800 logical viewport is usually saved as 2560x1600.

Mechanism

Electron captures copy from the guest web contents' compositor surface. A resident webview parked with display:none, offscreen coordinates, or opacity:0 can lose its copyable surface. The production parking state keeps the host fixed at left:0, top:0, width:1px, height:1px, overflow:hidden, opacity:1, and pointer-events:none. The webviews inside stay full-size at 1280x800, display:inline-flex, and absolutely overlap at left:0, top:0.

There is no renderer prep/restore handshake. Main disables guest background throttling once when the webview attaches, then screenshot capture uses the shared serialized queue, invalidates before each attempt, and retries known first-frame failures within the 5-second capture budget. Viewport screenshots use capturePage({ stayHidden:false }); full-page screenshots use the existing CDP path with layout metrics and screenshot clip.