feat: add logic to get deeply nested shadowDOM elements

This commit is contained in:
RohitR311
2024-12-30 22:59:28 +05:30
parent 05c7921c9d
commit d2ab81e229

View File

@@ -823,7 +823,7 @@ export const getSelectors = async (page: Page, coordinates: Coordinates) => {
return output; return output;
} }
const MAX_DEPTH = 10; // const MAX_DEPTH = 10;
const getDeepestElementFromPoint = (x: number, y: number): HTMLElement | null => { const getDeepestElementFromPoint = (x: number, y: number): HTMLElement | null => {
let element = document.elementFromPoint(x, y) as HTMLElement; let element = document.elementFromPoint(x, y) as HTMLElement;
@@ -832,60 +832,76 @@ export const getSelectors = async (page: Page, coordinates: Coordinates) => {
let current = element; let current = element;
let deepestElement = current; let deepestElement = current;
let depth = 0; let depth = 0;
const MAX_DEPTH = 4; // Limit to 2 levels of shadow DOM
while (current && depth < MAX_DEPTH) { while (current && depth < MAX_DEPTH) {
const shadowRoot = current.shadowRoot; const shadowRoot = current.shadowRoot;
if (shadowRoot) { if (!shadowRoot) break;
const shadowElement = shadowRoot.elementFromPoint(x, y) as HTMLElement;
if (!shadowElement) break;
deepestElement = shadowElement; const shadowElement = shadowRoot.elementFromPoint(x, y) as HTMLElement;
current = shadowElement; if (!shadowElement || shadowElement === current) break;
} else {
break; deepestElement = shadowElement;
} current = shadowElement;
depth++; depth++;
} }
return deepestElement; return deepestElement;
}; };
// Helper function to generate selectors for shadow DOM elements
const genSelectorForShadowDOM = (element: HTMLElement) => { const genSelectorForShadowDOM = (element: HTMLElement) => {
const findShadowContext = (element: HTMLElement): { host: HTMLElement, root: ShadowRoot } | null => { // Get complete path up to document root
let current: HTMLElement | null = element; const getShadowPath = (el: HTMLElement) => {
const path = [];
let current = el;
let depth = 0; let depth = 0;
const MAX_DEPTH = 4;
while (current && depth < MAX_DEPTH) { while (current && depth < MAX_DEPTH) {
// Check if element is inside a shadow root const rootNode = current.getRootNode();
if (current.parentNode instanceof ShadowRoot) { if (rootNode instanceof ShadowRoot) {
return { path.unshift({
host: (current.parentNode as ShadowRoot).host as HTMLElement, host: rootNode.host as HTMLElement,
root: current.parentNode as ShadowRoot root: rootNode,
}; element: current
});
current = rootNode.host as HTMLElement;
depth++;
} else {
break;
} }
current = current.parentElement;
depth++;
} }
return null; return path;
}; };
const shadowContext = findShadowContext(element); const shadowPath = getShadowPath(element);
if (!shadowContext) return null; if (shadowPath.length === 0) return null;
try { try {
// Generate selector for the shadow host const selectorParts: string[] = [];
const hostSelector = finder(shadowContext.host);
// Generate selector for the element within the shadow DOM // Generate selector for each shadow DOM boundary
const shadowElementSelector = finder(element, { shadowPath.forEach((context, index) => {
root: shadowContext.root as unknown as Element // Get selector for the host element
const hostSelector = finder(context.host, {
root: index === 0 ? document.body : (shadowPath[index - 1].root as unknown as Element)
});
// For the last context, get selector for target element
if (index === shadowPath.length - 1) {
const elementSelector = finder(element, {
root: context.root as unknown as Element
});
selectorParts.push(`${hostSelector} >> ${elementSelector}`);
} else {
selectorParts.push(hostSelector);
}
}); });
return { return {
fullSelector: `${hostSelector} >> ${shadowElementSelector}`, fullSelector: selectorParts.join(' >> '),
hostSelector, mode: shadowPath[shadowPath.length - 1].root.mode
shadowElementSelector,
mode: shadowContext.root.mode
}; };
} catch (e) { } catch (e) {
console.warn('Error generating shadow DOM selector:', e); console.warn('Error generating shadow DOM selector:', e);
@@ -963,12 +979,7 @@ export const getSelectors = async (page: Page, coordinates: Coordinates) => {
formSelector, formSelector,
// Shadow DOM selector // Shadow DOM selector
shadowSelector: shadowSelector ? { shadowSelector: shadowSelector ? {
// Full selector that can traverse shadow DOM
full: shadowSelector.fullSelector, full: shadowSelector.fullSelector,
// Individual parts for more flexible usage
host: shadowSelector.hostSelector,
element: shadowSelector.shadowElementSelector,
// Shadow root mode (open/closed)
mode: shadowSelector.mode mode: shadowSelector.mode
} : null } : null
}; };