Repository navigation
Read snapshot lines that Playwright quotes - #31
Merged
Merged
Conversation
Playwright's AI snapshot is YAML. When an element's role, name, and
marks hold text that YAML would misread, Playwright puts that text in
YAML single quotes and writes each ' inside it as ''. A button named
"Status: live" comes out as
- 'button "Status: live" [ref=e4]'
Both Rust parsers of the snapshot read the role from the start of the
line, so they read it as 'button. ACT reported such a click as
CLICK role:'button "Status: live", and a ' in a name stayed as ''.
With --jev, the element fell out of Jev's lists of buttons, fields, and
scroll areas, because Outline sorts elements by role.
In playwright-core 1.62.1, renderAriaTree builds each key from the
role, the name in JSON double quotes, and marks such as [ref=e4]. It
wraps the key with yamlEscapeKeyIfNeeded when yamlStringNeedsQuotes is
true. For a key, that is when it holds ": ", whitespace before "#",
"{", "}", a backtick, or a control character that JSON does not escape
(DEL and U+0080 to U+009F). The text after a key, "- text:" lines, and
property lines such as "- /url:" never get single quotes:
yamlEscapeValueIfNeeded puts them in double quotes with backslash
escapes, which the parsers already read. A probe page with names and
texts that hold each YAML indicator gave these forms.
A new unquote_line in snapshot.rs removes the quotes from one line and
turns '' back into '. The indent and the text after the closing quote
stay as they are, and every other line comes back unchanged.
PageSnapshot and Outline call it on each line before they parse it, so
a quoted line reads as the same line without quotes does. The text the
model reads goes through it too, so the model sees
- button "It's: here" [ref=e7], not - 'button "It''s: here" [ref=e7]',
and cannot copy the doubled quote into an option.
The shim still writes a named iframe line without single quotes. Rust
now reads both forms, and the model never sees the quotes, so wrapping
would add code and change nothing. Its comment and the shim protocol
doc now give that reason, and a shim test keeps the line unwrapped.
Unit tests give both parsers the exact lines from the probe and check
that each gives the same result as the lines without quotes. Two
acceptance tests click a button named "Status: live": the model's
answer runs as CLICK role:button "Status: live", and with --jev, Jev
picks the button from the page's buttons without a model call.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
brynary
force-pushed
the
snapshot-quoted-lines
branch
from
October 5, 2026 13:21
a9df34d to
b99e1d5
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #30. Playwright wraps an element line of its AI snapshot in YAML single quotes when the name holds
:,#, a brace, a backtick, or a control character:- 'button "Status: live" [ref=e2]'. Both Rust parsers read the role from the start of the line, so they read'button. The element dropped out of Jev's role-based candidates, and reports showedCLICK role:'button "Status: live". The bug is older than this stack; #28 found it.Playwright's rule (1.62.1)
renderAriaTreebuilds each key from the role, the name asJSON.stringify(name), and marks such as[ref=e4]. It wraps the key in'…', doubling any inner', whenyamlStringNeedsQuotes(key)is true. Values are only ever double-quoted, never single-quoted. Observed:Fix
One helper,
unquote_line, removes the wrapping quotes and turns''back into'. It keeps the indent and the text after the closing quote.PageSnapshot, itscondensestep, and Jev'sOutlineall read each line through it, and every other line is unchanged. The model also reads unwrapped lines, so every element line has one form, and it cannot copy a doubled''into an argument.The shim's iframe names (#28) stay unwrapped: Rust now reads both forms, and the model never sees the quotes.
Tests
ACT: the model clicks a button named "Status: live", and the report line isCLICK role:button "Status: live"; with--jev, Jev offers that button among the buttons and picks it with no model call.CLICK role:'button "Status: live".mise run checkpasses. SPEC §7.4, the shim protocol doc, and the CHANGELOG describe the change.Found, not fixed
- button /api/ [ref=e34], gets no JSON quotes; both parsers read no name, and Jev's parser also loses the ref.\xHHescapes in values are not decoded.parse_linetakes the first[ref=on a line, even one inside a name.🤖 Generated with Claude Code