Skip to content

Commit b86df93

Browse files
filter out elements that don't have an associated xpath (#882)
# why - if we do not find an xpath for an element, the element is not actionable. therefore, it should not be included in the `ObserveResult` array # what changed - added a filter step in `observeHandler.ts` to remove elements that have an undefined xpath # test plan - `observe` evals - `act` evals - `regression` evals
1 parent 98704c9 commit b86df93

File tree

2 files changed

+60
-46
lines changed

2 files changed

+60
-46
lines changed

.changeset/cold-pumas-accept.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+
remove elements that don't have xpaths from observe response

lib/handlers/observeHandler.ts

Lines changed: 55 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -151,58 +151,67 @@ export class StagehandObserveHandler {
151151
});
152152
}
153153

154-
const elementsWithSelectors = await Promise.all(
155-
observationResponse.elements.map(async (element) => {
156-
const { elementId, ...rest } = element;
157-
158-
// Generate xpath for the given element if not found in selectorMap
159-
this.logger({
160-
category: "observation",
161-
message: "Getting xpath for element",
162-
level: 1,
163-
auxiliary: {
164-
elementId: {
165-
value: elementId.toString(),
166-
type: "string",
167-
},
168-
},
169-
});
170-
171-
if (elementId.includes("-")) {
172-
const lookUpIndex = elementId as EncodedId;
173-
const xpath: string | undefined = combinedXpathMap[lookUpIndex];
154+
const elementsWithSelectors = (
155+
await Promise.all(
156+
observationResponse.elements.map(async (element) => {
157+
const { elementId, ...rest } = element;
174158

175-
const trimmedXpath = trimTrailingTextNode(xpath);
159+
// Generate xpath for the given element if not found in selectorMap
160+
this.logger({
161+
category: "observation",
162+
message: "Getting xpath for element",
163+
level: 1,
164+
auxiliary: {
165+
elementId: {
166+
value: elementId.toString(),
167+
type: "string",
168+
},
169+
},
170+
});
176171

177-
if (!trimmedXpath || trimmedXpath === "") {
172+
if (elementId.includes("-")) {
173+
const lookUpIndex = elementId as EncodedId;
174+
const xpath: string | undefined = combinedXpathMap[lookUpIndex];
175+
176+
const trimmedXpath = trimTrailingTextNode(xpath);
177+
178+
if (!trimmedXpath || trimmedXpath === "") {
179+
this.logger({
180+
category: "observation",
181+
message: `Empty xpath returned for element`,
182+
auxiliary: {
183+
observeResult: {
184+
value: JSON.stringify(element),
185+
type: "object",
186+
},
187+
},
188+
level: 1,
189+
});
190+
return undefined;
191+
}
192+
193+
return {
194+
...rest,
195+
selector: `xpath=${trimmedXpath}`,
196+
// Provisioning or future use if we want to use direct CDP
197+
// backendNodeId: elementId,
198+
};
199+
} else {
178200
this.logger({
179201
category: "observation",
180-
message: `Empty xpath returned for element: ${elementId}`,
181-
level: 1,
202+
message: `Element is inside a shadow DOM: ${elementId}`,
203+
level: 0,
182204
});
205+
return {
206+
description: "an element inside a shadow DOM",
207+
method: "not-supported",
208+
arguments: [] as string[],
209+
selector: "not-supported",
210+
};
183211
}
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 {
192-
this.logger({
193-
category: "observation",
194-
message: `Element is inside a shadow DOM: ${elementId}`,
195-
level: 0,
196-
});
197-
return {
198-
description: "an element inside a shadow DOM",
199-
method: "not-supported",
200-
arguments: [] as string[],
201-
selector: "not-supported",
202-
};
203-
}
204-
}),
205-
);
212+
}),
213+
)
214+
).filter(<T>(e: T | undefined): e is T => e !== undefined);
206215

207216
this.logger({
208217
category: "observation",

0 commit comments

Comments
 (0)