Skip to content

Commit c034e34

Browse files
committed
perf(web): fetch snapshot geometry in one batch instead of one spawn per ref
`is visible`/`is hidden` and every interaction that aims by ref ask the web backend for the bounds of every ref of a capture. The provider asked per ref, and each `agent-browser get box` is a fresh Node process: measured on a 408-ref page, 381 spawns for one check. The budget they spent on interpreter startup is why `is visible` on such a page died at the request envelope (`ready timed out after 4500ms`) - the command answers in 90s against a 45s deadline. The `--timeout` that would have raised the deadline is not even accepted by `is`/`wait`, so on a big page those commands could not be made to work at all. `batch` is the backend's own answer to that: it reads a JSON array of commands from stdin and runs them inside ONE process, so the whole geometry pass is a bounded number of spawns - 200 refs measure in 0.28s, all 408 of the bench page in 0.33s, against a 30s per-command budget. Getting `stdin` to the spawn is the whole plumbing cost - `ExecOptions` already supported it, so `runManagedAgentBrowser` forwards it and `runAgentBrowserCommand` passes it through. Grouping lives with the grouping decision: the projection asks for the bounds of every ref it published and the provider decides how many refs to send per call, because the refusal below is a claim about the whole capture and only one owner sees all of it. A ref the batch could not measure is an ordinary answer about that ref, and the entry's own `success` decides that - never the error's wording. `isIgnorableBoxError` is gone with it: it was a table of phrases the backend is free to reword, and a reworded one turned "this ref has no box" into "geometry is broken", failing an interaction over a ref `is visible` never aimed at. What the old code called a fatal error now has to be a fault of the batch as a whole, which is what it actually reports: a whole-batch fault, or an answer that cannot be paired back to the refs, is refused typed as `unusable batch result` with the reason named - the entry count, an entry that carries no verdict (an object with neither `success` nor the command it answers would read as "this element has no box" and drop a node silently), or an entry whose echoed command is not the command that was sent. Position is trusted only as the order of the echoed command, never as the pairing itself, because a mispaired box reads as a working capture with wrong geometry. And a capture whose refs ALL went unmeasurable is refused rather than published: a hidden element still measures as a box, so that shape means the backend answered to refs this snapshot does not publish, and the tree would carry no addressable target with usable bounds. It is decided over the capture rather than per group - a page whose first group went stale still answers for the rest. The refusal reports `reason: snapshot-refs-unmeasurable` with `requestedRefs` and `measuredRects`. Tests: the geometry seam is its own file, `agent-browser-provider-rects.test.ts`, mirroring the seam the provider source grew (the provider file was past the 1,000-line test tripwire and the ratchet named this split as its fix); the provider file keeps the capture half. The old per-ref tests became the batch contract: one call for the capture, groups bounded per fetch, a group that answered nothing while a later one measured, entries refused by echoed command or missing verdict, and each fault shape above. Measured live (managed agent-browser 0.27.1, same page, base/head interleaved in one loop iteration because this host sat at load 125-223, n=5 medians): | page (refs) | command | this branch | before | | --- | --- | --- | --- | | index.html (408) | `is visible` | **5.9s** | **90.1s, and 3 of 5 runs refused outright** | | index.html (408) | `fill` | **10.1s** | **90.2s** | | light.html (8) | `is visible` | **2.5s** | 3.9s | | light.html (8) | `fill` | 9.7s | 9.0s (noise; light pages were already fine) | Backend spawns for one capture + geometry pass: **4 instead of 385** (408-ref page), 4 instead of 11 (8-ref page). Verdicts are unchanged: on a page with an on-screen button and one at `top: -9000px`, both builds answer identically. Closes #3516
1 parent 4adcae0 commit c034e34

6 files changed

Lines changed: 672 additions & 150 deletions

‎packages/platform-web/src/__tests__/agent-browser-snapshot-refless.test.ts‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -282,8 +282,14 @@ describe('normalizeAgentBrowserSnapshot ref-less content', () => {
282282
});
283283

284284
test('attaches geometry only to the nodes the backend can measure', async () => {
285-
const result = await normalizeAgentBrowserSnapshot(FORM_AFTER_SUBMIT, async (ref) =>
286-
ref === 'e2' ? { x: 1, y: 2, width: 10, height: 20 } : undefined,
285+
const result = await normalizeAgentBrowserSnapshot(
286+
FORM_AFTER_SUBMIT,
287+
async (refs) =>
288+
new Map(
289+
refs.flatMap((ref) =>
290+
ref === 'e2' ? [[ref, { x: 1, y: 2, width: 10, height: 20 }] as const] : [],
291+
),
292+
),
287293
);
288294

289295
expect(result.nodes.find((node) => node.ref === 'e2')?.rect).toEqual({

0 commit comments

Comments
 (0)