UNPKG

vibe-tools

Version:
2 lines (1 loc) 38.2 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 var leafElementDenyList = [\"SVG\", \"IFRAME\", \"SCRIPT\", \"STYLE\", \"LINK\"];\n var interactiveElementTypes = [\n \"A\",\n \"BUTTON\",\n \"DETAILS\",\n \"EMBED\",\n \"INPUT\",\n \"LABEL\",\n \"MENU\",\n \"MENUITEM\",\n \"OBJECT\",\n \"SELECT\",\n \"TEXTAREA\",\n \"SUMMARY\"\n ];\n var interactiveRoles = [\n \"button\",\n \"menu\",\n \"menuitem\",\n \"link\",\n \"checkbox\",\n \"radio\",\n \"slider\",\n \"tab\",\n \"tabpanel\",\n \"textbox\",\n \"combobox\",\n \"grid\",\n \"listbox\",\n \"option\",\n \"progressbar\",\n \"scrollbar\",\n \"searchbox\",\n \"switch\",\n \"tree\",\n \"treeitem\",\n \"spinbutton\",\n \"tooltip\"\n ];\n var interactiveAriaRoles = [\"menu\", \"menuitem\", \"button\"];\n var isVisible = (element) => {\n const rect = element.getBoundingClientRect();\n if (rect.width === 0 || rect.height === 0 || rect.top < 0 || rect.top > window.innerHeight) {\n return false;\n }\n if (!isTopElement(element, rect)) {\n return false;\n }\n const visible = element.checkVisibility({\n checkOpacity: true,\n checkVisibilityCSS: true\n });\n return visible;\n };\n var isTextVisible = (element) => {\n const range = document.createRange();\n range.selectNodeContents(element);\n const rect = range.getBoundingClientRect();\n if (rect.width === 0 || rect.height === 0 || rect.top < 0 || rect.top > window.innerHeight) {\n return false;\n }\n const parent = element.parentElement;\n if (!parent) {\n return false;\n }\n const visible = parent.checkVisibility({\n checkOpacity: true,\n checkVisibilityCSS: true\n });\n return visible;\n };\n function isTopElement(elem, rect) {\n const points = [\n { x: rect.left + rect.width * 0.25, y: rect.top + rect.height * 0.25 },\n { x: rect.left + rect.width * 0.75, y: rect.top + rect.height * 0.25 },\n { x: rect.left + rect.width * 0.25, y: rect.top + rect.height * 0.75 },\n { x: rect.left + rect.width * 0.75, y: rect.top + rect.height * 0.75 },\n { x: rect.left + rect.width / 2, y: rect.top + rect.height / 2 }\n ];\n return points.some((point) => {\n const topEl = document.elementFromPoint(point.x, point.y);\n let current = topEl;\n while (current && current !== document.body) {\n if (current.isSameNode(elem)) {\n return true;\n }\n current = current.parentElement;\n }\n return false;\n });\n }\n var isActive = (element) => {\n if (element.hasAttribute(\"disabled\") || element.hasAttribute(\"hidden\") || element.getAttribute(\"aria-disabled\") === \"true\") {\n return false;\n }\n return true;\n };\n var isInteractiveElement = (element) => {\n const elementType = element.tagName;\n const elementRole = element.getAttribute(\"role\");\n const elementAriaRole = element.getAttribute(\"aria-role\");\n return elementType && interactiveElementTypes.includes(elementType) || elementRole && interactiveRoles.includes(elementRole) || elementAriaRole && interactiveAriaRoles.includes(elementAriaRole);\n };\n var isLeafElement = (element) => {\n if (element.textContent === \"\") {\n return false;\n }\n if (element.childNodes.length === 0) {\n return !leafElementDenyList.includes(element.tagName);\n }\n if (element.childNodes.length === 1 && isTextNode(element.childNodes[0])) {\n return true;\n }\n return false;\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 async function waitForDomSettle() {\n return new Promise((resolve) => {\n const createTimeout = () => {\n return setTimeout(() => {\n resolve();\n }, 2e3);\n };\n let timeout = createTimeout();\n const observer = new MutationObserver(() => {\n clearTimeout(timeout);\n timeout = createTimeout();\n });\n observer.observe(window.document.body, { childList: true, subtree: true });\n });\n }\n function calculateViewportHeight() {\n return Math.ceil(window.innerHeight * 0.75);\n }\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/candidateCollector.ts\n var xpathCache = /* @__PURE__ */ new Map();\n async function collectCandidateElements(candidateContainerRoot, indexOffset = 0) {\n const DOMQueue = [...candidateContainerRoot.childNodes];\n const candidateElements = [];\n while (DOMQueue.length > 0) {\n const node = DOMQueue.pop();\n let shouldAdd = false;\n if (node && isElementNode(node)) {\n for (let i = node.childNodes.length - 1; i >= 0; i--) {\n DOMQueue.push(node.childNodes[i]);\n }\n if (isInteractiveElement(node)) {\n if (isActive(node) && isVisible(node)) {\n shouldAdd = true;\n }\n }\n if (isLeafElement(node)) {\n if (isActive(node) && isVisible(node)) {\n shouldAdd = true;\n }\n }\n }\n if (node && isTextNode(node) && isTextVisible(node)) {\n shouldAdd = true;\n }\n if (shouldAdd) {\n candidateElements.push(node);\n }\n }\n const selectorMap = {};\n let outputString = \"\";\n const xpathLists = await Promise.all(\n candidateElements.map((elem) => {\n if (xpathCache.has(elem)) {\n return Promise.resolve(xpathCache.get(elem));\n }\n return generateXPathsForElement(elem).then((xpaths) => {\n xpathCache.set(elem, xpaths);\n return xpaths;\n });\n })\n );\n candidateElements.forEach((elem, idx) => {\n const xpaths = xpathLists[idx];\n let elemOutput = \"\";\n if (isTextNode(elem)) {\n const textContent = elem.textContent?.trim();\n if (textContent) {\n elemOutput += `${idx + indexOffset}:${textContent}\n`;\n }\n } else if (isElementNode(elem)) {\n const tagName = elem.tagName.toLowerCase();\n const attributes = collectEssentialAttributes(elem);\n const opening = `<${tagName}${attributes ? \" \" + attributes : \"\"}>`;\n const closing = `</${tagName}>`;\n const textContent = elem.textContent?.trim() || \"\";\n elemOutput += `${idx + indexOffset}:${opening}${textContent}${closing}\n`;\n }\n outputString += elemOutput;\n selectorMap[idx + indexOffset] = xpaths;\n });\n return { outputString, selectorMap };\n }\n function collectEssentialAttributes(element) {\n const essentialAttributes = [\n \"id\",\n \"class\",\n \"href\",\n \"src\",\n \"aria-label\",\n \"aria-name\",\n \"aria-role\",\n \"aria-description\",\n \"aria-expanded\",\n \"aria-haspopup\",\n \"type\",\n \"value\"\n ];\n const attrs = essentialAttributes.map((attr) => {\n const value = element.getAttribute(attr);\n return value ? `${attr}=\"${value}\"` : \"\";\n }).filter((attr) => attr !== \"\");\n Array.from(element.attributes).forEach((attr) => {\n if (attr.name.startsWith(\"data-\")) {\n attrs.push(`${attr.name}=\"${attr.value}\"`);\n }\n });\n return attrs.join(\" \");\n }\n\n // lib/dom/StagehandContainer.ts\n var StagehandContainer = class {\n /**\n * Collects multiple \"DOM chunks\" by scrolling through the container\n * in increments from `startOffset` to `endOffset`. At each scroll\n * position, the function extracts a snapshot of \"candidate elements\"\n * using `collectCandidateElements`.\n *\n * Each chunk represents a subset of the DOM at a particular\n * vertical scroll offset, including:\n *\n * - `startOffset` & `endOffset`: The vertical scroll bounds for this chunk.\n * - `outputString`: A serialized representation of extracted DOM text.\n * - `selectorMap`: A mapping of temporary indices to the actual element(s)\n * that were collected in this chunk, useful for further processing.\n *\n * @param startOffset - The initial scroll offset from which to begin collecting.\n * @param endOffset - The maximum scroll offset to collect up to.\n * @param chunkSize - The vertical increment to move between each chunk.\n * @param scrollTo - Whether we should scroll to the chunk\n * @param scrollBackToTop - Whether to scroll the container back to the top once finished.\n * @param candidateContainer - Optionally, a specific container element within\n * the root for which to collect data. If omitted, uses `this.getRootElement()`.\n *\n * @returns A promise that resolves with an array of `DomChunk` objects.\n *\n * ### How It Works\n *\n * 1. **Scroll Range Calculation**:\n * - Computes `maxOffset` as the maximum offset that can be scrolled\n * (`scrollHeight - viewportHeight`).\n * - Restricts `endOffset` to not exceed `maxOffset`.\n *\n * 2. **Chunk Iteration**:\n * - Loops from `startOffset` to `endOffset` in steps of `chunkSize`.\n * - For each offset `current`, we call `this.scrollTo(current)`\n * to position the container.\n *\n * 3. **Element Collection**:\n * - Invokes `collectCandidateElements` on either `candidateContainer`\n * (if provided) or the result of `this.getRootElement()`.\n * - This returns both an `outputString` (serialized text)\n * and a `selectorMap` of found elements for that section of the DOM.\n *\n * 4. **Chunk Assembly**:\n * - Creates a `DomChunk` object for the current offset range,\n * storing `outputString`, `selectorMap`, and scroll offsets.\n * - Pushes it onto the `chunks` array.\n *\n * 5. **Scroll Reset**:\n * - Once iteration completes, if `scrollBackToTop` is `true`,\n * we scroll back to offset `0`.\n */\n async collectDomChunks(startOffset, endOffset, chunkSize, scrollTo = true, scrollBackToTop = true, candidateContainer) {\n const chunks = [];\n let maxOffset = this.getScrollHeight();\n let current = startOffset;\n let finalEnd = endOffset;\n let index = 0;\n while (current <= finalEnd) {\n if (scrollTo) {\n await this.scrollTo(current);\n }\n const rootCandidate = candidateContainer || this.getRootElement();\n const { outputString, selectorMap } = await collectCandidateElements(\n rootCandidate,\n index\n );\n chunks.push({\n startOffset: current,\n endOffset: current + chunkSize,\n outputString,\n selectorMap\n });\n index += Object.keys(selectorMap).length;\n current += chunkSize;\n if (!candidateContainer && current > endOffset) {\n const newScrollHeight = this.getScrollHeight();\n if (newScrollHeight > maxOffset) {\n maxOffset = newScrollHeight;\n }\n if (newScrollHeight > finalEnd) {\n finalEnd = newScrollHeight;\n }\n }\n }\n if (scrollBackToTop) {\n await this.scrollTo(0);\n }\n return chunks;\n }\n };\n\n // lib/dom/GlobalPageContainer.ts\n var GlobalPageContainer = class extends StagehandContainer {\n getRootElement() {\n return document.body;\n }\n /**\n * Calculates the viewport height for the entire page, using a helper.\n * The helper returns 75% of the window height, to ensure that we don't\n * miss any content that may be behind sticky elements like nav bars.\n *\n * @returns The current height of the global viewport, in pixels.\n */\n getViewportHeight() {\n return calculateViewportHeight();\n }\n getScrollHeight() {\n return document.documentElement.scrollHeight;\n }\n getScrollPosition() {\n return window.scrollY;\n }\n /**\n * Smoothly scrolls the page to the specified vertical offset, and then\n * waits until scrolling has stopped. There is a delay built in to allow\n * for lazy loading and other asynchronous content to load.\n *\n * @param offset - The desired scroll offset from the top of the page.\n * @returns A promise that resolves once scrolling is complete.\n */\n async scrollTo(offset) {\n await new Promise((resolve) => setTimeout(resolve, 1500));\n window.scrollTo({ top: offset, behavior: \"smooth\" });\n await this.waitForScrollEnd();\n }\n /**\n * Scrolls the page so that a given element is visible, or scrolls to the top\n * if no element is specified. Uses smooth scrolling and waits for it to complete.\n *\n * @param element - The DOM element to bring into view. If omitted, scrolls to top.\n * @returns A promise that resolves once scrolling is complete.\n */\n async scrollIntoView(element) {\n if (!element) {\n window.scrollTo({ top: 0, behavior: \"smooth\" });\n } else {\n const rect = element.getBoundingClientRect();\n const currentY = window.scrollY || document.documentElement.scrollTop;\n const elementY = currentY + rect.top - window.innerHeight * 0.25;\n window.scrollTo({ top: elementY, behavior: \"smooth\" });\n }\n await this.waitForScrollEnd();\n }\n /**\n * Internal helper that waits until the global scroll activity has stopped.\n * It listens for scroll events, resetting a short timer every time a scroll\n * occurs, and resolves once there's no scroll for ~100ms.\n *\n * @returns A promise that resolves when scrolling has finished.\n */\n async waitForScrollEnd() {\n return new Promise((resolve) => {\n let scrollEndTimer;\n const handleScroll = () => {\n clearTimeout(scrollEndTimer);\n scrollEndTimer = window.setTimeout(() => {\n window.removeEventListener(\"scroll\", handleScroll);\n resolve();\n }, 100);\n };\n window.addEventListener(\"scroll\", handleScroll, { passive: true });\n handleScroll();\n });\n }\n };\n\n // lib/dom/ElementContainer.ts\n var ElementContainer = class extends StagehandContainer {\n /**\n * Creates an instance of `ElementContainer` tied to a specific element.\n * @param el - The scrollable `HTMLElement` that this container controls.\n */\n constructor(el) {\n super();\n this.el = el;\n }\n getRootElement() {\n return this.el;\n }\n /**\n * Retrieves the height of the visible viewport within this element\n * (`el.clientHeight`).\n *\n * @returns The visible (client) height of the element, in pixels.\n */\n getViewportHeight() {\n return this.el.clientHeight;\n }\n getScrollHeight() {\n return this.el.scrollHeight;\n }\n /**\n * Returns the element's current vertical scroll offset.\n */\n getScrollPosition() {\n return this.el.scrollTop;\n }\n /**\n * Smoothly scrolls this element to the specified vertical offset, and\n * waits for the scrolling to complete.\n *\n * @param offset - The scroll offset (in pixels) from the top of the element.\n * @returns A promise that resolves once scrolling is finished.\n */\n async scrollTo(offset) {\n await new Promise((resolve) => setTimeout(resolve, 1500));\n this.el.scrollTo({ top: offset, behavior: \"smooth\" });\n await this.waitForScrollEnd();\n }\n /**\n * Scrolls this element so that the given `element` is visible, or\n * scrolls to the top if none is provided. Smoothly animates the scroll\n * and waits until it finishes.\n *\n * @param element - The child element to bring into view. If omitted, scrolls to top.\n * @returns A promise that resolves once scrolling completes.\n */\n async scrollIntoView(element) {\n if (!element) {\n this.el.scrollTo({ top: 0, behavior: \"smooth\" });\n } else {\n element.scrollIntoView();\n }\n await this.waitForScrollEnd();\n }\n /**\n * Internal helper that waits until scrolling in this element has\n * fully stopped. It listens for scroll events on the element,\n * resetting a short timer every time a scroll occurs, and resolves\n * once there's no scroll for ~100ms.\n *\n * @returns A promise that resolves when scrolling has finished.\n */\n async waitForScrollEnd() {\n return new Promise((resolve) => {\n let scrollEndTimer;\n const handleScroll = () => {\n clearTimeout(scrollEndTimer);\n scrollEndTimer = window.setTimeout(() => {\n this.el.removeEventListener(\"scroll\", handleScroll);\n resolve();\n }, 100);\n };\n this.el.addEventListener(\"scroll\", handleScroll, { passive: true });\n handleScroll();\n });\n }\n };\n\n // lib/dom/containerFactory.ts\n function createStagehandContainer(obj) {\n if (obj instanceof Window) {\n return new GlobalPageContainer();\n } else {\n return new ElementContainer(obj);\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 function getNearestScrollableParent(el) {\n const allScrollables = getScrollableElements();\n let current = el;\n while (current) {\n if (allScrollables.includes(current)) {\n return current;\n }\n current = current.parentElement;\n }\n return document.documentElement;\n }\n async function processDom(chunksSeen) {\n const { chunk, chunksArray } = await pickChunk(chunksSeen);\n const container = new GlobalPageContainer();\n const chunkSize = container.getViewportHeight();\n const startOffset = chunk * chunkSize;\n const endOffset = startOffset;\n const domChunks = await container.collectDomChunks(\n startOffset,\n endOffset,\n chunkSize,\n true,\n false,\n // scrollBackToTop\n container.getRootElement()\n // BFS entire doc\n );\n const [domChunk] = domChunks;\n if (!domChunk) {\n return {\n outputString: \"\",\n selectorMap: {},\n chunk,\n chunks: chunksArray\n };\n }\n console.log(\"Extracted DOM chunk:\\n\", domChunk.outputString);\n return {\n outputString: domChunk.outputString,\n selectorMap: domChunk.selectorMap,\n chunk,\n chunks: chunksArray\n };\n }\n async function processAllOfDom(xpath) {\n let candidateElementContainer = null;\n let scrollTarget;\n if (xpath) {\n const node = getNodeFromXpath(xpath);\n if (node) {\n candidateElementContainer = node;\n console.log(`Found element via XPath: ${xpath}`);\n const scrollableElem = getNearestScrollableParent(\n candidateElementContainer\n );\n if (scrollableElem === document.documentElement) {\n scrollTarget = new GlobalPageContainer();\n } else {\n scrollTarget = new ElementContainer(scrollableElem);\n }\n await scrollTarget.scrollIntoView(candidateElementContainer);\n const startOffset2 = scrollTarget.getScrollPosition();\n const scrollTargetHeight = scrollTarget.getViewportHeight();\n const candidateElementContainerHeight = candidateElementContainer.scrollHeight;\n if (candidateElementContainerHeight <= scrollTargetHeight) {\n console.log(\n \"Element is smaller/equal to container\\u2019s viewport. Doing single chunk.\"\n );\n const domChunks2 = await scrollTarget.collectDomChunks(\n startOffset2,\n // startOffset\n startOffset2,\n // endOffset => same as start => 1 chunk\n 1,\n // chunkSize=1 => doesn't matter, because start==end means exactly 1 iteration\n true,\n true,\n candidateElementContainer\n );\n const singleChunkOutput = combineChunks(domChunks2);\n console.log(\n \"Final output (single-chunk):\",\n singleChunkOutput.outputString\n );\n return singleChunkOutput;\n }\n console.log(\"Element is bigger. Doing multi-chunk approach.\");\n } else {\n console.warn(`XPath not found: ${xpath}. Using entire doc.`);\n }\n } else {\n const scrollableElems = getScrollableElements(1);\n const mainScrollable = scrollableElems[0];\n scrollTarget = mainScrollable === document.documentElement ? createStagehandContainer(window) : createStagehandContainer(mainScrollable);\n }\n const startOffset = scrollTarget.getScrollPosition();\n const viewportHeight = scrollTarget.getViewportHeight();\n const maxScroll = candidateElementContainer ? startOffset + candidateElementContainer.scrollHeight : scrollTarget.getScrollHeight();\n const chunkSize = viewportHeight;\n console.log(\"processAllOfDom chunk-based from\", startOffset, \"to\", maxScroll);\n const domChunks = await scrollTarget.collectDomChunks(\n startOffset,\n maxScroll,\n chunkSize,\n true,\n true,\n candidateElementContainer ?? void 0\n );\n const finalOutput = combineChunks(domChunks);\n console.log(\n \"All DOM elements combined (chunk-based):\",\n finalOutput.outputString\n );\n return finalOutput;\n }\n function combineChunks(domChunks) {\n const outputString = domChunks.map((c) => c.outputString).join(\"\");\n let finalSelectorMap = {};\n domChunks.forEach((c) => {\n finalSelectorMap = { ...finalSelectorMap, ...c.selectorMap };\n });\n return { outputString, selectorMap: finalSelectorMap };\n }\n function storeDOM(xpath) {\n if (!xpath) {\n const originalDOM = document.body.cloneNode(true);\n console.log(\"DOM state stored (root).\");\n return originalDOM.outerHTML;\n } else {\n const node = getNodeFromXpath(xpath);\n if (!node) {\n console.error(\n `storeDOM: No element found for xpath: ${xpath}. Returning empty string.`\n );\n return \"\";\n }\n console.log(`DOM state stored (element at xpath: ${xpath}).`);\n return node.outerHTML;\n }\n }\n function restoreDOM(storedDOM, xpath) {\n console.log(\"Restoring DOM...\");\n if (!storedDOM) {\n console.error(\"No DOM state was provided.\");\n return;\n }\n if (!xpath) {\n document.body.innerHTML = storedDOM;\n console.log(\"DOM restored (root).\");\n } else {\n const node = getNodeFromXpath(xpath);\n if (!node) {\n console.error(\n `restoreDOM: No element found for xpath: ${xpath}. Cannot restore.`\n );\n return;\n }\n node.outerHTML = storedDOM;\n console.log(`DOM restored (element at xpath: ${xpath}).`);\n }\n }\n function createTextBoundingBoxes(xpath) {\n const style = document.createElement(\"style\");\n document.head.appendChild(style);\n if (style.sheet) {\n style.sheet.insertRule(\n `\n .stagehand-highlighted-word, .stagehand-space {\n border: 0px solid orange;\n display: inline-block !important;\n visibility: visible;\n }\n `,\n 0\n );\n style.sheet.insertRule(\n `\n code .stagehand-highlighted-word, code .stagehand-space,\n pre .stagehand-highlighted-word, pre .stagehand-space {\n white-space: pre-wrap;\n display: inline !important;\n }\n `,\n 1\n );\n }\n function applyHighlighting(root) {\n const containerSelector = root instanceof Document ? \"body *\" : \"*\";\n root.querySelectorAll(containerSelector).forEach((element) => {\n if (element.closest && element.closest(\".stagehand-nav, .stagehand-marker\")) {\n return;\n }\n if ([\"SCRIPT\", \"STYLE\", \"IFRAME\", \"INPUT\"].includes(element.tagName)) {\n return;\n }\n const childNodes = Array.from(element.childNodes);\n childNodes.forEach((node) => {\n if (node.nodeType === 3 && node.textContent?.trim().length > 0) {\n const textContent = node.textContent.replace(/\\u00A0/g, \" \");\n const tokens = textContent.split(/(\\s+)/g);\n const fragment = document.createDocumentFragment();\n const parentIsCode = element.tagName === \"CODE\";\n tokens.forEach((token) => {\n const span = document.createElement(\"span\");\n span.textContent = token;\n if (parentIsCode) {\n span.style.whiteSpace = \"pre-wrap\";\n span.style.display = \"inline\";\n }\n span.className = token.trim().length === 0 ? \"stagehand-space\" : \"stagehand-highlighted-word\";\n fragment.appendChild(span);\n });\n if (fragment.childNodes.length > 0 && node.parentNode) {\n element.insertBefore(fragment, node);\n node.remove();\n }\n }\n });\n });\n }\n if (!xpath) {\n applyHighlighting(document);\n document.querySelectorAll(\"iframe\").forEach((iframe) => {\n try {\n iframe.contentWindow?.postMessage({ action: \"highlight\" }, \"*\");\n } catch (error) {\n console.error(\"Error accessing iframe content: \", error);\n }\n });\n } else {\n const node = getNodeFromXpath(xpath);\n if (!node) {\n console.warn(\n `createTextBoundingBoxes: No element found for xpath \"${xpath}\".`\n );\n return;\n }\n applyHighlighting(node);\n }\n }\n function getElementBoundingBoxes(xpath) {\n const element = getNodeFromXpath(xpath);\n if (!element) return [];\n const isValidText = (text) => text && text.trim().length > 0;\n let dropDownElem = element.querySelector(\"option[selected]\");\n if (!dropDownElem) {\n dropDownElem = element.querySelector(\"option\");\n }\n if (dropDownElem) {\n const elemText = dropDownElem.textContent || \"\";\n if (isValidText(elemText)) {\n const parentRect = element.getBoundingClientRect();\n return [\n {\n text: elemText.trim(),\n top: parentRect.top + window.scrollY,\n left: parentRect.left + window.scrollX,\n width: parentRect.width,\n height: parentRect.height\n }\n ];\n } else {\n return [];\n }\n }\n let placeholderText = \"\";\n if ((element.tagName.toLowerCase() === \"input\" || element.tagName.toLowerCase() === \"textarea\") && element.placeholder) {\n placeholderText = element.placeholder;\n } else if (element.tagName.toLowerCase() === \"a\") {\n placeholderText = \"\";\n } else if (element.tagName.toLowerCase() === \"img\") {\n placeholderText = element.alt || \"\";\n }\n const words = element.querySelectorAll(\n \".stagehand-highlighted-word\"\n );\n const boundingBoxes = Array.from(words).map((word) => {\n const rect = word.getBoundingClientRect();\n return {\n text: word.innerText || \"\",\n top: rect.top + window.scrollY,\n left: rect.left + window.scrollX,\n width: rect.width,\n height: rect.height * 0.75\n };\n }).filter(\n (box) => box.width > 0 && box.height > 0 && box.top >= 0 && box.left >= 0 && isValidText(box.text)\n );\n if (boundingBoxes.length === 0) {\n const elementRect = element.getBoundingClientRect();\n return [\n {\n text: placeholderText,\n top: elementRect.top + window.scrollY,\n left: elementRect.left + window.scrollX,\n width: elementRect.width,\n height: elementRect.height * 0.75\n }\n ];\n }\n return boundingBoxes;\n }\n window.waitForDomSettle = waitForDomSettle;\n window.processDom = processDom;\n window.processAllOfDom = processAllOfDom;\n window.storeDOM = storeDOM;\n window.restoreDOM = restoreDOM;\n window.createTextBoundingBoxes = createTextBoundingBoxes;\n window.getElementBoundingBoxes = getElementBoundingBoxes;\n window.createStagehandContainer = createStagehandContainer;\n window.getScrollableElementXpaths = getScrollableElementXpaths;\n window.getNodeFromXpath = getNodeFromXpath;\n window.waitForElementScrollEnd = waitForElementScrollEnd;\n async function pickChunk(chunksSeen) {\n const viewportHeight = calculateViewportHeight();\n const documentHeight = document.documentElement.scrollHeight;\n const chunks = Math.ceil(documentHeight / viewportHeight);\n const chunksArray = Array.from({ length: chunks }, (_, i) => i);\n const chunksRemaining = chunksArray.filter((chunk2) => {\n return !chunksSeen.includes(chunk2);\n });\n const currentScrollPosition = window.scrollY;\n const closestChunk = chunksRemaining.reduce((closest, current) => {\n const currentChunkTop = viewportHeight * current;\n const closestChunkTop = viewportHeight * closest;\n return Math.abs(currentScrollPosition - currentChunkTop) < Math.abs(currentScrollPosition - closestChunkTop) ? current : closest;\n }, chunksRemaining[0]);\n const chunk = closestChunk;\n if (chunk === void 0) {\n throw new StagehandDomProcessError(\n `No chunks remaining to check: ${chunksRemaining}`\n );\n }\n return {\n chunk,\n chunksArray\n };\n }\n})();\n";