fix(server): text-only browser snapshots no longer hang on hidden tabs

BY
5d ago
+93-36

Problem

preview_snapshot fails after 10 seconds on a desktop tab the Browser panel isn't showing. Over a week of nightlies, 93 of 252 snapshots from one user failed this way, across 12 threads. Successful ones took 0.1 to 0.7 s. A preview_evaluate on the same tab right after a failure returned in 4 ms.

The snapshot always took a screenshot, even when the agent set neither includeImage nor save. That's the default, and nothing reads the image then. A hidden tab draws no frames, so Page.captureScreenshot never answers and the snapshot fails, though its text was ready in 50 ms.

preview_snapshot on a hidden tab: before, the text is ready in 50 ms but the snapshot waits on the screenshot and fails at 10,008 ms; after, the screenshot isn't requested and the snapshot returns in 50 ms

Change

  • Tool: preview_snapshot asks the host for a screenshot only when includeImage or save is set.
  • Server browser: without one, the snapshot skips Page.captureScreenshot and the screencast pause around it.
  • Contract: PreviewAutomationSnapshot.screenshot is optional. Only the server browser builds snapshots and only this tool reads them, so no client changes.

Snapshots with includeImage or save on a hidden tab still fail. Making those tabs draw is #18873. The two changes touch different parts of the capture code.

Scope and approval

Refs #16567. The maintainer triage confirms the failure: an offscreen desktop guest never answers Page.captureScreenshot, and the triage notes a snapshot captures even when includeImage is false. This fixes that for the default, image-free call only. Host eviction after timeouts (#16921) and the broker printing its own 15000 ms budget instead of the host's 10 s message are separate and not changed here.

Verification

  • A new test drives real headless Chromium through a CDP session whose Page.captureScreenshot never answers. The tree and refs come back, and no capture is requested. On main it fails after 10,045 ms with "The page was too busy to capture a snapshot in time."
  • MCP tests check that only includeImage or save calls ask the host for a screenshot, and that a reply without one returns the text alone.
  • In headless Chromium with begin-frame control, so no frames are drawn, requestAnimationFrame never fires and a bare Page.captureScreenshot stays pending. A text-only snapshot returns in 50 ms. The default one fails at 10,008 ms.
  • vp test run on ServerBrowserPage.test.ts, McpHttpServer.test.ts and ServerBrowser.test.ts: 90 passed. Server and contracts typecheck, scoped lint and format pass.
  • Not run in the packaged desktop app. The hidden-tab behavior in Electron comes from the fixtures in #16567.

Reviewed with GPT-6.1 Sol in Codex.

🤖 Generated with Claude Code

[!NOTE]
Implemented by claude-opus-5-5 with the help of gpt-6.1-sol