diff --git a/.changeset/true-bees-sniff.md b/.changeset/true-bees-sniff.md new file mode 100644 index 00000000..df4e6846 --- /dev/null +++ b/.changeset/true-bees-sniff.md @@ -0,0 +1,5 @@ +--- +"@browserbasehq/stagehand": patch +--- + +handle namespaced elements in xpath build step diff --git a/evals/evals.config.json b/evals/evals.config.json index 06ed6459..9d3c7ccb 100644 --- a/evals/evals.config.json +++ b/evals/evals.config.json @@ -418,6 +418,10 @@ { "name": "agent/sign_in", "categories": ["agent"] + }, + { + "name": "namespace_xpath", + "categories": ["act"] } ] } diff --git a/evals/tasks/namespace_xpath.ts b/evals/tasks/namespace_xpath.ts new file mode 100644 index 00000000..fe12f500 --- /dev/null +++ b/evals/tasks/namespace_xpath.ts @@ -0,0 +1,39 @@ +import { EvalFunction } from "@/types/evals"; + +export const namespace_xpath: EvalFunction = async ({ + debugUrl, + sessionUrl, + stagehand, + logger, +}) => { + try { + await stagehand.page.goto( + "https://browserbase.github.io/stagehand-eval-sites/sites/namespaced-xpath/", + ); + + await stagehand.page.act({ + action: "fill 'nunya' into the 'type here' form", + }); + + const inputValue = await stagehand.page.locator("#ns-text").inputValue(); + // confirm that the form was filled + const formHasBeenFilled = inputValue === "nunya"; + + return { + _success: formHasBeenFilled, + debugUrl, + sessionUrl, + logs: logger.getLogs(), + }; + } catch (error) { + return { + _success: false, + error: error, + debugUrl, + sessionUrl, + logs: logger.getLogs(), + }; + } finally { + await stagehand.close(); + } +}; diff --git a/lib/a11y/utils.ts b/lib/a11y/utils.ts index e603961b..6c77af94 100644 --- a/lib/a11y/utils.ts +++ b/lib/a11y/utils.ts @@ -222,13 +222,18 @@ export async function buildBackendIdMaps( const tag = lc(String(child.nodeName)); const key = `${child.nodeType}:${tag}`; const idx = (ctr[key] = (ctr[key] ?? 0) + 1); - segs.push( - child.nodeType === 3 - ? `text()[${idx}]` - : child.nodeType === 8 - ? `comment()[${idx}]` + if (child.nodeType === 3) { + segs.push(`text()[${idx}]`); + } else if (child.nodeType === 8) { + segs.push(`comment()[${idx}]`); + } else { + // Element node: if qualified (e.g. "as:ajaxinclude"), switch to name()='as:ajaxinclude' + segs.push( + tag.includes(":") + ? `*[name()='${tag}'][${idx}]` : `${tag}[${idx}]`, - ); + ); + } } // push R→L so traversal remains L→R for (let i = kids.length - 1; i >= 0; i--) {