@browserbasehq/stagehand 2.5.3 → 2.5.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +71 -30
- package/dist/index.js +152 -81
- package/dist/lib/StagehandContext.d.ts +1 -0
- package/dist/lib/StagehandPage.d.ts +0 -1
- package/dist/lib/a11y/utils.d.ts +3 -19
- package/dist/lib/dom/build/scriptContent.d.ts +1 -1
- package/dist/lib/dom/index.d.ts +1 -2
- package/dist/lib/dom/process.d.ts +0 -17
- package/dist/lib/index.d.ts +3 -0
- package/dist/lib/version.d.ts +1 -1
- package/dist/types/context.d.ts +4 -0
- package/dist/types/model.d.ts +1 -1
- package/dist/types/stagehandErrors.d.ts +1 -0
- package/package.json +25 -27
- package/dist/evals/cli.js +0 -965
- package/dist/evals/evals.config.json +0 -553
- package/dist/examples/accessibility_tests.d.ts +0 -16
- package/dist/examples/download.d.ts +0 -3
- package/dist/examples/evaluator.d.ts +0 -1
- package/dist/examples/mem_test.d.ts +0 -1
- package/dist/examples/multi_page.d.ts +0 -1
- package/dist/examples/perf_test.d.ts +0 -1
- package/dist/examples/pwtest.d.ts +0 -1
- package/dist/examples/test.d.ts +0 -1
- package/dist/lib/dom/elementCheckUtils.d.ts +0 -2
- package/dist/lib/dom/utils.d.ts +0 -7
- package/dist/lib/dom/xpathUtils.d.ts +0 -14
package/dist/index.js
CHANGED
|
@@ -473,6 +473,7 @@ __export(index_exports, {
|
|
|
473
473
|
ZodSchemaValidationError: () => ZodSchemaValidationError,
|
|
474
474
|
connectToMCPServer: () => connectToMCPServer,
|
|
475
475
|
defaultExtractSchema: () => defaultExtractSchema,
|
|
476
|
+
getAccessibilityTree: () => getAccessibilityTree,
|
|
476
477
|
pageTextSchema: () => pageTextSchema
|
|
477
478
|
});
|
|
478
479
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -493,7 +494,7 @@ var StagehandFunctionName = /* @__PURE__ */ ((StagehandFunctionName2) => {
|
|
|
493
494
|
})(StagehandFunctionName || {});
|
|
494
495
|
|
|
495
496
|
// lib/version.ts
|
|
496
|
-
var STAGEHAND_VERSION = "2.5.
|
|
497
|
+
var STAGEHAND_VERSION = "2.5.5";
|
|
497
498
|
|
|
498
499
|
// types/stagehandErrors.ts
|
|
499
500
|
var StagehandError = class extends Error {
|
|
@@ -514,6 +515,7 @@ If you need help, please open a Github issue or reach out to us on Slack: https:
|
|
|
514
515
|
Full error:
|
|
515
516
|
${error.message}`
|
|
516
517
|
);
|
|
518
|
+
this.causedBy = error;
|
|
517
519
|
}
|
|
518
520
|
}
|
|
519
521
|
};
|
|
@@ -2607,6 +2609,115 @@ var PUA_START = 57344;
|
|
|
2607
2609
|
var PUA_END = 63743;
|
|
2608
2610
|
var NBSP_CHARS = /* @__PURE__ */ new Set([160, 8239, 8199, 65279]);
|
|
2609
2611
|
var WORLD_NAME = "stagehand-world";
|
|
2612
|
+
var DOM_DEPTH_ATTEMPTS = [-1, 256, 128, 64, 32, 16, 8, 4, 2, 1];
|
|
2613
|
+
var DESCRIBE_DEPTH_ATTEMPTS = [-1, 64, 32, 16, 8, 4, 2, 1];
|
|
2614
|
+
function isCborStackError(message) {
|
|
2615
|
+
return message.includes("CBOR: stack limit exceeded");
|
|
2616
|
+
}
|
|
2617
|
+
function shouldExpandNode(node) {
|
|
2618
|
+
var _a15, _b, _c;
|
|
2619
|
+
const declaredChildren = (_a15 = node.childNodeCount) != null ? _a15 : 0;
|
|
2620
|
+
const realizedChildren = (_c = (_b = node.children) == null ? void 0 : _b.length) != null ? _c : 0;
|
|
2621
|
+
return declaredChildren > realizedChildren;
|
|
2622
|
+
}
|
|
2623
|
+
function mergeDomNodes(target, source) {
|
|
2624
|
+
var _a15, _b, _c, _d;
|
|
2625
|
+
target.childNodeCount = (_a15 = source.childNodeCount) != null ? _a15 : target.childNodeCount;
|
|
2626
|
+
target.children = (_b = source.children) != null ? _b : target.children;
|
|
2627
|
+
target.shadowRoots = (_c = source.shadowRoots) != null ? _c : target.shadowRoots;
|
|
2628
|
+
target.contentDocument = (_d = source.contentDocument) != null ? _d : target.contentDocument;
|
|
2629
|
+
}
|
|
2630
|
+
function collectDomTraversalTargets(node) {
|
|
2631
|
+
const targets = [];
|
|
2632
|
+
if (node.children) targets.push(...node.children);
|
|
2633
|
+
if (node.shadowRoots) targets.push(...node.shadowRoots);
|
|
2634
|
+
if (node.contentDocument) targets.push(node.contentDocument);
|
|
2635
|
+
return targets;
|
|
2636
|
+
}
|
|
2637
|
+
function hydrateDomTree(session, root) {
|
|
2638
|
+
return __async(this, null, function* () {
|
|
2639
|
+
var _a15, _b;
|
|
2640
|
+
const stack = [root];
|
|
2641
|
+
const expandedNodeIds = /* @__PURE__ */ new Set();
|
|
2642
|
+
const expandedBackendIds = /* @__PURE__ */ new Set();
|
|
2643
|
+
let describeCalls = 0;
|
|
2644
|
+
while (stack.length) {
|
|
2645
|
+
const node = stack.pop();
|
|
2646
|
+
const nodeId = typeof node.nodeId === "number" && node.nodeId > 0 ? node.nodeId : void 0;
|
|
2647
|
+
const backendId = typeof node.backendNodeId === "number" && node.backendNodeId > 0 ? node.backendNodeId : void 0;
|
|
2648
|
+
const seenByNode = nodeId ? expandedNodeIds.has(nodeId) : false;
|
|
2649
|
+
const seenByBackend = !nodeId && backendId ? expandedBackendIds.has(backendId) : false;
|
|
2650
|
+
if (seenByNode || seenByBackend) continue;
|
|
2651
|
+
if (nodeId) expandedNodeIds.add(nodeId);
|
|
2652
|
+
else if (backendId) expandedBackendIds.add(backendId);
|
|
2653
|
+
const needsExpansion = shouldExpandNode(node);
|
|
2654
|
+
if (needsExpansion && (nodeId || backendId)) {
|
|
2655
|
+
const describeParamsBase = nodeId ? { nodeId } : { backendNodeId: backendId };
|
|
2656
|
+
let expanded = false;
|
|
2657
|
+
for (const depth of DESCRIBE_DEPTH_ATTEMPTS) {
|
|
2658
|
+
try {
|
|
2659
|
+
const described = yield session.send("DOM.describeNode", __spreadProps(__spreadValues({}, describeParamsBase), {
|
|
2660
|
+
depth,
|
|
2661
|
+
pierce: true
|
|
2662
|
+
}));
|
|
2663
|
+
mergeDomNodes(node, described.node);
|
|
2664
|
+
if (!nodeId && described.node.nodeId && described.node.nodeId > 0) {
|
|
2665
|
+
node.nodeId = described.node.nodeId;
|
|
2666
|
+
expandedNodeIds.add(described.node.nodeId);
|
|
2667
|
+
}
|
|
2668
|
+
describeCalls++;
|
|
2669
|
+
expanded = true;
|
|
2670
|
+
break;
|
|
2671
|
+
} catch (err) {
|
|
2672
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2673
|
+
if (isCborStackError(message)) {
|
|
2674
|
+
continue;
|
|
2675
|
+
}
|
|
2676
|
+
const identifier = (_a15 = nodeId != null ? nodeId : backendId) != null ? _a15 : "unknown";
|
|
2677
|
+
throw new StagehandDomProcessError(
|
|
2678
|
+
`Failed to expand DOM node ${identifier}: ${String(err)}`
|
|
2679
|
+
);
|
|
2680
|
+
}
|
|
2681
|
+
}
|
|
2682
|
+
if (!expanded) {
|
|
2683
|
+
const identifier = (_b = nodeId != null ? nodeId : backendId) != null ? _b : "unknown";
|
|
2684
|
+
throw new StagehandDomProcessError(
|
|
2685
|
+
`Unable to expand DOM node ${identifier} after describeNode depth retries`
|
|
2686
|
+
);
|
|
2687
|
+
}
|
|
2688
|
+
}
|
|
2689
|
+
for (const child of collectDomTraversalTargets(node)) {
|
|
2690
|
+
stack.push(child);
|
|
2691
|
+
}
|
|
2692
|
+
}
|
|
2693
|
+
return describeCalls;
|
|
2694
|
+
});
|
|
2695
|
+
}
|
|
2696
|
+
function getDomTreeWithFallback(session) {
|
|
2697
|
+
return __async(this, null, function* () {
|
|
2698
|
+
let lastCborMessage = "";
|
|
2699
|
+
for (const depth of DOM_DEPTH_ATTEMPTS) {
|
|
2700
|
+
try {
|
|
2701
|
+
const params = { depth, pierce: true };
|
|
2702
|
+
const { root } = yield session.send("DOM.getDocument", params);
|
|
2703
|
+
if (depth !== -1) {
|
|
2704
|
+
yield hydrateDomTree(session, root);
|
|
2705
|
+
}
|
|
2706
|
+
return root;
|
|
2707
|
+
} catch (err) {
|
|
2708
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2709
|
+
if (isCborStackError(message)) {
|
|
2710
|
+
lastCborMessage = message;
|
|
2711
|
+
continue;
|
|
2712
|
+
}
|
|
2713
|
+
throw err;
|
|
2714
|
+
}
|
|
2715
|
+
}
|
|
2716
|
+
throw new StagehandDomProcessError(
|
|
2717
|
+
lastCborMessage ? `CDP DOM.getDocument failed after adaptive depth retries: ${lastCborMessage}` : "CDP DOM.getDocument failed after adaptive depth retries."
|
|
2718
|
+
);
|
|
2719
|
+
});
|
|
2720
|
+
}
|
|
2610
2721
|
function cleanText(input) {
|
|
2611
2722
|
let out = "";
|
|
2612
2723
|
let prevWasSpace = false;
|
|
@@ -2664,10 +2775,7 @@ function buildBackendIdMaps(experimental, sp, targetFrame) {
|
|
|
2664
2775
|
session === (yield sp.getCDPClient()) ? void 0 : targetFrame
|
|
2665
2776
|
);
|
|
2666
2777
|
try {
|
|
2667
|
-
const
|
|
2668
|
-
depth: -1,
|
|
2669
|
-
pierce: true
|
|
2670
|
-
});
|
|
2778
|
+
const root = yield getDomTreeWithFallback(session);
|
|
2671
2779
|
let startNode = root;
|
|
2672
2780
|
let rootFid = targetFrame && (yield getCDPFrameId(sp, targetFrame));
|
|
2673
2781
|
if (targetFrame && targetFrame !== sp.page.mainFrame() && session === (yield sp.getCDPClient())) {
|
|
@@ -2689,8 +2797,10 @@ function buildBackendIdMaps(experimental, sp, targetFrame) {
|
|
|
2689
2797
|
if (n.contentDocument && locate(n.contentDocument)) return true;
|
|
2690
2798
|
return false;
|
|
2691
2799
|
} else {
|
|
2692
|
-
if (n.backendNodeId === backendNodeId)
|
|
2693
|
-
|
|
2800
|
+
if (n.backendNodeId === backendNodeId) {
|
|
2801
|
+
iframeNode = n;
|
|
2802
|
+
return true;
|
|
2803
|
+
}
|
|
2694
2804
|
return ((_d2 = (_c2 = n.children) == null ? void 0 : _c2.some(locate)) != null ? _d2 : false) || (n.contentDocument ? locate(n.contentDocument) : false);
|
|
2695
2805
|
}
|
|
2696
2806
|
};
|
|
@@ -2702,17 +2812,25 @@ function buildBackendIdMaps(experimental, sp, targetFrame) {
|
|
|
2702
2812
|
}
|
|
2703
2813
|
const tagNameMap = {};
|
|
2704
2814
|
const xpathMap = {};
|
|
2815
|
+
const scrollableBackendIds = /* @__PURE__ */ new Set();
|
|
2705
2816
|
const stack = [{ node: startNode, path: "", fid: rootFid }];
|
|
2706
2817
|
const seen = /* @__PURE__ */ new Set();
|
|
2707
2818
|
const joinStep = (base, step) => base.endsWith("//") ? `${base}${step}` : `${base}/${step}`;
|
|
2708
2819
|
while (stack.length) {
|
|
2709
2820
|
const { node, path: path4, fid } = stack.pop();
|
|
2710
2821
|
if (!node.backendNodeId) continue;
|
|
2822
|
+
const backendId = node.backendNodeId;
|
|
2711
2823
|
const enc = sp.encodeWithFrameId(fid, node.backendNodeId);
|
|
2712
2824
|
if (seen.has(enc)) continue;
|
|
2713
2825
|
seen.add(enc);
|
|
2714
2826
|
tagNameMap[enc] = lc(String(node.nodeName));
|
|
2715
2827
|
xpathMap[enc] = path4;
|
|
2828
|
+
if (node.isScrollable === true) {
|
|
2829
|
+
scrollableBackendIds.add(backendId);
|
|
2830
|
+
}
|
|
2831
|
+
if (lc(String(node.nodeName)) === "html") {
|
|
2832
|
+
scrollableBackendIds.add(backendId);
|
|
2833
|
+
}
|
|
2716
2834
|
if (lc(node.nodeName) === "iframe" && node.contentDocument) {
|
|
2717
2835
|
const childFid = (_b = node.contentDocument.frameId) != null ? _b : fid;
|
|
2718
2836
|
stack.push({ node: node.contentDocument, path: "", fid: childFid });
|
|
@@ -2753,7 +2871,7 @@ function buildBackendIdMaps(experimental, sp, targetFrame) {
|
|
|
2753
2871
|
}
|
|
2754
2872
|
}
|
|
2755
2873
|
}
|
|
2756
|
-
return { tagNameMap, xpathMap };
|
|
2874
|
+
return { tagNameMap, xpathMap, scrollableBackendIds };
|
|
2757
2875
|
} finally {
|
|
2758
2876
|
yield sp.disableCDP(
|
|
2759
2877
|
"DOM",
|
|
@@ -2885,11 +3003,7 @@ function getCDPFrameId(sp, frame) {
|
|
|
2885
3003
|
}
|
|
2886
3004
|
function getAccessibilityTree(experimental, stagehandPage, logger, selector, targetFrame) {
|
|
2887
3005
|
return __async(this, null, function* () {
|
|
2888
|
-
const { tagNameMap, xpathMap } = yield buildBackendIdMaps(
|
|
2889
|
-
experimental,
|
|
2890
|
-
stagehandPage,
|
|
2891
|
-
targetFrame
|
|
2892
|
-
);
|
|
3006
|
+
const { tagNameMap, xpathMap, scrollableBackendIds } = yield buildBackendIdMaps(experimental, stagehandPage, targetFrame);
|
|
2893
3007
|
yield stagehandPage.enableCDP("Accessibility", targetFrame);
|
|
2894
3008
|
try {
|
|
2895
3009
|
let params = {};
|
|
@@ -2916,10 +3030,6 @@ function getAccessibilityTree(experimental, stagehandPage, logger, selector, tar
|
|
|
2916
3030
|
}
|
|
2917
3031
|
}
|
|
2918
3032
|
const { nodes: fullNodes } = yield stagehandPage.sendCDP("Accessibility.getFullAXTree", params, sessionFrame);
|
|
2919
|
-
const scrollableIds = yield findScrollableElementIds(
|
|
2920
|
-
stagehandPage,
|
|
2921
|
-
targetFrame
|
|
2922
|
-
);
|
|
2923
3033
|
let nodes = fullNodes;
|
|
2924
3034
|
if (selector) {
|
|
2925
3035
|
nodes = yield filterAXTreeByXPath(
|
|
@@ -2931,7 +3041,7 @@ function getAccessibilityTree(experimental, stagehandPage, logger, selector, tar
|
|
|
2931
3041
|
}
|
|
2932
3042
|
const start = Date.now();
|
|
2933
3043
|
const tree = yield buildHierarchicalTree(
|
|
2934
|
-
decorateRoles(nodes,
|
|
3044
|
+
decorateRoles(nodes, scrollableBackendIds),
|
|
2935
3045
|
tagNameMap,
|
|
2936
3046
|
logger,
|
|
2937
3047
|
xpathMap
|
|
@@ -2980,16 +3090,16 @@ function filterAXTreeByXPath(page, full, xpath, targetFrame) {
|
|
|
2980
3090
|
}
|
|
2981
3091
|
function decorateRoles(nodes, scrollables) {
|
|
2982
3092
|
return nodes.map((n) => {
|
|
2983
|
-
var _a15, _b, _c, _d, _e;
|
|
3093
|
+
var _a15, _b, _c, _d, _e, _f;
|
|
2984
3094
|
let role = (_b = (_a15 = n.role) == null ? void 0 : _a15.value) != null ? _b : "";
|
|
2985
|
-
if (scrollables.has(n.backendDOMNodeId)) {
|
|
3095
|
+
if (((_c = n.role) == null ? void 0 : _c.value) !== "RootWebArea" && scrollables.has(n.backendDOMNodeId)) {
|
|
2986
3096
|
role = role && role !== "generic" && role !== "none" ? `scrollable, ${role}` : "scrollable";
|
|
2987
3097
|
}
|
|
2988
3098
|
return {
|
|
2989
3099
|
role,
|
|
2990
|
-
name: (
|
|
2991
|
-
description: (
|
|
2992
|
-
value: (
|
|
3100
|
+
name: (_d = n.name) == null ? void 0 : _d.value,
|
|
3101
|
+
description: (_e = n.description) == null ? void 0 : _e.value,
|
|
3102
|
+
value: (_f = n.value) == null ? void 0 : _f.value,
|
|
2993
3103
|
nodeId: n.nodeId,
|
|
2994
3104
|
backendDOMNodeId: n.backendDOMNodeId,
|
|
2995
3105
|
parentId: n.parentId,
|
|
@@ -3219,27 +3329,6 @@ function getAccessibilityTreeWithFrames(experimental, stagehandPage, logger, roo
|
|
|
3219
3329
|
return { combinedTree, combinedXpathMap, combinedUrlMap };
|
|
3220
3330
|
});
|
|
3221
3331
|
}
|
|
3222
|
-
function findScrollableElementIds(stagehandPage, targetFrame) {
|
|
3223
|
-
return __async(this, null, function* () {
|
|
3224
|
-
const xpaths = targetFrame ? yield targetFrame.evaluate(() => window.getScrollableElementXpaths()) : yield stagehandPage.page.evaluate(
|
|
3225
|
-
() => window.getScrollableElementXpaths()
|
|
3226
|
-
);
|
|
3227
|
-
const backendIds = /* @__PURE__ */ new Set();
|
|
3228
|
-
for (const xpath of xpaths) {
|
|
3229
|
-
if (!xpath) continue;
|
|
3230
|
-
const objectId = yield resolveObjectIdForXPath(
|
|
3231
|
-
stagehandPage,
|
|
3232
|
-
xpath,
|
|
3233
|
-
targetFrame
|
|
3234
|
-
);
|
|
3235
|
-
if (objectId) {
|
|
3236
|
-
const { node } = yield stagehandPage.sendCDP("DOM.describeNode", { objectId }, targetFrame);
|
|
3237
|
-
if (node == null ? void 0 : node.backendNodeId) backendIds.add(node.backendNodeId);
|
|
3238
|
-
}
|
|
3239
|
-
}
|
|
3240
|
-
return backendIds;
|
|
3241
|
-
});
|
|
3242
|
-
}
|
|
3243
3332
|
function resolveObjectIdForXPath(page, xpath, targetFrame) {
|
|
3244
3333
|
return __async(this, null, function* () {
|
|
3245
3334
|
const contextId = yield getFrameExecutionContextId(page, targetFrame);
|
|
@@ -3788,9 +3877,6 @@ var StagehandResponseParseError = class extends StagehandAPIError {
|
|
|
3788
3877
|
}
|
|
3789
3878
|
};
|
|
3790
3879
|
|
|
3791
|
-
// lib/dom/build/scriptContent.ts
|
|
3792
|
-
var scriptContent = '(() => {\n // lib/dom/elementCheckUtils.ts\n function isElementNode(node) {\n return node.nodeType === Node.ELEMENT_NODE;\n }\n function isTextNode(node) {\n return node.nodeType === Node.TEXT_NODE && Boolean(node.textContent?.trim());\n }\n\n // lib/dom/xpathUtils.ts\n function getParentElement(node) {\n return isElementNode(node) ? node.parentElement : node.parentNode;\n }\n function getCombinations(attributes, size) {\n const results = [];\n function helper(start, combo) {\n if (combo.length === size) {\n results.push([...combo]);\n return;\n }\n for (let i = start; i < attributes.length; i++) {\n combo.push(attributes[i]);\n helper(i + 1, combo);\n combo.pop();\n }\n }\n helper(0, []);\n return results;\n }\n function isXPathFirstResultElement(xpath, target) {\n try {\n const result = document.evaluate(\n xpath,\n document.documentElement,\n null,\n XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,\n null\n );\n return result.snapshotItem(0) === target;\n } catch (error) {\n console.warn(`Invalid XPath expression: ${xpath}`, error);\n return false;\n }\n }\n function escapeXPathString(value) {\n if (value.includes("\'")) {\n if (value.includes(\'"\')) {\n return "concat(" + value.split(/(\'+)/).map((part) => {\n if (part === "\'") {\n return `"\'"`;\n } else if (part.startsWith("\'") && part.endsWith("\'")) {\n return `"${part}"`;\n } else {\n return `\'${part}\'`;\n }\n }).join(",") + ")";\n } else {\n return `"${value}"`;\n }\n } else {\n return `\'${value}\'`;\n }\n }\n async function generateXPathsForElement(element) {\n if (!element) return [];\n const [complexXPath, standardXPath, idBasedXPath] = await Promise.all([\n generateComplexXPath(element),\n generateStandardXPath(element),\n generatedIdBasedXPath(element)\n ]);\n return [standardXPath, ...idBasedXPath ? [idBasedXPath] : [], complexXPath];\n }\n async function generateComplexXPath(element) {\n const parts = [];\n let currentElement = element;\n while (currentElement && (isTextNode(currentElement) || isElementNode(currentElement))) {\n if (isElementNode(currentElement)) {\n const el = currentElement;\n let selector = el.tagName.toLowerCase();\n const attributePriority = [\n "data-qa",\n "data-component",\n "data-role",\n "role",\n "aria-role",\n "type",\n "name",\n "aria-label",\n "placeholder",\n "title",\n "alt"\n ];\n const attributes = attributePriority.map((attr) => {\n let value = el.getAttribute(attr);\n if (attr === "href-full" && value) {\n value = el.getAttribute("href");\n }\n return value ? { attr: attr === "href-full" ? "href" : attr, value } : null;\n }).filter((attr) => attr !== null);\n let uniqueSelector = "";\n for (let i = 1; i <= attributes.length; i++) {\n const combinations = getCombinations(attributes, i);\n for (const combo of combinations) {\n const conditions = combo.map((a) => `@${a.attr}=${escapeXPathString(a.value)}`).join(" and ");\n const xpath2 = `//${selector}[${conditions}]`;\n if (isXPathFirstResultElement(xpath2, el)) {\n uniqueSelector = xpath2;\n break;\n }\n }\n if (uniqueSelector) break;\n }\n if (uniqueSelector) {\n parts.unshift(uniqueSelector.replace("//", ""));\n break;\n } else {\n const parent = getParentElement(el);\n if (parent) {\n const siblings = Array.from(parent.children).filter(\n (sibling) => sibling.tagName === el.tagName\n );\n const index = siblings.indexOf(el) + 1;\n selector += siblings.length > 1 ? `[${index}]` : "";\n }\n parts.unshift(selector);\n }\n }\n currentElement = getParentElement(currentElement);\n }\n const xpath = "//" + parts.join("/");\n return xpath;\n }\n async function generateStandardXPath(element) {\n const parts = [];\n while (element && (isTextNode(element) || isElementNode(element))) {\n let index = 0;\n let hasSameTypeSiblings = false;\n const siblings = element.parentElement ? Array.from(element.parentElement.childNodes) : [];\n for (let i = 0; i < siblings.length; i++) {\n const sibling = siblings[i];\n if (sibling.nodeType === element.nodeType && sibling.nodeName === element.nodeName) {\n index = index + 1;\n hasSameTypeSiblings = true;\n if (sibling.isSameNode(element)) {\n break;\n }\n }\n }\n if (element.nodeName !== "#text") {\n const tagName = element.nodeName.toLowerCase();\n const pathIndex = hasSameTypeSiblings ? `[${index}]` : "";\n parts.unshift(`${tagName}${pathIndex}`);\n }\n element = element.parentElement;\n }\n return parts.length ? `/${parts.join("/")}` : "";\n }\n async function generatedIdBasedXPath(element) {\n if (isElementNode(element) && element.id) {\n return `//*[@id=\'${element.id}\']`;\n }\n return null;\n }\n\n // types/stagehandErrors.ts\n var StagehandError = class extends Error {\n constructor(message) {\n super(message);\n this.name = this.constructor.name;\n }\n };\n var StagehandDomProcessError = class extends StagehandError {\n constructor(message) {\n super(`Error Processing Dom: ${message}`);\n }\n };\n\n // lib/dom/utils.ts\n function canElementScroll(elem) {\n if (typeof elem.scrollTo !== "function") {\n console.warn("canElementScroll: .scrollTo is not a function.");\n return false;\n }\n try {\n const originalTop = elem.scrollTop;\n elem.scrollTo({\n top: originalTop + 100,\n left: 0,\n behavior: "instant"\n });\n if (elem.scrollTop === originalTop) {\n throw new StagehandDomProcessError("scrollTop did not change");\n }\n elem.scrollTo({\n top: originalTop,\n left: 0,\n behavior: "instant"\n });\n return true;\n } catch (error) {\n console.warn("canElementScroll error:", error.message || error);\n return false;\n }\n }\n function getNodeFromXpath(xpath) {\n return document.evaluate(\n xpath,\n document.documentElement,\n null,\n XPathResult.FIRST_ORDERED_NODE_TYPE,\n null\n ).singleNodeValue;\n }\n function waitForElementScrollEnd(element, idleMs = 100) {\n return new Promise((resolve) => {\n let scrollEndTimer;\n const handleScroll = () => {\n clearTimeout(scrollEndTimer);\n scrollEndTimer = window.setTimeout(() => {\n element.removeEventListener("scroll", handleScroll);\n resolve();\n }, idleMs);\n };\n element.addEventListener("scroll", handleScroll, { passive: true });\n handleScroll();\n });\n }\n\n // lib/dom/process.ts\n function getScrollableElements(topN) {\n const docEl = document.documentElement;\n const scrollableElements = [docEl];\n const allElements = document.querySelectorAll("*");\n for (const elem of allElements) {\n const style = window.getComputedStyle(elem);\n const overflowY = style.overflowY;\n const isPotentiallyScrollable = overflowY === "auto" || overflowY === "scroll" || overflowY === "overlay";\n if (isPotentiallyScrollable) {\n const candidateScrollDiff = elem.scrollHeight - elem.clientHeight;\n if (candidateScrollDiff > 0 && canElementScroll(elem)) {\n scrollableElements.push(elem);\n }\n }\n }\n scrollableElements.sort((a, b) => b.scrollHeight - a.scrollHeight);\n if (topN !== void 0) {\n return scrollableElements.slice(0, topN);\n }\n return scrollableElements;\n }\n async function getScrollableElementXpaths(topN) {\n const scrollableElems = getScrollableElements(topN);\n const xpaths = [];\n for (const elem of scrollableElems) {\n const allXPaths = await generateXPathsForElement(elem);\n const firstXPath = allXPaths?.[0] || "";\n xpaths.push(firstXPath);\n }\n return xpaths;\n }\n (() => {\n const closedRoots = /* @__PURE__ */ new WeakMap();\n const nativeAttachShadow = Element.prototype.attachShadow;\n Element.prototype.attachShadow = function(init) {\n const root = nativeAttachShadow.call(this, init);\n if (init.mode === "closed") closedRoots.set(this, root);\n return root;\n };\n const backdoor = {\n getClosedRoot: (host) => closedRoots.get(host),\n queryClosed: (host, selector) => {\n const root = closedRoots.get(host);\n return root ? Array.from(root.querySelectorAll(selector)) : [];\n },\n xpathClosed: (host, xp) => {\n const root = closedRoots.get(host);\n if (!root) return [];\n const it = document.evaluate(\n xp,\n root,\n null,\n XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,\n null\n );\n const out = [];\n for (let i = 0; i < it.snapshotLength; ++i) {\n const n = it.snapshotItem(i);\n if (n) out.push(n);\n }\n return out;\n }\n };\n if (!("__stagehand__" in window)) {\n Object.defineProperty(window, "__stagehand__", {\n value: backdoor,\n enumerable: false,\n writable: false,\n configurable: false\n });\n }\n })();\n window.getScrollableElementXpaths = getScrollableElementXpaths;\n window.getNodeFromXpath = getNodeFromXpath;\n window.waitForElementScrollEnd = waitForElementScrollEnd;\n})();\n';
|
|
3793
|
-
|
|
3794
3880
|
// lib/StagehandPage.ts
|
|
3795
3881
|
function getCurrentRootFrameId(session) {
|
|
3796
3882
|
return __async(this, null, function* () {
|
|
@@ -3875,32 +3961,6 @@ var StagehandPage = class _StagehandPage {
|
|
|
3875
3961
|
resetFrameOrdinals() {
|
|
3876
3962
|
this.fidOrdinals = /* @__PURE__ */ new Map([[void 0, 0]]);
|
|
3877
3963
|
}
|
|
3878
|
-
ensureStagehandScript() {
|
|
3879
|
-
return __async(this, null, function* () {
|
|
3880
|
-
try {
|
|
3881
|
-
const injected = yield this.rawPage.evaluate(
|
|
3882
|
-
() => !!window.__stagehandInjected
|
|
3883
|
-
);
|
|
3884
|
-
if (injected) return;
|
|
3885
|
-
const guardedScript = `if (!window.__stagehandInjected) { window.__stagehandInjected = true; ${scriptContent} }`;
|
|
3886
|
-
yield this.rawPage.addInitScript({ content: guardedScript });
|
|
3887
|
-
yield this.rawPage.evaluate(guardedScript);
|
|
3888
|
-
} catch (err) {
|
|
3889
|
-
if (!this.stagehand.isClosed) {
|
|
3890
|
-
this.stagehand.log({
|
|
3891
|
-
category: "dom",
|
|
3892
|
-
message: "Failed to inject Stagehand helper script",
|
|
3893
|
-
level: 1,
|
|
3894
|
-
auxiliary: {
|
|
3895
|
-
error: { value: err.message, type: "string" },
|
|
3896
|
-
trace: { value: err.stack, type: "string" }
|
|
3897
|
-
}
|
|
3898
|
-
});
|
|
3899
|
-
throw err;
|
|
3900
|
-
}
|
|
3901
|
-
}
|
|
3902
|
-
});
|
|
3903
|
-
}
|
|
3904
3964
|
/** Register the custom selector engine that pierces open/closed shadow roots. */
|
|
3905
3965
|
ensureStagehandSelectorEngine() {
|
|
3906
3966
|
return __async(this, null, function* () {
|
|
@@ -4056,7 +4116,6 @@ var StagehandPage = class _StagehandPage {
|
|
|
4056
4116
|
const value = target[prop];
|
|
4057
4117
|
if (prop === "evaluate" || prop === "evaluateHandle" || prop === "$eval" || prop === "$$eval") {
|
|
4058
4118
|
return (...args) => __async(this, null, function* () {
|
|
4059
|
-
yield this.ensureStagehandScript();
|
|
4060
4119
|
return value.apply(
|
|
4061
4120
|
target,
|
|
4062
4121
|
args
|
|
@@ -4663,8 +4722,17 @@ var StagehandPage = class _StagehandPage {
|
|
|
4663
4722
|
}
|
|
4664
4723
|
};
|
|
4665
4724
|
|
|
4725
|
+
// lib/dom/build/scriptContent.ts
|
|
4726
|
+
var scriptContent = '(() => {\n // lib/dom/process.ts\n (() => {\n const closedRoots = /* @__PURE__ */ new WeakMap();\n const nativeAttachShadow = Element.prototype.attachShadow;\n Element.prototype.attachShadow = function(init) {\n const root = nativeAttachShadow.call(this, init);\n if (init.mode === "closed") closedRoots.set(this, root);\n return root;\n };\n const backdoor = {\n getClosedRoot: (host) => closedRoots.get(host),\n queryClosed: (host, selector) => {\n const root = closedRoots.get(host);\n return root ? Array.from(root.querySelectorAll(selector)) : [];\n },\n xpathClosed: (host, xp) => {\n const root = closedRoots.get(host);\n if (!root) return [];\n const it = document.evaluate(\n xp,\n root,\n null,\n XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,\n null\n );\n const out = [];\n for (let i = 0; i < it.snapshotLength; ++i) {\n const n = it.snapshotItem(i);\n if (n) out.push(n);\n }\n return out;\n }\n };\n if (!("__stagehand__" in window)) {\n Object.defineProperty(window, "__stagehand__", {\n value: backdoor,\n enumerable: false,\n writable: false,\n configurable: false\n });\n }\n })();\n})();\n';
|
|
4727
|
+
|
|
4666
4728
|
// lib/StagehandContext.ts
|
|
4667
|
-
var
|
|
4729
|
+
var stagehandInitScript = `
|
|
4730
|
+
if (!window.__stagehandInjected) {
|
|
4731
|
+
window.__stagehandInjected = true;
|
|
4732
|
+
${scriptContent}
|
|
4733
|
+
}
|
|
4734
|
+
`;
|
|
4735
|
+
var _StagehandContext = class _StagehandContext {
|
|
4668
4736
|
constructor(context, stagehand) {
|
|
4669
4737
|
this.activeStagehandPage = null;
|
|
4670
4738
|
this.frameIdMap = /* @__PURE__ */ new Map();
|
|
@@ -4723,6 +4791,10 @@ var StagehandContext = class _StagehandContext {
|
|
|
4723
4791
|
}
|
|
4724
4792
|
static init(context, stagehand) {
|
|
4725
4793
|
return __async(this, null, function* () {
|
|
4794
|
+
if (!_StagehandContext.contextsWithInitScript.has(context)) {
|
|
4795
|
+
yield context.addInitScript({ content: stagehandInitScript });
|
|
4796
|
+
_StagehandContext.contextsWithInitScript.add(context);
|
|
4797
|
+
}
|
|
4726
4798
|
const instance = new _StagehandContext(context, stagehand);
|
|
4727
4799
|
context.on("page", (pwPage) => __async(null, null, function* () {
|
|
4728
4800
|
yield instance.handleNewPlaywrightPage(pwPage);
|
|
@@ -4825,6 +4897,8 @@ var StagehandContext = class _StagehandContext {
|
|
|
4825
4897
|
});
|
|
4826
4898
|
}
|
|
4827
4899
|
};
|
|
4900
|
+
_StagehandContext.contextsWithInitScript = /* @__PURE__ */ new WeakSet();
|
|
4901
|
+
var StagehandContext = _StagehandContext;
|
|
4828
4902
|
|
|
4829
4903
|
// lib/api.ts
|
|
4830
4904
|
var import_zod_to_json_schema = __toESM(require("zod-to-json-schema"));
|
|
@@ -20290,9 +20364,7 @@ var modelToProviderMap = {
|
|
|
20290
20364
|
"gpt-4o-2024-08-06": "openai",
|
|
20291
20365
|
"gpt-4.5-preview": "openai",
|
|
20292
20366
|
"o1-preview": "openai",
|
|
20293
|
-
"claude-
|
|
20294
|
-
"claude-3-5-sonnet-20240620": "anthropic",
|
|
20295
|
-
"claude-3-5-sonnet-20241022": "anthropic",
|
|
20367
|
+
"claude-haiku-4-5": "anthropic",
|
|
20296
20368
|
"claude-3-7-sonnet-20250219": "anthropic",
|
|
20297
20369
|
"claude-3-7-sonnet-latest": "anthropic",
|
|
20298
20370
|
"cerebras-llama-3.3-70b": "cerebras",
|
|
@@ -24090,9 +24162,7 @@ var AvailableModelSchema = import_v315.z.enum([
|
|
|
24090
24162
|
"gpt-4o-2024-08-06",
|
|
24091
24163
|
"gpt-4.5-preview",
|
|
24092
24164
|
"o1-preview",
|
|
24093
|
-
"claude-
|
|
24094
|
-
"claude-3-5-sonnet-20241022",
|
|
24095
|
-
"claude-3-5-sonnet-20240620",
|
|
24165
|
+
"claude-haiku-4-5",
|
|
24096
24166
|
"claude-3-7-sonnet-latest",
|
|
24097
24167
|
"claude-3-7-sonnet-20250219",
|
|
24098
24168
|
"cerebras-llama-3.3-70b",
|
|
@@ -24932,5 +25002,6 @@ var Stagehand3 = class {
|
|
|
24932
25002
|
ZodSchemaValidationError,
|
|
24933
25003
|
connectToMCPServer,
|
|
24934
25004
|
defaultExtractSchema,
|
|
25005
|
+
getAccessibilityTree,
|
|
24935
25006
|
pageTextSchema
|
|
24936
25007
|
});
|
|
@@ -8,6 +8,7 @@ export declare class StagehandContext {
|
|
|
8
8
|
private pageMap;
|
|
9
9
|
private activeStagehandPage;
|
|
10
10
|
private readonly frameIdMap;
|
|
11
|
+
private static readonly contextsWithInitScript;
|
|
11
12
|
private constructor();
|
|
12
13
|
private createStagehandPage;
|
|
13
14
|
static init(context: PlaywrightContext, stagehand: Stagehand): Promise<StagehandContext>;
|
|
@@ -30,7 +30,6 @@ export declare class StagehandPage {
|
|
|
30
30
|
ordinalForFrameId(fid: string | undefined): number;
|
|
31
31
|
encodeWithFrameId(fid: string | undefined, backendId: number): EncodedId;
|
|
32
32
|
resetFrameOrdinals(): void;
|
|
33
|
-
private ensureStagehandScript;
|
|
34
33
|
/** Register the custom selector engine that pierces open/closed shadow roots. */
|
|
35
34
|
private ensureStagehandSelectorEngine;
|
|
36
35
|
/**
|
package/dist/lib/a11y/utils.d.ts
CHANGED
|
@@ -55,6 +55,9 @@ export declare function getCDPFrameId(sp: StagehandPage, frame?: Frame): Promise
|
|
|
55
55
|
* Retrieve and build a cleaned accessibility tree for a document or specific iframe.
|
|
56
56
|
* Prunes, formats, and optionally filters by XPath, including scrollable role decoration.
|
|
57
57
|
*
|
|
58
|
+
* @deprecated This helper is an escape hatch intended for troubleshooting. Prefer
|
|
59
|
+
* extract() for supported usage and reach for this only
|
|
60
|
+
* when absolutely necessary.
|
|
58
61
|
* @param stagehandPage - The StagehandPage instance for Playwright and CDP interaction.
|
|
59
62
|
* @param logger - Logging function for diagnostics and performance metrics.
|
|
60
63
|
* @param selector - Optional XPath to filter the AX tree to a specific subtree.
|
|
@@ -97,25 +100,6 @@ export declare function injectSubtrees(tree: string, idToTree: Map<EncodedId, st
|
|
|
97
100
|
* @returns A Promise resolving to CombinedA11yResult with combined tree text, xpath map, and URL map.
|
|
98
101
|
*/
|
|
99
102
|
export declare function getAccessibilityTreeWithFrames(experimental: boolean, stagehandPage: StagehandPage, logger: (l: LogLine) => void, rootXPath?: string): Promise<CombinedA11yResult>;
|
|
100
|
-
/**
|
|
101
|
-
* `findScrollableElementIds` is a function that identifies elements in
|
|
102
|
-
* the browser that are deemed "scrollable". At a high level, it does the
|
|
103
|
-
* following:
|
|
104
|
-
* - Calls the browser-side `window.getScrollableElementXpaths()` function,
|
|
105
|
-
* which returns a list of XPaths for scrollable containers.
|
|
106
|
-
* - Iterates over the returned list of XPaths, locating each element in the DOM
|
|
107
|
-
* using `stagehandPage.sendCDP(...)`
|
|
108
|
-
* - During each iteration, we call `Runtime.evaluate` to run `document.evaluate(...)`
|
|
109
|
-
* with each XPath, obtaining a `RemoteObject` reference if it exists.
|
|
110
|
-
* - Then, for each valid object reference, we call `DOM.describeNode` to retrieve
|
|
111
|
-
* the element’s `backendNodeId`.
|
|
112
|
-
* - Collects all resulting `backendNodeId`s in a Set and returns them.
|
|
113
|
-
*
|
|
114
|
-
* @param stagehandPage - A StagehandPage instance with built-in CDP helpers.
|
|
115
|
-
* @returns A Promise that resolves to a Set of unique `backendNodeId`s corresponding
|
|
116
|
-
* to scrollable elements in the DOM.
|
|
117
|
-
*/
|
|
118
|
-
export declare function findScrollableElementIds(stagehandPage: StagehandPage, targetFrame?: Frame): Promise<Set<number>>;
|
|
119
103
|
/**
|
|
120
104
|
* Resolve an XPath to a Chrome-DevTools-Protocol (CDP) remote-object ID.
|
|
121
105
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const scriptContent = "(() => {\n // lib/dom/elementCheckUtils.ts\n function isElementNode(node) {\n return node.nodeType === Node.ELEMENT_NODE;\n }\n function isTextNode(node) {\n return node.nodeType === Node.TEXT_NODE && Boolean(node.textContent?.trim());\n }\n\n // lib/dom/xpathUtils.ts\n function getParentElement(node) {\n return isElementNode(node) ? node.parentElement : node.parentNode;\n }\n function getCombinations(attributes, size) {\n const results = [];\n function helper(start, combo) {\n if (combo.length === size) {\n results.push([...combo]);\n return;\n }\n for (let i = start; i < attributes.length; i++) {\n combo.push(attributes[i]);\n helper(i + 1, combo);\n combo.pop();\n }\n }\n helper(0, []);\n return results;\n }\n function isXPathFirstResultElement(xpath, target) {\n try {\n const result = document.evaluate(\n xpath,\n document.documentElement,\n null,\n XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,\n null\n );\n return result.snapshotItem(0) === target;\n } catch (error) {\n console.warn(`Invalid XPath expression: ${xpath}`, error);\n return false;\n }\n }\n function escapeXPathString(value) {\n if (value.includes(\"'\")) {\n if (value.includes('\"')) {\n return \"concat(\" + value.split(/('+)/).map((part) => {\n if (part === \"'\") {\n return `\"'\"`;\n } else if (part.startsWith(\"'\") && part.endsWith(\"'\")) {\n return `\"${part}\"`;\n } else {\n return `'${part}'`;\n }\n }).join(\",\") + \")\";\n } else {\n return `\"${value}\"`;\n }\n } else {\n return `'${value}'`;\n }\n }\n async function generateXPathsForElement(element) {\n if (!element) return [];\n const [complexXPath, standardXPath, idBasedXPath] = await Promise.all([\n generateComplexXPath(element),\n generateStandardXPath(element),\n generatedIdBasedXPath(element)\n ]);\n return [standardXPath, ...idBasedXPath ? [idBasedXPath] : [], complexXPath];\n }\n async function generateComplexXPath(element) {\n const parts = [];\n let currentElement = element;\n while (currentElement && (isTextNode(currentElement) || isElementNode(currentElement))) {\n if (isElementNode(currentElement)) {\n const el = currentElement;\n let selector = el.tagName.toLowerCase();\n const attributePriority = [\n \"data-qa\",\n \"data-component\",\n \"data-role\",\n \"role\",\n \"aria-role\",\n \"type\",\n \"name\",\n \"aria-label\",\n \"placeholder\",\n \"title\",\n \"alt\"\n ];\n const attributes = attributePriority.map((attr) => {\n let value = el.getAttribute(attr);\n if (attr === \"href-full\" && value) {\n value = el.getAttribute(\"href\");\n }\n return value ? { attr: attr === \"href-full\" ? \"href\" : attr, value } : null;\n }).filter((attr) => attr !== null);\n let uniqueSelector = \"\";\n for (let i = 1; i <= attributes.length; i++) {\n const combinations = getCombinations(attributes, i);\n for (const combo of combinations) {\n const conditions = combo.map((a) => `@${a.attr}=${escapeXPathString(a.value)}`).join(\" and \");\n const xpath2 = `//${selector}[${conditions}]`;\n if (isXPathFirstResultElement(xpath2, el)) {\n uniqueSelector = xpath2;\n break;\n }\n }\n if (uniqueSelector) break;\n }\n if (uniqueSelector) {\n parts.unshift(uniqueSelector.replace(\"//\", \"\"));\n break;\n } else {\n const parent = getParentElement(el);\n if (parent) {\n const siblings = Array.from(parent.children).filter(\n (sibling) => sibling.tagName === el.tagName\n );\n const index = siblings.indexOf(el) + 1;\n selector += siblings.length > 1 ? `[${index}]` : \"\";\n }\n parts.unshift(selector);\n }\n }\n currentElement = getParentElement(currentElement);\n }\n const xpath = \"//\" + parts.join(\"/\");\n return xpath;\n }\n async function generateStandardXPath(element) {\n const parts = [];\n while (element && (isTextNode(element) || isElementNode(element))) {\n let index = 0;\n let hasSameTypeSiblings = false;\n const siblings = element.parentElement ? Array.from(element.parentElement.childNodes) : [];\n for (let i = 0; i < siblings.length; i++) {\n const sibling = siblings[i];\n if (sibling.nodeType === element.nodeType && sibling.nodeName === element.nodeName) {\n index = index + 1;\n hasSameTypeSiblings = true;\n if (sibling.isSameNode(element)) {\n break;\n }\n }\n }\n if (element.nodeName !== \"#text\") {\n const tagName = element.nodeName.toLowerCase();\n const pathIndex = hasSameTypeSiblings ? `[${index}]` : \"\";\n parts.unshift(`${tagName}${pathIndex}`);\n }\n element = element.parentElement;\n }\n return parts.length ? `/${parts.join(\"/\")}` : \"\";\n }\n async function generatedIdBasedXPath(element) {\n if (isElementNode(element) && element.id) {\n return `//*[@id='${element.id}']`;\n }\n return null;\n }\n\n // types/stagehandErrors.ts\n var StagehandError = class extends Error {\n constructor(message) {\n super(message);\n this.name = this.constructor.name;\n }\n };\n var StagehandDomProcessError = class extends StagehandError {\n constructor(message) {\n super(`Error Processing Dom: ${message}`);\n }\n };\n\n // lib/dom/utils.ts\n function canElementScroll(elem) {\n if (typeof elem.scrollTo !== \"function\") {\n console.warn(\"canElementScroll: .scrollTo is not a function.\");\n return false;\n }\n try {\n const originalTop = elem.scrollTop;\n elem.scrollTo({\n top: originalTop + 100,\n left: 0,\n behavior: \"instant\"\n });\n if (elem.scrollTop === originalTop) {\n throw new StagehandDomProcessError(\"scrollTop did not change\");\n }\n elem.scrollTo({\n top: originalTop,\n left: 0,\n behavior: \"instant\"\n });\n return true;\n } catch (error) {\n console.warn(\"canElementScroll error:\", error.message || error);\n return false;\n }\n }\n function getNodeFromXpath(xpath) {\n return document.evaluate(\n xpath,\n document.documentElement,\n null,\n XPathResult.FIRST_ORDERED_NODE_TYPE,\n null\n ).singleNodeValue;\n }\n function waitForElementScrollEnd(element, idleMs = 100) {\n return new Promise((resolve) => {\n let scrollEndTimer;\n const handleScroll = () => {\n clearTimeout(scrollEndTimer);\n scrollEndTimer = window.setTimeout(() => {\n element.removeEventListener(\"scroll\", handleScroll);\n resolve();\n }, idleMs);\n };\n element.addEventListener(\"scroll\", handleScroll, { passive: true });\n handleScroll();\n });\n }\n\n // lib/dom/process.ts\n function getScrollableElements(topN) {\n const docEl = document.documentElement;\n const scrollableElements = [docEl];\n const allElements = document.querySelectorAll(\"*\");\n for (const elem of allElements) {\n const style = window.getComputedStyle(elem);\n const overflowY = style.overflowY;\n const isPotentiallyScrollable = overflowY === \"auto\" || overflowY === \"scroll\" || overflowY === \"overlay\";\n if (isPotentiallyScrollable) {\n const candidateScrollDiff = elem.scrollHeight - elem.clientHeight;\n if (candidateScrollDiff > 0 && canElementScroll(elem)) {\n scrollableElements.push(elem);\n }\n }\n }\n scrollableElements.sort((a, b) => b.scrollHeight - a.scrollHeight);\n if (topN !== void 0) {\n return scrollableElements.slice(0, topN);\n }\n return scrollableElements;\n }\n async function getScrollableElementXpaths(topN) {\n const scrollableElems = getScrollableElements(topN);\n const xpaths = [];\n for (const elem of scrollableElems) {\n const allXPaths = await generateXPathsForElement(elem);\n const firstXPath = allXPaths?.[0] || \"\";\n xpaths.push(firstXPath);\n }\n return xpaths;\n }\n (() => {\n const closedRoots = /* @__PURE__ */ new WeakMap();\n const nativeAttachShadow = Element.prototype.attachShadow;\n Element.prototype.attachShadow = function(init) {\n const root = nativeAttachShadow.call(this, init);\n if (init.mode === \"closed\") closedRoots.set(this, root);\n return root;\n };\n const backdoor = {\n getClosedRoot: (host) => closedRoots.get(host),\n queryClosed: (host, selector) => {\n const root = closedRoots.get(host);\n return root ? Array.from(root.querySelectorAll(selector)) : [];\n },\n xpathClosed: (host, xp) => {\n const root = closedRoots.get(host);\n if (!root) return [];\n const it = document.evaluate(\n xp,\n root,\n null,\n XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,\n null\n );\n const out = [];\n for (let i = 0; i < it.snapshotLength; ++i) {\n const n = it.snapshotItem(i);\n if (n) out.push(n);\n }\n return out;\n }\n };\n if (!(\"__stagehand__\" in window)) {\n Object.defineProperty(window, \"__stagehand__\", {\n value: backdoor,\n enumerable: false,\n writable: false,\n configurable: false\n });\n }\n })();\n window.getScrollableElementXpaths = getScrollableElementXpaths;\n window.getNodeFromXpath = getNodeFromXpath;\n window.waitForElementScrollEnd = waitForElementScrollEnd;\n})();\n";
|
|
1
|
+
export declare const scriptContent = "(() => {\n // lib/dom/process.ts\n (() => {\n const closedRoots = /* @__PURE__ */ new WeakMap();\n const nativeAttachShadow = Element.prototype.attachShadow;\n Element.prototype.attachShadow = function(init) {\n const root = nativeAttachShadow.call(this, init);\n if (init.mode === \"closed\") closedRoots.set(this, root);\n return root;\n };\n const backdoor = {\n getClosedRoot: (host) => closedRoots.get(host),\n queryClosed: (host, selector) => {\n const root = closedRoots.get(host);\n return root ? Array.from(root.querySelectorAll(selector)) : [];\n },\n xpathClosed: (host, xp) => {\n const root = closedRoots.get(host);\n if (!root) return [];\n const it = document.evaluate(\n xp,\n root,\n null,\n XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,\n null\n );\n const out = [];\n for (let i = 0; i < it.snapshotLength; ++i) {\n const n = it.snapshotItem(i);\n if (n) out.push(n);\n }\n return out;\n }\n };\n if (!(\"__stagehand__\" in window)) {\n Object.defineProperty(window, \"__stagehand__\", {\n value: backdoor,\n enumerable: false,\n writable: false,\n configurable: false\n });\n }\n })();\n})();\n";
|
package/dist/lib/dom/index.d.ts
CHANGED
|
@@ -1,2 +1 @@
|
|
|
1
|
-
|
|
2
|
-
export * from "./utils";
|
|
1
|
+
import "./process";
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Finds and returns a list of scrollable elements on the page,
|
|
3
|
-
* ordered from the element with the largest scrollHeight to the smallest.
|
|
4
|
-
*
|
|
5
|
-
* @param topN Optional maximum number of scrollable elements to return.
|
|
6
|
-
* If not provided, all found scrollable elements are returned.
|
|
7
|
-
* @returns An array of HTMLElements sorted by descending scrollHeight.
|
|
8
|
-
*/
|
|
9
|
-
export declare function getScrollableElements(topN?: number): HTMLElement[];
|
|
10
|
-
/**
|
|
11
|
-
* Calls getScrollableElements, then for each element calls generateXPaths,
|
|
12
|
-
* and returns the first XPath for each.
|
|
13
|
-
*
|
|
14
|
-
* @param topN (optional) integer limit on how many scrollable elements to process
|
|
15
|
-
* @returns string[] list of XPaths (1 for each scrollable element)
|
|
16
|
-
*/
|
|
17
|
-
export declare function getScrollableElementXpaths(topN?: number): Promise<string[]>;
|
package/dist/lib/index.d.ts
CHANGED
|
@@ -91,4 +91,7 @@ export * from "../types/stagehandApiErrors";
|
|
|
91
91
|
export * from "../types/stagehandErrors";
|
|
92
92
|
export * from "./llm/LLMClient";
|
|
93
93
|
export * from "./llm/aisdk";
|
|
94
|
+
export type { TreeResult } from "../types/context";
|
|
94
95
|
export { connectToMCPServer };
|
|
96
|
+
/** @deprecated Direct AX access is not part of the stable API. Prefer extract(). */
|
|
97
|
+
export { getAccessibilityTree } from "./a11y/utils";
|
package/dist/lib/version.d.ts
CHANGED
package/dist/types/context.d.ts
CHANGED
|
@@ -51,6 +51,7 @@ export interface TreeResult {
|
|
|
51
51
|
xpathMap: Record<EncodedId, string>;
|
|
52
52
|
}
|
|
53
53
|
export type DOMNode = {
|
|
54
|
+
nodeId?: number;
|
|
54
55
|
backendNodeId?: number;
|
|
55
56
|
nodeName?: string;
|
|
56
57
|
children?: DOMNode[];
|
|
@@ -58,11 +59,14 @@ export type DOMNode = {
|
|
|
58
59
|
contentDocument?: DOMNode;
|
|
59
60
|
nodeType: number;
|
|
60
61
|
frameId?: string;
|
|
62
|
+
isScrollable?: boolean;
|
|
63
|
+
childNodeCount?: number;
|
|
61
64
|
};
|
|
62
65
|
export type BackendIdMaps = {
|
|
63
66
|
tagNameMap: Record<number, string>;
|
|
64
67
|
xpathMap: Record<number, string>;
|
|
65
68
|
iframeXPath?: string;
|
|
69
|
+
scrollableBackendIds: Set<number>;
|
|
66
70
|
};
|
|
67
71
|
export interface EnhancedContext extends Omit<PlaywrightContext, "newPage" | "pages"> {
|
|
68
72
|
newPage(): Promise<Page>;
|
package/dist/types/model.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ClientOptions as AnthropicClientOptions } from "@anthropic-ai/sdk";
|
|
2
2
|
import type { ClientOptions as OpenAIClientOptions } from "openai";
|
|
3
3
|
import { z } from "zod/v3";
|
|
4
|
-
export declare const AvailableModelSchema: z.ZodEnum<["gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano", "o4-mini", "o3", "o3-mini", "o1", "o1-mini", "gpt-4o", "gpt-4o-mini", "gpt-4o-2024-08-06", "gpt-4.5-preview", "o1-preview", "claude-
|
|
4
|
+
export declare const AvailableModelSchema: z.ZodEnum<["gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano", "o4-mini", "o3", "o3-mini", "o1", "o1-mini", "gpt-4o", "gpt-4o-mini", "gpt-4o-2024-08-06", "gpt-4.5-preview", "o1-preview", "claude-haiku-4-5", "claude-3-7-sonnet-latest", "claude-3-7-sonnet-20250219", "cerebras-llama-3.3-70b", "cerebras-llama-3.1-8b", "groq-llama-3.3-70b-versatile", "groq-llama-3.3-70b-specdec", "gemini-1.5-flash", "gemini-1.5-pro", "gemini-1.5-flash-8b", "gemini-2.0-flash-lite", "gemini-2.0-flash", "gemini-2.5-flash-preview-04-17", "gemini-2.5-pro-preview-03-25"]>;
|
|
5
5
|
export type AvailableModel = z.infer<typeof AvailableModelSchema> | string;
|
|
6
6
|
export type ModelProvider = "openai" | "anthropic" | "cerebras" | "groq" | "google" | "aisdk";
|
|
7
7
|
export type ClientOptions = OpenAIClientOptions | AnthropicClientOptions;
|
|
@@ -3,6 +3,7 @@ export declare class StagehandError extends Error {
|
|
|
3
3
|
constructor(message: string);
|
|
4
4
|
}
|
|
5
5
|
export declare class StagehandDefaultError extends StagehandError {
|
|
6
|
+
causedBy?: Error | StagehandError;
|
|
6
7
|
constructor(error?: unknown);
|
|
7
8
|
}
|
|
8
9
|
export declare class StagehandEnvironmentError extends StagehandError {
|