Merge pull request #488 from getmaxun/highlight-fix

feat: better DOM parsing and highlighting
This commit is contained in:
Karishma Shukla
2025-03-20 17:39:13 +05:30
committed by GitHub

View File

@@ -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})`;
}