fix: keep interactive nodes in snapshot -c; better stale-ref guidance (issue #2/#3)
- snapshot -c (compact) now always keeps lines with an interactive ARIA role (button/link/textbox/combobox/option/…), not only `ref=`/`": "` lines — so a clickable control can't vanish from compact output and leave the agent clicking an empty ref (issue #2 P1). Additive: only ever keeps more. compact tests green. - stale-ref error now leads with "take a fresh snapshot" and points to the `eval` fallback for ref-churning SPAs, and demotes AGENT_BROWSER_VERIFY_REF=0 to a flagged last resort instead of presenting it as the fix (issue #3 P1).
This commit is contained in:
@@ -556,8 +556,12 @@ async fn verify_ref_identity(
|
|||||||
Err(format!(
|
Err(format!(
|
||||||
"Ref {} no longer matches its snapshot. Was [{} \"{}\"], now [{} \"{}\"].\n\
|
"Ref {} no longer matches its snapshot. Was [{} \"{}\"], now [{} \"{}\"].\n\
|
||||||
The DOM mutated between snapshot and interaction (typical with React/Vue \
|
The DOM mutated between snapshot and interaction (typical with React/Vue \
|
||||||
reusing nodes during re-render). Take a fresh snapshot, then re-target.\n\
|
reusing nodes during re-render). Fix: take a fresh `snapshot` and re-target \
|
||||||
To bypass this guard set AGENT_BROWSER_VERIFY_REF=0.",
|
with the new ref. For SPAs where refs churn every interaction, drive the \
|
||||||
|
element directly with `eval` (e.g. `eval \"document.querySelector(...).click()\"`), \
|
||||||
|
which doesn't depend on refs.\n\
|
||||||
|
(Last resort: AGENT_BROWSER_VERIFY_REF=0 disables this safety check — only \
|
||||||
|
if you accept clicks may land on a re-rendered/wrong node.)",
|
||||||
ref_id, expected_role, expected_name, actual_role, actual_name,
|
ref_id, expected_role, expected_name, actual_role, actual_name,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1305,6 +1305,39 @@ fn render_tree(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// True if a snapshot line names an interactive ARIA role. Compaction keeps
|
||||||
|
/// these even without a `ref=`/`": "` marker, so a clickable control never gets
|
||||||
|
/// dropped from `-c` output (the dogfood reports saw a button present in the full
|
||||||
|
/// snapshot vanish from compact, leaving the agent clicking an empty ref).
|
||||||
|
fn is_interactive_line(line: &str) -> bool {
|
||||||
|
const ROLES: &[&str] = &[
|
||||||
|
"button",
|
||||||
|
"link",
|
||||||
|
"textbox",
|
||||||
|
"checkbox",
|
||||||
|
"radio",
|
||||||
|
"combobox",
|
||||||
|
"listbox",
|
||||||
|
"menuitem",
|
||||||
|
"menuitemcheckbox",
|
||||||
|
"menuitemradio",
|
||||||
|
"option",
|
||||||
|
"switch",
|
||||||
|
"slider",
|
||||||
|
"spinbutton",
|
||||||
|
"searchbox",
|
||||||
|
"tab ",
|
||||||
|
"clickable",
|
||||||
|
"focusable",
|
||||||
|
"editable",
|
||||||
|
];
|
||||||
|
let t = line.trim_start();
|
||||||
|
// Lines look like `- button "Label" [ref=e1]`; match the role token after the
|
||||||
|
// leading "- " marker.
|
||||||
|
let t = t.strip_prefix("- ").unwrap_or(t);
|
||||||
|
ROLES.iter().any(|r| t.starts_with(r))
|
||||||
|
}
|
||||||
|
|
||||||
fn compact_tree(tree: &str, interactive: bool) -> String {
|
fn compact_tree(tree: &str, interactive: bool) -> String {
|
||||||
let lines: Vec<&str> = tree.lines().collect();
|
let lines: Vec<&str> = tree.lines().collect();
|
||||||
if lines.is_empty() {
|
if lines.is_empty() {
|
||||||
@@ -1314,7 +1347,7 @@ fn compact_tree(tree: &str, interactive: bool) -> String {
|
|||||||
let mut keep = vec![false; lines.len()];
|
let mut keep = vec![false; lines.len()];
|
||||||
|
|
||||||
for (i, line) in lines.iter().enumerate() {
|
for (i, line) in lines.iter().enumerate() {
|
||||||
if line.contains("ref=") || line.contains(": ") {
|
if line.contains("ref=") || line.contains(": ") || is_interactive_line(line) {
|
||||||
keep[i] = true;
|
keep[i] = true;
|
||||||
// Mark ancestors
|
// Mark ancestors
|
||||||
let my_indent = count_indent(line);
|
let my_indent = count_indent(line);
|
||||||
|
|||||||
Reference in New Issue
Block a user