From ef571c4ea09ebc15b12bdbdc7398bef7c5d69d68 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Sat, 7 Dec 2024 23:28:31 +0530 Subject: [PATCH] feat: traverse dom tree for parent element selection --- server/src/workflow-management/selector.ts | 144 +++++++++++---------- 1 file changed, 78 insertions(+), 66 deletions(-) diff --git a/server/src/workflow-management/selector.ts b/server/src/workflow-management/selector.ts index 193de891..e0cd10c5 100644 --- a/server/src/workflow-management/selector.ts +++ b/server/src/workflow-management/selector.ts @@ -20,49 +20,6 @@ type Workflow = WorkflowFile["workflow"]; * @category WorkflowManagement-Selectors * @returns {Promise} */ -export const getRect = async (page: Page, coordinates: Coordinates) => { - try { - const rect = await page.evaluate( - async ({ x, y }) => { - const el = document.elementFromPoint(x, y) as HTMLElement; - if (el) { - const { parentElement } = el; - // Match the logic in recorder.ts for link clicks - const element = parentElement?.tagName === 'A' ? parentElement : el; - const rectangle = element?.getBoundingClientRect(); - // @ts-ignore - if (rectangle) { - return { - x: rectangle.x, - y: rectangle.y, - width: rectangle.width, - height: rectangle.height, - top: rectangle.top, - right: rectangle.right, - bottom: rectangle.bottom, - left: rectangle.left, - }; - } - } - }, - { x: coordinates.x, y: coordinates.y }, - ); - return rect; - } catch (error) { - const { message, stack } = error as Error; - logger.log('error', `Error while retrieving selector: ${message}`); - logger.log('error', `Stack: ${stack}`); - } -} - -/** - * Checks the basic info about an element and returns a {@link BaseActionInfo} object. - * If the element is not found, returns undefined. - * @param page The page instance. - * @param coordinates Coordinates of an element. - * @category WorkflowManagement-Selectors - * @returns {Promise} - */ export const getElementInformation = async ( page: Page, coordinates: Coordinates @@ -70,10 +27,15 @@ export const getElementInformation = async ( try { const elementInfo = await page.evaluate( async ({ x, y }) => { - const el = document.elementFromPoint(x, y) as HTMLElement; - if (el) { - const { parentElement } = el; - const element = parentElement?.tagName === 'A' ? parentElement : el; + // Find the initial element at the point + const initialElement = document.elementFromPoint(x, y) as HTMLElement; + + if (initialElement) { + // Simply use the direct parent, no complex logic + const parentElement = initialElement.parentElement; + + // Use the parent if it exists, otherwise use the initial element + const element = parentElement || initialElement; let info: { tagName: string; @@ -84,32 +46,41 @@ export const getElementInformation = async ( attributes?: Record; innerHTML?: string; outerHTML?: string; + parentTagName?: string; + parentClasses?: string[]; } = { - tagName: element?.tagName ?? '', + tagName: element.tagName, + parentTagName: element.parentElement?.tagName, + parentClasses: element.parentElement + ? Array.from(element.parentElement.classList) + : [] }; - if (element) { - info.attributes = Array.from(element.attributes).reduce( - (acc, attr) => { - acc[attr.name] = attr.value; - return acc; - }, - {} as Record - ); - } + // Collect attributes + info.attributes = Array.from(element.attributes).reduce( + (acc, attr) => { + acc[attr.name] = attr.value; + return acc; + }, + {} as Record + ); - // Gather specific information based on the tag - if (element?.tagName === 'A') { - info.url = (element as HTMLAnchorElement).href; - info.innerText = element.innerText ?? ''; - } else if (element?.tagName === 'IMG') { - info.imageUrl = (element as HTMLImageElement).src; + // Specific handling for different element types + if (element.tagName === 'A') { + const anchorElement = element as HTMLAnchorElement; + info.url = anchorElement.href; + info.innerText = anchorElement.innerText ?? ''; + } else if (element.tagName === 'IMG') { + const imgElement = element as HTMLImageElement; + info.imageUrl = imgElement.src; } else { - info.hasOnlyText = element?.children?.length === 0 && - element?.innerText?.length > 0; - info.innerText = element?.innerText ?? ''; + // Check if element contains only text + info.hasOnlyText = element.children.length === 0 && + (element.innerText?.length ?? 0) > 0; + info.innerText = element.innerText ?? ''; } + // HTML content info.innerHTML = element.innerHTML; info.outerHTML = element.outerHTML; @@ -127,6 +98,47 @@ export const getElementInformation = async ( } }; +export const getRect = async (page: Page, coordinates: Coordinates) => { + try { + const rect = await page.evaluate( + async ({ x, y }) => { + // Find the initial element at the point + const initialElement = document.elementFromPoint(x, y) as HTMLElement; + + if (initialElement) { + // Simply use the direct parent, no complex logic + const parentElement = initialElement.parentElement; + + // Use the parent if it exists, otherwise use the initial element + const element = parentElement || initialElement; + + // Get bounding rectangle + const rectangle = element?.getBoundingClientRect(); + + if (rectangle) { + return { + x: rectangle.x, + y: rectangle.y, + width: rectangle.width, + height: rectangle.height, + top: rectangle.top, + right: rectangle.right, + bottom: rectangle.bottom, + left: rectangle.left, + }; + } + } + return null; + }, + { x: coordinates.x, y: coordinates.y }, + ); + return rect; + } catch (error) { + const { message, stack } = error as Error; + console.error('Error while retrieving selector:', message); + console.error('Stack:', stack); + } +}; /** * Returns the best and unique css {@link Selectors} for the element on the page.