Skip to content

Commit 890ffcc

Browse files
prevent crashing on shadow DOM elements (#857)
# why - when the LLM returns an ID, we look up that ID in our ID->xpath mapping. If that element does not exist, it returns undefined. downstream, we attempt to run some regex on the xpath - when running the regex on the undefined xpath, we get the following error: `TypeError: Cannot read properties of undefined (reading 'replace')` - the actual fix for this is to add support for taking actions on elements within the shadow dom - for now, we should have better error handling for this case # what changed - added a check in the `ObserveHandler` to check if the returned ID contains `-`, which indicates that it will be present in our `idToXpath` mapping - if it does not contain `-`, then we log an error, and return `"not-supported"` as the selector, and `"an element inside a shadow DOM"` as the description # test plan - added an eval to ensure that we are returning `"not-supported"` as the selector, and `"an element inside a shadow DOM"` for elements that exist inside the shadow dom --------- Co-authored-by: Sean McGuire <[email protected]>
1 parent a5a0107 commit 890ffcc

File tree

4 files changed

+79
-15
lines changed

4 files changed

+79
-15
lines changed

.changeset/evil-snakes-juggle.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+
return "not-supported" for elements inside the shadow-dom

evals/evals.config.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,10 @@
366366
{
367367
"name": "no_js_click",
368368
"categories": ["act", "regression"]
369+
},
370+
{
371+
"name": "shadow_dom",
372+
"categories": ["act"]
369373
}
370374
]
371375
}

evals/tasks/shadow_dom.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { EvalFunction } from "@/types/evals";
2+
3+
export const shadow_dom: EvalFunction = async ({
4+
debugUrl,
5+
sessionUrl,
6+
stagehand,
7+
logger,
8+
}) => {
9+
const page = stagehand.page;
10+
try {
11+
await page.goto(
12+
"https://browserbase.github.io/stagehand-eval-sites/sites/shadow-dom/",
13+
);
14+
const result = await page.act("click the button");
15+
16+
if (!result.success && result.message.includes("not-supported")) {
17+
return {
18+
_success: true,
19+
debugUrl,
20+
sessionUrl,
21+
logs: logger.getLogs(),
22+
};
23+
}
24+
return {
25+
_success: false,
26+
debugUrl,
27+
sessionUrl,
28+
logs: logger.getLogs(),
29+
};
30+
} catch (error) {
31+
return {
32+
_success: false,
33+
message: `error: ${error.message}`,
34+
debugUrl,
35+
sessionUrl,
36+
logs: logger.getLogs(),
37+
};
38+
} finally {
39+
await stagehand.close();
40+
}
41+
};

lib/handlers/observeHandler.ts

Lines changed: 29 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -168,25 +168,39 @@ export class StagehandObserveHandler {
168168
},
169169
});
170170

171-
const lookUpIndex = elementId as EncodedId;
172-
const xpath = combinedXpathMap[lookUpIndex];
173-
174-
const trimmedXpath = trimTrailingTextNode(xpath);
175-
176-
if (!trimmedXpath || trimmedXpath === "") {
171+
if (elementId.includes("-")) {
172+
const lookUpIndex = elementId as EncodedId;
173+
const xpath = combinedXpathMap[lookUpIndex];
174+
175+
const trimmedXpath = trimTrailingTextNode(xpath);
176+
177+
if (!trimmedXpath || trimmedXpath === "") {
178+
this.logger({
179+
category: "observation",
180+
message: `Empty xpath returned for element: ${elementId}`,
181+
level: 1,
182+
});
183+
}
184+
185+
return {
186+
...rest,
187+
selector: `xpath=${trimmedXpath}`,
188+
// Provisioning or future use if we want to use direct CDP
189+
// backendNodeId: elementId,
190+
};
191+
} else {
177192
this.logger({
178193
category: "observation",
179-
message: `Empty xpath returned for element: ${elementId}`,
180-
level: 1,
194+
message: `Element is inside a shadow DOM: ${elementId}`,
195+
level: 0,
181196
});
197+
return {
198+
description: "an element inside a shadow DOM",
199+
method: "not-supported",
200+
arguments: [] as string[],
201+
selector: "not-supported",
202+
};
182203
}
183-
184-
return {
185-
...rest,
186-
selector: `xpath=${trimmedXpath}`,
187-
// Provisioning or future use if we want to use direct CDP
188-
// backendNodeId: elementId,
189-
};
190204
}),
191205
);
192206

0 commit comments

Comments
 (0)