feat: add logic to get deeply nested shadowDOM elements
This commit is contained in:
@@ -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;
|
const shadowElement = shadowRoot.elementFromPoint(x, y) as HTMLElement;
|
||||||
|
if (!shadowElement || shadowElement === current) break;
|
||||||
deepestElement = shadowElement;
|
|
||||||
current = shadowElement;
|
deepestElement = shadowElement;
|
||||||
} else {
|
current = shadowElement;
|
||||||
break;
|
|
||||||
}
|
|
||||||
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
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user