Skip to content

Commit 72d2683

Browse files
generate xpaths for namespaced elements (#962)
1 parent 0ead63d commit 72d2683

File tree

4 files changed

+59
-6
lines changed

4 files changed

+59
-6
lines changed

.changeset/true-bees-sniff.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@browserbasehq/stagehand": patch
3+
---
4+
5+
handle namespaced elements in xpath build step

evals/evals.config.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,10 @@
418418
{
419419
"name": "agent/sign_in",
420420
"categories": ["agent"]
421+
},
422+
{
423+
"name": "namespace_xpath",
424+
"categories": ["act"]
421425
}
422426
]
423427
}

evals/tasks/namespace_xpath.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { EvalFunction } from "@/types/evals";
2+
3+
export const namespace_xpath: EvalFunction = async ({
4+
debugUrl,
5+
sessionUrl,
6+
stagehand,
7+
logger,
8+
}) => {
9+
try {
10+
await stagehand.page.goto(
11+
"https://browserbase.github.io/stagehand-eval-sites/sites/namespaced-xpath/",
12+
);
13+
14+
await stagehand.page.act({
15+
action: "fill 'nunya' into the 'type here' form",
16+
});
17+
18+
const inputValue = await stagehand.page.locator("#ns-text").inputValue();
19+
// confirm that the form was filled
20+
const formHasBeenFilled = inputValue === "nunya";
21+
22+
return {
23+
_success: formHasBeenFilled,
24+
debugUrl,
25+
sessionUrl,
26+
logs: logger.getLogs(),
27+
};
28+
} catch (error) {
29+
return {
30+
_success: false,
31+
error: error,
32+
debugUrl,
33+
sessionUrl,
34+
logs: logger.getLogs(),
35+
};
36+
} finally {
37+
await stagehand.close();
38+
}
39+
};

lib/a11y/utils.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -222,13 +222,18 @@ export async function buildBackendIdMaps(
222222
const tag = lc(String(child.nodeName));
223223
const key = `${child.nodeType}:${tag}`;
224224
const idx = (ctr[key] = (ctr[key] ?? 0) + 1);
225-
segs.push(
226-
child.nodeType === 3
227-
? `text()[${idx}]`
228-
: child.nodeType === 8
229-
? `comment()[${idx}]`
225+
if (child.nodeType === 3) {
226+
segs.push(`text()[${idx}]`);
227+
} else if (child.nodeType === 8) {
228+
segs.push(`comment()[${idx}]`);
229+
} else {
230+
// Element node: if qualified (e.g. "as:ajaxinclude"), switch to name()='as:ajaxinclude'
231+
segs.push(
232+
tag.includes(":")
233+
? `*[name()='${tag}'][${idx}]`
230234
: `${tag}[${idx}]`,
231-
);
235+
);
236+
}
232237
}
233238
// push R→L so traversal remains L→R
234239
for (let i = kids.length - 1; i >= 0; i--) {

0 commit comments

Comments
 (0)