Skip to content

fix(web): preserve the backend ref so snapshot refs match actionable refs - #2283

Open
thymikee wants to merge 1 commit into
mainfrom
fix/web-ref-preservation
Open

fix(web): preserve the backend ref so snapshot refs match actionable refs#2283
thymikee wants to merge 1 commit into
mainfrom
fix/web-ref-preservation

Conversation

@thymikee

@thymikee thymikee commented Sep 4, 2026

Copy link
Copy Markdown
Member

Summary

Fixes a web ref mismatch where the ref an agent reads off a snapshot does not equal the ref the agent-browser backend resolves on the next action, causing taps/fills to land in the wrong element.

Repro

On the ShopDemo web login screen (driven through agent-device with the web platform):

  1. snapshot shows the username textbox as one ref and the passcode textbox as the next ref.
  2. fill @<ref> "..." aimed at the username lands in the passcode field (verified against live CDP DOM ground truth: #login-username stayed empty while #login-password received the text).

Root cause

The web/agent-browser backend mints refs in tree order and skips non-interactive nodes (e.g. the leading generic container), so its @eN refs are not dense — there are gaps.

agent-device was dropping that backend ref in normalizeAgentBrowserSnapshot and then re-minting a dense positional e${index+1} in attachRefs. The ref an agent reads off the rendered snapshot (dense, by printed position) therefore did not equal the ref the backend resolves on the next action (tree-ordered, by its own map). When a node was skipped, the two numbering schemes drifted apart and the same @eN string referred to two different elements on the two sides of the round trip.

Fix

  • packages/kernel/src/snapshot.ts: add an optional ref to RawSnapshotNode and make attachRefs keep a node's existing ref when present, falling back to dense e${index+1} otherwise. Backends that don't mint refs (iOS/Android/maestro) are unaffected — they still get dense numbering.
  • packages/platform-web/src/agent-browser-snapshot.ts: carry each web node's backend ref (draft.ref, which agent-browser already uses for box/geometry fetches) onto the node so the displayed ref equals the actionable ref.

No code derives an index from a ref's numeric value, so non-dense refs are safe downstream (findNodeByRef/normalizeRef operate on the ref string).

Tests

  • packages/kernel/src/snapshot-attach-refs.test.ts (new): preserves a backend ref; re-mints dense refs for nodes without one.
  • packages/platform-web/.../agent-browser-snapshot.test.ts: new case reproducing the non-dense login shape (Username e2, Passcode e3, Sign-in e4) and asserting the refs survive normalization.
  • packages/platform-web/.../agent-browser-provider.test.ts: expectedNode now carries the preserved ref.

Full pnpm typecheck green; unit-core suites for packages/platform-web, packages/kernel, core snapshot, interaction, and CLI snapshot-output tests all pass.

…refs

The web/agent-browser backend mints refs in tree order and skips
non-interactive nodes, so its `@eN` refs are not dense. agent-device was
dropping that ref in `normalizeAgentBrowserSnapshot` and then re-minting a
dense positional `e${index+1}` in `attachRefs`. The ref an agent reads off
the snapshot (dense, positional) therefore did not equal the ref the backend
resolves on the next action (tree-ordered). On the ShopDemo login screen the
username textbox displayed as one ref while the backend's ref for the same
position pointed at the passcode field, so `fill @e3` landed in the wrong
input.

Preserve the backend ref on each web node and make `attachRefs` keep a
node's existing `ref` when present, falling back to dense numbering for
backends that do not mint refs (iOS/Android/maestro are unaffected).
@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown

Size Report

Metric Base Current Diff
JS raw 2.61 MB 2.61 MB +17 B
JS gzip 878.4 kB 878.4 kB +8 B
npm bundled raw 2.61 MB 2.61 MB +17 B
npm bundled gzip 878.4 kB 878.4 kB +8 B
npm tarball 1.04 MB 1.04 MB +178 B
npm unpacked 3.52 MB 3.52 MB +526 B
npm clean-installed 3.52 MB 3.52 MB +526 B

npm unpacked components

Component Base Current Diff
JS / dist source 2.77 MB 2.77 MB +526 B
Apple runner source/project 544.4 kB 544.4 kB 0 B
Apple snapshot presentation source 33.8 kB 33.8 kB 0 B
Apple Simulator snapshot bridge source 30.5 kB 30.5 kB 0 B
macOS helper source 54.8 kB 54.8 kB 0 B
Android helper artifacts 43.3 kB 43.3 kB 0 B
Other package files 46.7 kB 46.7 kB 0 B

Startup median (7 runs, lower is better):

Scenario Base Current Diff
CLI --version 29.2 ms 30.6 ms +1.3 ms
CLI --help 78.8 ms 78.2 ms -0.6 ms

Top changed chunks: no changes in the largest emitted chunks.

Top changed packed files

Packed file Base Current Diff
dist/src/sdk-contracts.d.ts 18.3 kB 18.8 kB +509 B
dist/src/agent-browser-provider.js 16.4 kB 16.4 kB +10 B
dist/src/sdk-contracts.js 11.7 kB 11.7 kB +7 B

@thymikee

thymikee commented Sep 4, 2026

Copy link
Copy Markdown
Member Author

Review at exact head f848b6f found one actionable regression-proof gap. The new tests prove the agent-browser normalizer retains draft.ref and attachRefs preserves it in isolation, but they do not prove the shipped snapshot → buildSnapshotState → stored-ref resolution → web fillRef/tapRef route. Existing route coverage uses dense positional refs and would still pass with the original bug. Please add a non-dense production-route scenario where the snapshot exposes e2/e3/e4 and acting on displayed e2 is forwarded as @e2 to the intended username element; reverting either preservation leg must make it red. Also record practical after-fix web evidence (snapshot ref, action command, and DOM outcome), since the PR currently records only the live reproduction. No other code finding. The failed iOS smoke was an unrelated xcrun SDK lookup timeout during unchanged package verification; its failed-job rerun is in progress.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant