UNPKG

vibe-tools

Version:
2 lines (1 loc) 8.88 kB
export declare const STAGEHAND_SCRIPT = "(() => {\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 window.getScrollableElementXpaths = getScrollableElementXpaths;\n window.getNodeFromXpath = getNodeFromXpath;\n window.waitForElementScrollEnd = waitForElementScrollEnd;\n})();\n";