Merge pull request #488 from getmaxun/highlight-fix
feat: better DOM parsing and highlighting
This commit is contained in:
@@ -24,10 +24,40 @@ export const getElementInformation = async (
|
||||
const elementInfo = await page.evaluate(
|
||||
async ({ x, y }) => {
|
||||
const getDeepestElementFromPoint = (x: number, y: number): HTMLElement | null => {
|
||||
let element = document.elementFromPoint(x, y) as HTMLElement;
|
||||
if (!element) return null;
|
||||
let elements = document.elementsFromPoint(x, y) as HTMLElement[];
|
||||
if (!elements.length) return null;
|
||||
|
||||
const findDeepestElement = (elements: HTMLElement[]): HTMLElement | null => {
|
||||
if (!elements.length) return null;
|
||||
if (elements.length === 1) return elements[0];
|
||||
|
||||
let deepestElement = elements[0];
|
||||
let maxDepth = 0;
|
||||
|
||||
for (const element of elements) {
|
||||
let depth = 0;
|
||||
let current = element;
|
||||
|
||||
while (current) {
|
||||
depth++;
|
||||
if (current.parentElement) {
|
||||
current = current.parentElement;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (depth > maxDepth) {
|
||||
maxDepth = depth;
|
||||
deepestElement = element;
|
||||
}
|
||||
}
|
||||
|
||||
return deepestElement;
|
||||
};
|
||||
|
||||
let deepestElement = element;
|
||||
let deepestElement = findDeepestElement(elements);
|
||||
if (!deepestElement) return null;
|
||||
|
||||
const traverseShadowDOM = (element: HTMLElement): HTMLElement => {
|
||||
let current = element;
|
||||
@@ -50,7 +80,7 @@ export const getElementInformation = async (
|
||||
};
|
||||
|
||||
const isInFrameset = () => {
|
||||
let node = element;
|
||||
let node = deepestElement;
|
||||
while (node && node.parentElement) {
|
||||
if (node.tagName === 'FRAMESET' || node.tagName === 'FRAME') {
|
||||
return true;
|
||||
@@ -60,8 +90,8 @@ export const getElementInformation = async (
|
||||
return false;
|
||||
};
|
||||
|
||||
if (element.tagName === 'IFRAME') {
|
||||
let currentIframe = element as HTMLIFrameElement;
|
||||
if (deepestElement.tagName === 'IFRAME') {
|
||||
let currentIframe = deepestElement as HTMLIFrameElement;
|
||||
let depth = 0;
|
||||
const MAX_IFRAME_DEPTH = 4;
|
||||
|
||||
@@ -91,11 +121,11 @@ export const getElementInformation = async (
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (element.tagName === 'FRAME' || isInFrameset()) {
|
||||
else if (deepestElement.tagName === 'FRAME' || isInFrameset()) {
|
||||
const framesToCheck = [];
|
||||
|
||||
if (element.tagName === 'FRAME') {
|
||||
framesToCheck.push(element as HTMLFrameElement);
|
||||
if (deepestElement.tagName === 'FRAME') {
|
||||
framesToCheck.push(deepestElement as HTMLFrameElement);
|
||||
}
|
||||
|
||||
if (isInFrameset()) {
|
||||
@@ -145,7 +175,7 @@ export const getElementInformation = async (
|
||||
|
||||
processFrames(framesToCheck, frameDepth);
|
||||
} else {
|
||||
deepestElement = traverseShadowDOM(element);
|
||||
deepestElement = traverseShadowDOM(deepestElement);
|
||||
}
|
||||
|
||||
return deepestElement;
|
||||
@@ -275,10 +305,40 @@ export const getElementInformation = async (
|
||||
const elementInfo = await page.evaluate(
|
||||
async ({ x, y }) => {
|
||||
const getDeepestElementFromPoint = (x: number, y: number): HTMLElement | null => {
|
||||
let element = document.elementFromPoint(x, y) as HTMLElement;
|
||||
if (!element) return null;
|
||||
let elements = document.elementsFromPoint(x, y) as HTMLElement[];
|
||||
if (!elements.length) return null;
|
||||
|
||||
const findDeepestElement = (elements: HTMLElement[]): HTMLElement | null => {
|
||||
if (!elements.length) return null;
|
||||
if (elements.length === 1) return elements[0];
|
||||
|
||||
let deepestElement = elements[0];
|
||||
let maxDepth = 0;
|
||||
|
||||
for (const element of elements) {
|
||||
let depth = 0;
|
||||
let current = element;
|
||||
|
||||
while (current) {
|
||||
depth++;
|
||||
if (current.parentElement) {
|
||||
current = current.parentElement;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (depth > maxDepth) {
|
||||
maxDepth = depth;
|
||||
deepestElement = element;
|
||||
}
|
||||
}
|
||||
|
||||
return deepestElement;
|
||||
};
|
||||
|
||||
let deepestElement = element;
|
||||
let deepestElement = findDeepestElement(elements);
|
||||
if (!deepestElement) return null;
|
||||
|
||||
const traverseShadowDOM = (element: HTMLElement): HTMLElement => {
|
||||
let current = element;
|
||||
@@ -301,7 +361,7 @@ export const getElementInformation = async (
|
||||
};
|
||||
|
||||
const isInFrameset = () => {
|
||||
let node = element;
|
||||
let node = deepestElement;
|
||||
while (node && node.parentElement) {
|
||||
if (node.tagName === 'FRAMESET' || node.tagName === 'FRAME') {
|
||||
return true;
|
||||
@@ -311,8 +371,8 @@ export const getElementInformation = async (
|
||||
return false;
|
||||
};
|
||||
|
||||
if (element.tagName === 'IFRAME') {
|
||||
let currentIframe = element as HTMLIFrameElement;
|
||||
if (deepestElement.tagName === 'IFRAME') {
|
||||
let currentIframe = deepestElement as HTMLIFrameElement;
|
||||
let depth = 0;
|
||||
const MAX_IFRAME_DEPTH = 4;
|
||||
|
||||
@@ -342,11 +402,11 @@ export const getElementInformation = async (
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (element.tagName === 'FRAME' || isInFrameset()) {
|
||||
else if (deepestElement.tagName === 'FRAME' || isInFrameset()) {
|
||||
const framesToCheck = [];
|
||||
|
||||
if (element.tagName === 'FRAME') {
|
||||
framesToCheck.push(element as HTMLFrameElement);
|
||||
if (deepestElement.tagName === 'FRAME') {
|
||||
framesToCheck.push(deepestElement as HTMLFrameElement);
|
||||
}
|
||||
|
||||
if (isInFrameset()) {
|
||||
@@ -396,7 +456,7 @@ export const getElementInformation = async (
|
||||
|
||||
processFrames(framesToCheck, frameDepth);
|
||||
} else {
|
||||
deepestElement = traverseShadowDOM(element);
|
||||
deepestElement = traverseShadowDOM(deepestElement);
|
||||
}
|
||||
|
||||
return deepestElement;
|
||||
@@ -412,40 +472,6 @@ export const getElementInformation = async (
|
||||
element = tableParent;
|
||||
}
|
||||
}
|
||||
|
||||
if (element.tagName !== 'TABLE') {
|
||||
while (element.parentElement) {
|
||||
if (element.tagName.toLowerCase() === 'body' ||
|
||||
element.tagName.toLowerCase() === 'html') {
|
||||
break;
|
||||
}
|
||||
|
||||
const parentRect = element.parentElement.getBoundingClientRect();
|
||||
const childRect = element.getBoundingClientRect();
|
||||
|
||||
const fullyContained =
|
||||
parentRect.left <= childRect.left &&
|
||||
parentRect.right >= childRect.right &&
|
||||
parentRect.top <= childRect.top &&
|
||||
parentRect.bottom >= childRect.bottom;
|
||||
|
||||
const significantOverlap =
|
||||
(childRect.width * childRect.height) /
|
||||
(parentRect.width * parentRect.height) > 0.1;
|
||||
|
||||
if (fullyContained && significantOverlap) {
|
||||
const nextParent = element.parentElement;
|
||||
if (nextParent.tagName.toLowerCase() !== 'body' &&
|
||||
nextParent.tagName.toLowerCase() !== 'html') {
|
||||
element = nextParent;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const ownerDocument = element.ownerDocument;
|
||||
const frameElement = ownerDocument?.defaultView?.frameElement;
|
||||
@@ -573,10 +599,40 @@ export const getRect = async (page: Page, coordinates: Coordinates, listSelector
|
||||
const rect = await page.evaluate(
|
||||
async ({ x, y }) => {
|
||||
const getDeepestElementFromPoint = (x: number, y: number): HTMLElement | null => {
|
||||
let element = document.elementFromPoint(x, y) as HTMLElement;
|
||||
if (!element) return null;
|
||||
let elements = document.elementsFromPoint(x, y) as HTMLElement[];
|
||||
if (!elements.length) return null;
|
||||
|
||||
const findDeepestElement = (elements: HTMLElement[]): HTMLElement | null => {
|
||||
if (!elements.length) return null;
|
||||
if (elements.length === 1) return elements[0];
|
||||
|
||||
let deepestElement = elements[0];
|
||||
let maxDepth = 0;
|
||||
|
||||
for (const element of elements) {
|
||||
let depth = 0;
|
||||
let current = element;
|
||||
|
||||
while (current) {
|
||||
depth++;
|
||||
if (current.parentElement) {
|
||||
current = current.parentElement;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (depth > maxDepth) {
|
||||
maxDepth = depth;
|
||||
deepestElement = element;
|
||||
}
|
||||
}
|
||||
|
||||
return deepestElement;
|
||||
};
|
||||
|
||||
let deepestElement = element;
|
||||
let deepestElement = findDeepestElement(elements);
|
||||
if (!deepestElement) return null;
|
||||
|
||||
const traverseShadowDOM = (element: HTMLElement): HTMLElement => {
|
||||
let current = element;
|
||||
@@ -599,7 +655,7 @@ export const getRect = async (page: Page, coordinates: Coordinates, listSelector
|
||||
};
|
||||
|
||||
const isInFrameset = () => {
|
||||
let node = element;
|
||||
let node = deepestElement;
|
||||
while (node && node.parentElement) {
|
||||
if (node.tagName === 'FRAMESET' || node.tagName === 'FRAME') {
|
||||
return true;
|
||||
@@ -609,8 +665,8 @@ export const getRect = async (page: Page, coordinates: Coordinates, listSelector
|
||||
return false;
|
||||
};
|
||||
|
||||
if (element.tagName === 'IFRAME') {
|
||||
let currentIframe = element as HTMLIFrameElement;
|
||||
if (deepestElement.tagName === 'IFRAME') {
|
||||
let currentIframe = deepestElement as HTMLIFrameElement;
|
||||
let depth = 0;
|
||||
const MAX_IFRAME_DEPTH = 4;
|
||||
|
||||
@@ -640,11 +696,11 @@ export const getRect = async (page: Page, coordinates: Coordinates, listSelector
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (element.tagName === 'FRAME' || isInFrameset()) {
|
||||
else if (deepestElement.tagName === 'FRAME' || isInFrameset()) {
|
||||
const framesToCheck = [];
|
||||
|
||||
if (element.tagName === 'FRAME') {
|
||||
framesToCheck.push(element as HTMLFrameElement);
|
||||
if (deepestElement.tagName === 'FRAME') {
|
||||
framesToCheck.push(deepestElement as HTMLFrameElement);
|
||||
}
|
||||
|
||||
if (isInFrameset()) {
|
||||
@@ -694,7 +750,7 @@ export const getRect = async (page: Page, coordinates: Coordinates, listSelector
|
||||
|
||||
processFrames(framesToCheck, frameDepth);
|
||||
} else {
|
||||
deepestElement = traverseShadowDOM(element);
|
||||
deepestElement = traverseShadowDOM(deepestElement);
|
||||
}
|
||||
|
||||
return deepestElement;
|
||||
@@ -766,10 +822,40 @@ export const getRect = async (page: Page, coordinates: Coordinates, listSelector
|
||||
const rect = await page.evaluate(
|
||||
async ({ x, y }) => {
|
||||
const getDeepestElementFromPoint = (x: number, y: number): HTMLElement | null => {
|
||||
let element = document.elementFromPoint(x, y) as HTMLElement;
|
||||
if (!element) return null;
|
||||
let elements = document.elementsFromPoint(x, y) as HTMLElement[];
|
||||
if (!elements.length) return null;
|
||||
|
||||
const findDeepestElement = (elements: HTMLElement[]): HTMLElement | null => {
|
||||
if (!elements.length) return null;
|
||||
if (elements.length === 1) return elements[0];
|
||||
|
||||
let deepestElement = elements[0];
|
||||
let maxDepth = 0;
|
||||
|
||||
for (const element of elements) {
|
||||
let depth = 0;
|
||||
let current = element;
|
||||
|
||||
while (current) {
|
||||
depth++;
|
||||
if (current.parentElement) {
|
||||
current = current.parentElement;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (depth > maxDepth) {
|
||||
maxDepth = depth;
|
||||
deepestElement = element;
|
||||
}
|
||||
}
|
||||
|
||||
return deepestElement;
|
||||
};
|
||||
|
||||
let deepestElement = element;
|
||||
let deepestElement = findDeepestElement(elements);
|
||||
if (!deepestElement) return null;
|
||||
|
||||
const traverseShadowDOM = (element: HTMLElement): HTMLElement => {
|
||||
let current = element;
|
||||
@@ -792,7 +878,7 @@ export const getRect = async (page: Page, coordinates: Coordinates, listSelector
|
||||
};
|
||||
|
||||
const isInFrameset = () => {
|
||||
let node = element;
|
||||
let node = deepestElement;
|
||||
while (node && node.parentElement) {
|
||||
if (node.tagName === 'FRAMESET' || node.tagName === 'FRAME') {
|
||||
return true;
|
||||
@@ -802,8 +888,8 @@ export const getRect = async (page: Page, coordinates: Coordinates, listSelector
|
||||
return false;
|
||||
};
|
||||
|
||||
if (element.tagName === 'IFRAME') {
|
||||
let currentIframe = element as HTMLIFrameElement;
|
||||
if (deepestElement.tagName === 'IFRAME') {
|
||||
let currentIframe = deepestElement as HTMLIFrameElement;
|
||||
let depth = 0;
|
||||
const MAX_IFRAME_DEPTH = 4;
|
||||
|
||||
@@ -833,11 +919,11 @@ export const getRect = async (page: Page, coordinates: Coordinates, listSelector
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (element.tagName === 'FRAME' || isInFrameset()) {
|
||||
else if (deepestElement.tagName === 'FRAME' || isInFrameset()) {
|
||||
const framesToCheck = [];
|
||||
|
||||
if (element.tagName === 'FRAME') {
|
||||
framesToCheck.push(element as HTMLFrameElement);
|
||||
if (deepestElement.tagName === 'FRAME') {
|
||||
framesToCheck.push(deepestElement as HTMLFrameElement);
|
||||
}
|
||||
|
||||
if (isInFrameset()) {
|
||||
@@ -887,7 +973,7 @@ export const getRect = async (page: Page, coordinates: Coordinates, listSelector
|
||||
|
||||
processFrames(framesToCheck, frameDepth);
|
||||
} else {
|
||||
deepestElement = traverseShadowDOM(element);
|
||||
deepestElement = traverseShadowDOM(deepestElement);
|
||||
}
|
||||
|
||||
return deepestElement;
|
||||
@@ -904,40 +990,6 @@ export const getRect = async (page: Page, coordinates: Coordinates, listSelector
|
||||
}
|
||||
}
|
||||
|
||||
if (element.tagName !== 'TABLE') {
|
||||
while (element.parentElement) {
|
||||
if (element.tagName.toLowerCase() === 'body' ||
|
||||
element.tagName.toLowerCase() === 'html') {
|
||||
break;
|
||||
}
|
||||
|
||||
const parentRect = element.parentElement.getBoundingClientRect();
|
||||
const childRect = element.getBoundingClientRect();
|
||||
|
||||
const fullyContained =
|
||||
parentRect.left <= childRect.left &&
|
||||
parentRect.right >= childRect.right &&
|
||||
parentRect.top <= childRect.top &&
|
||||
parentRect.bottom >= childRect.bottom;
|
||||
|
||||
const significantOverlap =
|
||||
(childRect.width * childRect.height) /
|
||||
(parentRect.width * parentRect.height) > 0.1;
|
||||
|
||||
if (fullyContained && significantOverlap) {
|
||||
const nextParent = element.parentElement;
|
||||
if (nextParent.tagName.toLowerCase() !== 'body' &&
|
||||
nextParent.tagName.toLowerCase() !== 'html') {
|
||||
element = nextParent;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const rectangle = element?.getBoundingClientRect();
|
||||
if (rectangle) {
|
||||
const createRectObject = (rect: DOMRect) => ({
|
||||
@@ -1477,10 +1529,40 @@ export const getSelectors = async (page: Page, coordinates: Coordinates) => {
|
||||
}
|
||||
|
||||
const getDeepestElementFromPoint = (x: number, y: number): HTMLElement | null => {
|
||||
let element = document.elementFromPoint(x, y) as HTMLElement;
|
||||
if (!element) return null;
|
||||
let elements = document.elementsFromPoint(x, y) as HTMLElement[];
|
||||
if (!elements.length) return null;
|
||||
|
||||
const findDeepestElement = (elements: HTMLElement[]): HTMLElement | null => {
|
||||
if (!elements.length) return null;
|
||||
if (elements.length === 1) return elements[0];
|
||||
|
||||
let deepestElement = elements[0];
|
||||
let maxDepth = 0;
|
||||
|
||||
for (const element of elements) {
|
||||
let depth = 0;
|
||||
let current = element;
|
||||
|
||||
while (current) {
|
||||
depth++;
|
||||
if (current.parentElement) {
|
||||
current = current.parentElement;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (depth > maxDepth) {
|
||||
maxDepth = depth;
|
||||
deepestElement = element;
|
||||
}
|
||||
}
|
||||
|
||||
return deepestElement;
|
||||
};
|
||||
|
||||
let deepestElement = element;
|
||||
let deepestElement = findDeepestElement(elements);
|
||||
if (!deepestElement) return null;
|
||||
|
||||
const traverseShadowDOM = (element: HTMLElement): HTMLElement => {
|
||||
let current = element;
|
||||
@@ -1503,7 +1585,7 @@ export const getSelectors = async (page: Page, coordinates: Coordinates) => {
|
||||
};
|
||||
|
||||
const isInFrameset = () => {
|
||||
let node = element;
|
||||
let node = deepestElement;
|
||||
while (node && node.parentElement) {
|
||||
if (node.tagName === 'FRAMESET' || node.tagName === 'FRAME') {
|
||||
return true;
|
||||
@@ -1513,8 +1595,8 @@ export const getSelectors = async (page: Page, coordinates: Coordinates) => {
|
||||
return false;
|
||||
};
|
||||
|
||||
if (element.tagName === 'IFRAME') {
|
||||
let currentIframe = element as HTMLIFrameElement;
|
||||
if (deepestElement.tagName === 'IFRAME') {
|
||||
let currentIframe = deepestElement as HTMLIFrameElement;
|
||||
let depth = 0;
|
||||
const MAX_IFRAME_DEPTH = 4;
|
||||
|
||||
@@ -1544,11 +1626,11 @@ export const getSelectors = async (page: Page, coordinates: Coordinates) => {
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (element.tagName === 'FRAME' || isInFrameset()) {
|
||||
else if (deepestElement.tagName === 'FRAME' || isInFrameset()) {
|
||||
const framesToCheck = [];
|
||||
|
||||
if (element.tagName === 'FRAME') {
|
||||
framesToCheck.push(element as HTMLFrameElement);
|
||||
if (deepestElement.tagName === 'FRAME') {
|
||||
framesToCheck.push(deepestElement as HTMLFrameElement);
|
||||
}
|
||||
|
||||
if (isInFrameset()) {
|
||||
@@ -1598,7 +1680,7 @@ export const getSelectors = async (page: Page, coordinates: Coordinates) => {
|
||||
|
||||
processFrames(framesToCheck, frameDepth);
|
||||
} else {
|
||||
deepestElement = traverseShadowDOM(element);
|
||||
deepestElement = traverseShadowDOM(deepestElement);
|
||||
}
|
||||
|
||||
return deepestElement;
|
||||
@@ -1922,10 +2004,40 @@ export const getNonUniqueSelectors = async (page: Page, coordinates: Coordinates
|
||||
if (!listSelector) {
|
||||
const selectors = await page.evaluate(({ x, y }: { x: number, y: number }) => {
|
||||
const getDeepestElementFromPoint = (x: number, y: number): HTMLElement | null => {
|
||||
let element = document.elementFromPoint(x, y) as HTMLElement;
|
||||
if (!element) return null;
|
||||
let elements = document.elementsFromPoint(x, y) as HTMLElement[];
|
||||
if (!elements.length) return null;
|
||||
|
||||
const findDeepestElement = (elements: HTMLElement[]): HTMLElement | null => {
|
||||
if (!elements.length) return null;
|
||||
if (elements.length === 1) return elements[0];
|
||||
|
||||
let deepestElement = elements[0];
|
||||
let maxDepth = 0;
|
||||
|
||||
for (const element of elements) {
|
||||
let depth = 0;
|
||||
let current = element;
|
||||
|
||||
while (current) {
|
||||
depth++;
|
||||
if (current.parentElement) {
|
||||
current = current.parentElement;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (depth > maxDepth) {
|
||||
maxDepth = depth;
|
||||
deepestElement = element;
|
||||
}
|
||||
}
|
||||
|
||||
return deepestElement;
|
||||
};
|
||||
|
||||
let deepestElement = element;
|
||||
let deepestElement = findDeepestElement(elements);
|
||||
if (!deepestElement) return null;
|
||||
|
||||
const traverseShadowDOM = (element: HTMLElement): HTMLElement => {
|
||||
let current = element;
|
||||
@@ -1948,7 +2060,7 @@ export const getNonUniqueSelectors = async (page: Page, coordinates: Coordinates
|
||||
};
|
||||
|
||||
const isInFrameset = () => {
|
||||
let node = element;
|
||||
let node = deepestElement;
|
||||
while (node && node.parentElement) {
|
||||
if (node.tagName === 'FRAMESET' || node.tagName === 'FRAME') {
|
||||
return true;
|
||||
@@ -1958,8 +2070,8 @@ export const getNonUniqueSelectors = async (page: Page, coordinates: Coordinates
|
||||
return false;
|
||||
};
|
||||
|
||||
if (element.tagName === 'IFRAME') {
|
||||
let currentIframe = element as HTMLIFrameElement;
|
||||
if (deepestElement.tagName === 'IFRAME') {
|
||||
let currentIframe = deepestElement as HTMLIFrameElement;
|
||||
let depth = 0;
|
||||
const MAX_IFRAME_DEPTH = 4;
|
||||
|
||||
@@ -1989,11 +2101,11 @@ export const getNonUniqueSelectors = async (page: Page, coordinates: Coordinates
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (element.tagName === 'FRAME' || isInFrameset()) {
|
||||
else if (deepestElement.tagName === 'FRAME' || isInFrameset()) {
|
||||
const framesToCheck = [];
|
||||
|
||||
if (element.tagName === 'FRAME') {
|
||||
framesToCheck.push(element as HTMLFrameElement);
|
||||
if (deepestElement.tagName === 'FRAME') {
|
||||
framesToCheck.push(deepestElement as HTMLFrameElement);
|
||||
}
|
||||
|
||||
if (isInFrameset()) {
|
||||
@@ -2043,7 +2155,7 @@ export const getNonUniqueSelectors = async (page: Page, coordinates: Coordinates
|
||||
|
||||
processFrames(framesToCheck, frameDepth);
|
||||
} else {
|
||||
deepestElement = traverseShadowDOM(element);
|
||||
deepestElement = traverseShadowDOM(deepestElement);
|
||||
}
|
||||
|
||||
return deepestElement;
|
||||
@@ -2085,25 +2197,18 @@ export const getNonUniqueSelectors = async (page: Page, coordinates: Coordinates
|
||||
|
||||
if (element.parentElement) {
|
||||
const siblings = Array.from(element.parentElement.children);
|
||||
const identicalSiblings = siblings.filter(sibling => {
|
||||
if (sibling === element) return false;
|
||||
|
||||
let siblingSelector = sibling.tagName.toLowerCase();
|
||||
const siblingClassName = typeof sibling.className === 'string' ? sibling.className : '';
|
||||
if (siblingClassName) {
|
||||
const siblingClasses = siblingClassName.split(/\s+/).filter(Boolean);
|
||||
const validSiblingClasses = siblingClasses.filter(cls => !cls.startsWith('!') && !cls.includes(':'));
|
||||
if (validSiblingClasses.length > 0) {
|
||||
siblingSelector += '.' + validSiblingClasses.map(cls => CSS.escape(cls)).join('.');
|
||||
}
|
||||
}
|
||||
|
||||
return siblingSelector === baseSelector;
|
||||
});
|
||||
|
||||
if (identicalSiblings.length > 0) {
|
||||
const elementClasses = Array.from(element.classList || []);
|
||||
|
||||
const similarSiblings = siblings.filter(sibling => {
|
||||
if (sibling === element) return false;
|
||||
const siblingClasses = Array.from(sibling.classList || []);
|
||||
return siblingClasses.some(cls => elementClasses.includes(cls));
|
||||
});
|
||||
|
||||
if (similarSiblings.length > 0) {
|
||||
const position = siblings.indexOf(element) + 1;
|
||||
return `${baseSelector}:nth-child(${position})`;
|
||||
selector += `:nth-child(${position})`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2128,23 +2233,16 @@ export const getNonUniqueSelectors = async (page: Page, coordinates: Coordinates
|
||||
|
||||
if (element.parentElement) {
|
||||
const siblings = Array.from(element.parentElement.children);
|
||||
const identicalSiblings = siblings.filter(sibling => {
|
||||
|
||||
const elementClasses = Array.from(element.classList || []);
|
||||
|
||||
const similarSiblings = siblings.filter(sibling => {
|
||||
if (sibling === element) return false;
|
||||
|
||||
let siblingSelector = sibling.tagName.toLowerCase();
|
||||
const siblingClassName = typeof sibling.className === 'string' ? sibling.className : '';
|
||||
if (siblingClassName) {
|
||||
const siblingClasses = siblingClassName.split(/\s+/).filter(Boolean);
|
||||
const validSiblingClasses = siblingClasses.filter(cls => !cls.startsWith('!') && !cls.includes(':'));
|
||||
if (validSiblingClasses.length > 0) {
|
||||
siblingSelector += '.' + validSiblingClasses.map(cls => CSS.escape(cls)).join('.');
|
||||
}
|
||||
}
|
||||
|
||||
return siblingSelector === selector;
|
||||
const siblingClasses = Array.from(sibling.classList || []);
|
||||
return siblingClasses.some(cls => elementClasses.includes(cls));
|
||||
});
|
||||
|
||||
if (identicalSiblings.length > 0) {
|
||||
|
||||
if (similarSiblings.length > 0) {
|
||||
const position = siblings.indexOf(element) + 1;
|
||||
selector += `:nth-child(${position})`;
|
||||
}
|
||||
@@ -2253,40 +2351,6 @@ export const getNonUniqueSelectors = async (page: Page, coordinates: Coordinates
|
||||
}
|
||||
}
|
||||
|
||||
if (element.tagName !== 'TABLE') {
|
||||
while (element.parentElement) {
|
||||
if (element.tagName.toLowerCase() === 'body' ||
|
||||
element.tagName.toLowerCase() === 'html') {
|
||||
break;
|
||||
}
|
||||
|
||||
const parentRect = element.parentElement.getBoundingClientRect();
|
||||
const childRect = element.getBoundingClientRect();
|
||||
|
||||
const fullyContained =
|
||||
parentRect.left <= childRect.left &&
|
||||
parentRect.right >= childRect.right &&
|
||||
parentRect.top <= childRect.top &&
|
||||
parentRect.bottom >= childRect.bottom;
|
||||
|
||||
const significantOverlap =
|
||||
(childRect.width * childRect.height) /
|
||||
(parentRect.width * parentRect.height) > 0.1;
|
||||
|
||||
if (fullyContained && significantOverlap) {
|
||||
const nextParent = element.parentElement;
|
||||
if (nextParent.tagName.toLowerCase() !== 'body' &&
|
||||
nextParent.tagName.toLowerCase() !== 'html') {
|
||||
element = nextParent;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const generalSelector = getSelectorPath(element);
|
||||
return { generalSelector };
|
||||
}, coordinates);
|
||||
@@ -2296,10 +2360,40 @@ export const getNonUniqueSelectors = async (page: Page, coordinates: Coordinates
|
||||
// When we have a list selector, we need special handling while maintaining shadow DOM and frame support
|
||||
const selectors = await page.evaluate(({ x, y }: { x: number, y: number }) => {
|
||||
const getDeepestElementFromPoint = (x: number, y: number): HTMLElement | null => {
|
||||
let element = document.elementFromPoint(x, y) as HTMLElement;
|
||||
if (!element) return null;
|
||||
let elements = document.elementsFromPoint(x, y) as HTMLElement[];
|
||||
if (!elements.length) return null;
|
||||
|
||||
const findDeepestElement = (elements: HTMLElement[]): HTMLElement | null => {
|
||||
if (!elements.length) return null;
|
||||
if (elements.length === 1) return elements[0];
|
||||
|
||||
let deepestElement = elements[0];
|
||||
let maxDepth = 0;
|
||||
|
||||
for (const element of elements) {
|
||||
let depth = 0;
|
||||
let current = element;
|
||||
|
||||
while (current) {
|
||||
depth++;
|
||||
if (current.parentElement) {
|
||||
current = current.parentElement;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (depth > maxDepth) {
|
||||
maxDepth = depth;
|
||||
deepestElement = element;
|
||||
}
|
||||
}
|
||||
|
||||
return deepestElement;
|
||||
};
|
||||
|
||||
let deepestElement = element;
|
||||
let deepestElement = findDeepestElement(elements);
|
||||
if (!deepestElement) return null;
|
||||
|
||||
const traverseShadowDOM = (element: HTMLElement): HTMLElement => {
|
||||
let current = element;
|
||||
@@ -2322,7 +2416,7 @@ export const getNonUniqueSelectors = async (page: Page, coordinates: Coordinates
|
||||
};
|
||||
|
||||
const isInFrameset = () => {
|
||||
let node = element;
|
||||
let node = deepestElement;
|
||||
while (node && node.parentElement) {
|
||||
if (node.tagName === 'FRAMESET' || node.tagName === 'FRAME') {
|
||||
return true;
|
||||
@@ -2332,8 +2426,8 @@ export const getNonUniqueSelectors = async (page: Page, coordinates: Coordinates
|
||||
return false;
|
||||
};
|
||||
|
||||
if (element.tagName === 'IFRAME') {
|
||||
let currentIframe = element as HTMLIFrameElement;
|
||||
if (deepestElement.tagName === 'IFRAME') {
|
||||
let currentIframe = deepestElement as HTMLIFrameElement;
|
||||
let depth = 0;
|
||||
const MAX_IFRAME_DEPTH = 4;
|
||||
|
||||
@@ -2363,11 +2457,11 @@ export const getNonUniqueSelectors = async (page: Page, coordinates: Coordinates
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (element.tagName === 'FRAME' || isInFrameset()) {
|
||||
else if (deepestElement.tagName === 'FRAME' || isInFrameset()) {
|
||||
const framesToCheck = [];
|
||||
|
||||
if (element.tagName === 'FRAME') {
|
||||
framesToCheck.push(element as HTMLFrameElement);
|
||||
if (deepestElement.tagName === 'FRAME') {
|
||||
framesToCheck.push(deepestElement as HTMLFrameElement);
|
||||
}
|
||||
|
||||
if (isInFrameset()) {
|
||||
@@ -2417,7 +2511,7 @@ export const getNonUniqueSelectors = async (page: Page, coordinates: Coordinates
|
||||
|
||||
processFrames(framesToCheck, frameDepth);
|
||||
} else {
|
||||
deepestElement = traverseShadowDOM(element);
|
||||
deepestElement = traverseShadowDOM(deepestElement);
|
||||
}
|
||||
|
||||
return deepestElement;
|
||||
@@ -2459,25 +2553,18 @@ export const getNonUniqueSelectors = async (page: Page, coordinates: Coordinates
|
||||
|
||||
if (element.parentElement) {
|
||||
const siblings = Array.from(element.parentElement.children);
|
||||
const identicalSiblings = siblings.filter(sibling => {
|
||||
|
||||
const elementClasses = Array.from(element.classList || []);
|
||||
|
||||
const similarSiblings = siblings.filter(sibling => {
|
||||
if (sibling === element) return false;
|
||||
|
||||
let siblingSelector = sibling.tagName.toLowerCase();
|
||||
const siblingClassName = typeof sibling.className === 'string' ? sibling.className : '';
|
||||
if (siblingClassName) {
|
||||
const siblingClasses = siblingClassName.split(/\s+/).filter(Boolean);
|
||||
const validSiblingClasses = siblingClasses.filter(cls => !cls.startsWith('!') && !cls.includes(':'));
|
||||
if (validSiblingClasses.length > 0) {
|
||||
siblingSelector += '.' + validSiblingClasses.map(cls => CSS.escape(cls)).join('.');
|
||||
}
|
||||
}
|
||||
|
||||
return siblingSelector === baseSelector;
|
||||
const siblingClasses = Array.from(sibling.classList || []);
|
||||
return siblingClasses.some(cls => elementClasses.includes(cls));
|
||||
});
|
||||
|
||||
if (identicalSiblings.length > 0) {
|
||||
|
||||
if (similarSiblings.length > 0) {
|
||||
const position = siblings.indexOf(element) + 1;
|
||||
return `${baseSelector}:nth-child(${position})`;
|
||||
selector += `:nth-child(${position})`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2502,23 +2589,16 @@ export const getNonUniqueSelectors = async (page: Page, coordinates: Coordinates
|
||||
|
||||
if (element.parentElement) {
|
||||
const siblings = Array.from(element.parentElement.children);
|
||||
const identicalSiblings = siblings.filter(sibling => {
|
||||
|
||||
const elementClasses = Array.from(element.classList || []);
|
||||
|
||||
const similarSiblings = siblings.filter(sibling => {
|
||||
if (sibling === element) return false;
|
||||
|
||||
let siblingSelector = sibling.tagName.toLowerCase();
|
||||
const siblingClassName = typeof sibling.className === 'string' ? sibling.className : '';
|
||||
if (siblingClassName) {
|
||||
const siblingClasses = siblingClassName.split(/\s+/).filter(Boolean);
|
||||
const validSiblingClasses = siblingClasses.filter(cls => !cls.startsWith('!') && !cls.includes(':'));
|
||||
if (validSiblingClasses.length > 0) {
|
||||
siblingSelector += '.' + validSiblingClasses.map(cls => CSS.escape(cls)).join('.');
|
||||
}
|
||||
}
|
||||
|
||||
return siblingSelector === selector;
|
||||
const siblingClasses = Array.from(sibling.classList || []);
|
||||
return siblingClasses.some(cls => elementClasses.includes(cls));
|
||||
});
|
||||
|
||||
if (identicalSiblings.length > 0) {
|
||||
|
||||
if (similarSiblings.length > 0) {
|
||||
const position = siblings.indexOf(element) + 1;
|
||||
selector += `:nth-child(${position})`;
|
||||
}
|
||||
@@ -2657,25 +2737,17 @@ export const getChildSelectors = async (page: Page, parentSelector: string): Pro
|
||||
}
|
||||
|
||||
if (element.parentElement) {
|
||||
// Look for identical siblings
|
||||
const siblings = Array.from(element.parentElement.children);
|
||||
const identicalSiblings = siblings.filter(sibling => {
|
||||
|
||||
const elementClasses = Array.from(element.classList || []);
|
||||
|
||||
const similarSiblings = siblings.filter(sibling => {
|
||||
if (sibling === element) return false;
|
||||
|
||||
let siblingSelector = sibling.tagName.toLowerCase();
|
||||
const siblingClassName = typeof sibling.className === 'string' ? sibling.className : '';
|
||||
if (siblingClassName) {
|
||||
const siblingClasses = siblingClassName.split(/\s+/).filter(Boolean);
|
||||
const validSiblingClasses = siblingClasses.filter(cls => !cls.startsWith('!') && !cls.includes(':'));
|
||||
if (validSiblingClasses.length > 0) {
|
||||
siblingSelector += '.' + validSiblingClasses.map(cls => CSS.escape(cls)).join('.');
|
||||
}
|
||||
}
|
||||
|
||||
return siblingSelector === selector;
|
||||
const siblingClasses = Array.from(sibling.classList || []);
|
||||
return siblingClasses.some(cls => elementClasses.includes(cls));
|
||||
});
|
||||
|
||||
if (identicalSiblings.length > 0) {
|
||||
|
||||
if (similarSiblings.length > 0) {
|
||||
const position = siblings.indexOf(element) + 1;
|
||||
selector += `:nth-child(${position})`;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user