feat: condtionally handle getRect & getelementInfo
This commit is contained in:
@@ -22,100 +22,144 @@ type Workflow = WorkflowFile["workflow"];
|
|||||||
*/
|
*/
|
||||||
export const getElementInformation = async (
|
export const getElementInformation = async (
|
||||||
page: Page,
|
page: Page,
|
||||||
coordinates: Coordinates
|
coordinates: Coordinates,
|
||||||
|
listSelector: string,
|
||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
const elementInfo = await page.evaluate(
|
if (listSelector !== '') {
|
||||||
async ({ x, y }) => {
|
// Old implementation
|
||||||
const originalEl = document.elementFromPoint(x, y) as HTMLElement;
|
const elementInfo = await page.evaluate(
|
||||||
if (originalEl) {
|
async ({ x, y }) => {
|
||||||
let element = originalEl;
|
const el = document.elementFromPoint(x, y) as HTMLElement;
|
||||||
|
if (el) {
|
||||||
|
const { parentElement } = el;
|
||||||
|
const element = parentElement?.tagName === 'A' ? parentElement : el;
|
||||||
|
let info: {
|
||||||
|
tagName: string;
|
||||||
|
hasOnlyText?: boolean;
|
||||||
|
innerText?: string;
|
||||||
|
url?: string;
|
||||||
|
imageUrl?: string;
|
||||||
|
attributes?: Record<string, string>;
|
||||||
|
innerHTML?: string;
|
||||||
|
outerHTML?: string;
|
||||||
|
} = {
|
||||||
|
tagName: element?.tagName ?? '',
|
||||||
|
};
|
||||||
|
if (element) {
|
||||||
|
info.attributes = Array.from(element.attributes).reduce(
|
||||||
|
(acc, attr) => {
|
||||||
|
acc[attr.name] = attr.value;
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{} as Record<string, string>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// 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;
|
||||||
|
} else {
|
||||||
|
info.hasOnlyText = element?.children?.length === 0 &&
|
||||||
|
element?.innerText?.length > 0;
|
||||||
|
info.innerText = element?.innerText ?? '';
|
||||||
|
}
|
||||||
|
info.innerHTML = element.innerHTML;
|
||||||
|
info.outerHTML = element.outerHTML;
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
{ x: coordinates.x, y: coordinates.y },
|
||||||
|
);
|
||||||
|
return elementInfo;
|
||||||
|
} else {
|
||||||
|
// New implementation
|
||||||
|
const elementInfo = await page.evaluate(
|
||||||
|
async ({ x, y }) => {
|
||||||
|
const originalEl = document.elementFromPoint(x, y) as HTMLElement;
|
||||||
|
if (originalEl) {
|
||||||
|
let element = originalEl;
|
||||||
|
|
||||||
// if (originalEl.tagName === 'A') {
|
const containerTags = ['DIV', 'SECTION', 'ARTICLE', 'MAIN', 'HEADER', 'FOOTER', 'NAV', 'ASIDE',
|
||||||
// element = originalEl;
|
'ADDRESS', 'BLOCKQUOTE', 'DETAILS', 'DIALOG', 'FIGURE', 'FIGCAPTION', 'MAIN', 'MARK', 'SUMMARY', 'TIME',
|
||||||
// } else if (originalEl.parentElement?.tagName === 'A') {
|
'TABLE', 'THEAD', 'TBODY', 'TFOOT', 'TR', 'TH', 'TD', 'CAPTION', 'COLGROUP', 'COL', 'FORM', 'FIELDSET',
|
||||||
// element = originalEl.parentElement;
|
'LEGEND', 'LABEL', 'INPUT', 'BUTTON', 'SELECT', 'DATALIST', 'OPTGROUP', 'OPTION', 'TEXTAREA', 'OUTPUT',
|
||||||
// } else {
|
'PROGRESS', 'METER', 'DETAILS', 'SUMMARY', 'MENU', 'MENUITEM', 'MENUITEM', 'APPLET', 'EMBED', 'OBJECT',
|
||||||
// Generic parent finding logic based on visual containment
|
'PARAM', 'VIDEO', 'AUDIO', 'SOURCE', 'TRACK', 'CANVAS', 'MAP', 'AREA', 'SVG', 'IFRAME', 'FRAME', 'FRAMESET',
|
||||||
const containerTags = ['DIV', 'SECTION', 'ARTICLE', 'MAIN', 'HEADER', 'FOOTER', 'NAV', 'ASIDE',
|
'LI', 'UL', 'OL', 'DL', 'DT', 'DD', 'HR', 'P', 'PRE', 'LISTING', 'PLAINTEXT'
|
||||||
'ADDRESS', 'BLOCKQUOTE', 'DETAILS', 'DIALOG', 'FIGURE', 'FIGCAPTION', 'MAIN', 'MARK', 'SUMMARY', 'TIME',
|
];
|
||||||
'TABLE', 'THEAD', 'TBODY', 'TFOOT', 'TR', 'TH', 'TD', 'CAPTION', 'COLGROUP', 'COL', 'FORM', 'FIELDSET',
|
while (element.parentElement) {
|
||||||
'LEGEND', 'LABEL', 'INPUT', 'BUTTON', 'SELECT', 'DATALIST', 'OPTGROUP', 'OPTION', 'TEXTAREA', 'OUTPUT',
|
const parentRect = element.parentElement.getBoundingClientRect();
|
||||||
'PROGRESS', 'METER', 'DETAILS', 'SUMMARY', 'MENU', 'MENUITEM', 'MENUITEM', 'APPLET', 'EMBED', 'OBJECT',
|
const childRect = element.getBoundingClientRect();
|
||||||
'PARAM', 'VIDEO', 'AUDIO', 'SOURCE', 'TRACK', 'CANVAS', 'MAP', 'AREA', 'SVG', 'IFRAME', 'FRAME', 'FRAMESET',
|
|
||||||
'LI', 'UL', 'OL', 'DL', 'DT', 'DD', 'HR', 'P', 'PRE', 'LISTING', 'PLAINTEXT', 'A'
|
|
||||||
];
|
|
||||||
while (element.parentElement) {
|
|
||||||
const parentRect = element.parentElement.getBoundingClientRect();
|
|
||||||
const childRect = element.getBoundingClientRect();
|
|
||||||
|
|
||||||
if (!containerTags.includes(element.parentElement.tagName)) {
|
if (!containerTags.includes(element.parentElement.tagName)) {
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
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.5;
|
||||||
|
|
||||||
|
if (fullyContained && significantOverlap) {
|
||||||
|
element = element.parentElement;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if parent visually contains the child
|
let info: {
|
||||||
const fullyContained =
|
tagName: string;
|
||||||
parentRect.left <= childRect.left &&
|
hasOnlyText?: boolean;
|
||||||
parentRect.right >= childRect.right &&
|
innerText?: string;
|
||||||
parentRect.top <= childRect.top &&
|
url?: string;
|
||||||
parentRect.bottom >= childRect.bottom;
|
imageUrl?: string;
|
||||||
|
attributes?: Record<string, string>;
|
||||||
|
innerHTML?: string;
|
||||||
|
outerHTML?: string;
|
||||||
|
} = {
|
||||||
|
tagName: element?.tagName ?? '',
|
||||||
|
};
|
||||||
|
|
||||||
// Additional checks for more comprehensive containment
|
if (element) {
|
||||||
const significantOverlap =
|
info.attributes = Array.from(element.attributes).reduce(
|
||||||
(childRect.width * childRect.height) /
|
(acc, attr) => {
|
||||||
(parentRect.width * parentRect.height) > 0.5;
|
acc[attr.name] = attr.value;
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{} as Record<string, string>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (fullyContained && significantOverlap) {
|
if (element?.tagName === 'A') {
|
||||||
element = element.parentElement;
|
info.url = (element as HTMLAnchorElement).href;
|
||||||
|
info.innerText = element.innerText ?? '';
|
||||||
|
} else if (element?.tagName === 'IMG') {
|
||||||
|
info.imageUrl = (element as HTMLImageElement).src;
|
||||||
} else {
|
} else {
|
||||||
break;
|
info.hasOnlyText = element?.children?.length === 0 &&
|
||||||
// }
|
element?.innerText?.length > 0;
|
||||||
} }
|
info.innerText = element?.innerText ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
let info: {
|
info.innerHTML = element.innerHTML;
|
||||||
tagName: string;
|
info.outerHTML = element.outerHTML;
|
||||||
hasOnlyText?: boolean;
|
return info;
|
||||||
innerText?: string;
|
|
||||||
url?: string;
|
|
||||||
imageUrl?: string;
|
|
||||||
attributes?: Record<string, string>;
|
|
||||||
innerHTML?: string;
|
|
||||||
outerHTML?: string;
|
|
||||||
} = {
|
|
||||||
tagName: element?.tagName ?? '',
|
|
||||||
};
|
|
||||||
|
|
||||||
if (element) {
|
|
||||||
info.attributes = Array.from(element.attributes).reduce(
|
|
||||||
(acc, attr) => {
|
|
||||||
acc[attr.name] = attr.value;
|
|
||||||
return acc;
|
|
||||||
},
|
|
||||||
{} as Record<string, string>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
// Existing tag-specific logic
|
},
|
||||||
if (element?.tagName === 'A') {
|
{ x: coordinates.x, y: coordinates.y },
|
||||||
info.url = (element as HTMLAnchorElement).href;
|
);
|
||||||
info.innerText = element.innerText ?? '';
|
return elementInfo;
|
||||||
} else if (element?.tagName === 'IMG') {
|
}
|
||||||
info.imageUrl = (element as HTMLImageElement).src;
|
|
||||||
} else {
|
|
||||||
info.hasOnlyText = element?.children?.length === 0 &&
|
|
||||||
element?.innerText?.length > 0;
|
|
||||||
info.innerText = element?.innerText ?? '';
|
|
||||||
}
|
|
||||||
|
|
||||||
info.innerHTML = element.innerHTML;
|
|
||||||
info.outerHTML = element.outerHTML;
|
|
||||||
return info;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
{ x: coordinates.x, y: coordinates.y },
|
|
||||||
);
|
|
||||||
return elementInfo;
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const { message, stack } = error as Error;
|
const { message, stack } = error as Error;
|
||||||
console.error('Error while retrieving selector:', message);
|
console.error('Error while retrieving selector:', message);
|
||||||
@@ -123,79 +167,103 @@ export const getElementInformation = async (
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getRect = async (page: Page, coordinates: Coordinates) => {
|
export const getRect = async (page: Page, coordinates: Coordinates, listSelector: string) => {
|
||||||
try {
|
try {
|
||||||
const rect = await page.evaluate(
|
if (listSelector !== '') {
|
||||||
async ({ x, y }) => {
|
// Old implementation
|
||||||
const originalEl = document.elementFromPoint(x, y) as HTMLElement;
|
const rect = await page.evaluate(
|
||||||
if (originalEl) {
|
async ({ x, y }) => {
|
||||||
let element = originalEl;
|
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();
|
||||||
|
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;
|
||||||
|
} else {
|
||||||
|
// New implementation
|
||||||
|
const rect = await page.evaluate(
|
||||||
|
async ({ x, y }) => {
|
||||||
|
const originalEl = document.elementFromPoint(x, y) as HTMLElement;
|
||||||
|
if (originalEl) {
|
||||||
|
let element = originalEl;
|
||||||
|
|
||||||
// if (originalEl.tagName === 'A') {
|
const containerTags = ['DIV', 'SECTION', 'ARTICLE', 'MAIN', 'HEADER', 'FOOTER', 'NAV', 'ASIDE',
|
||||||
// element = originalEl;
|
'ADDRESS', 'BLOCKQUOTE', 'DETAILS', 'DIALOG', 'FIGURE', 'FIGCAPTION', 'MAIN', 'MARK', 'SUMMARY', 'TIME',
|
||||||
// } else if (originalEl.parentElement?.tagName === 'A') {
|
'TABLE', 'THEAD', 'TBODY', 'TFOOT', 'TR', 'TH', 'TD', 'CAPTION', 'COLGROUP', 'COL', 'FORM', 'FIELDSET',
|
||||||
// element = originalEl.parentElement;
|
'LEGEND', 'LABEL', 'INPUT', 'BUTTON', 'SELECT', 'DATALIST', 'OPTGROUP', 'OPTION', 'TEXTAREA', 'OUTPUT',
|
||||||
// } else {
|
'PROGRESS', 'METER', 'DETAILS', 'SUMMARY', 'MENU', 'MENUITEM', 'MENUITEM', 'APPLET', 'EMBED', 'OBJECT',
|
||||||
const containerTags = ['DIV', 'SECTION', 'ARTICLE', 'MAIN', 'HEADER', 'FOOTER', 'NAV', 'ASIDE',
|
'PARAM', 'VIDEO', 'AUDIO', 'SOURCE', 'TRACK', 'CANVAS', 'MAP', 'AREA', 'SVG', 'IFRAME', 'FRAME', 'FRAMESET',
|
||||||
'ADDRESS', 'BLOCKQUOTE', 'DETAILS', 'DIALOG', 'FIGURE', 'FIGCAPTION', 'MAIN', 'MARK', 'SUMMARY', 'TIME',
|
'LI', 'UL', 'OL', 'DL', 'DT', 'DD', 'HR', 'P', 'PRE', 'LISTING', 'PLAINTEXT'
|
||||||
'TABLE', 'THEAD', 'TBODY', 'TFOOT', 'TR', 'TH', 'TD', 'CAPTION', 'COLGROUP', 'COL', 'FORM', 'FIELDSET',
|
];
|
||||||
'LEGEND', 'LABEL', 'INPUT', 'BUTTON', 'SELECT', 'DATALIST', 'OPTGROUP', 'OPTION', 'TEXTAREA', 'OUTPUT',
|
while (element.parentElement) {
|
||||||
'PROGRESS', 'METER', 'DETAILS', 'SUMMARY', 'MENU', 'MENUITEM', 'MENUITEM', 'APPLET', 'EMBED', 'OBJECT',
|
const parentRect = element.parentElement.getBoundingClientRect();
|
||||||
'PARAM', 'VIDEO', 'AUDIO', 'SOURCE', 'TRACK', 'CANVAS', 'MAP', 'AREA', 'SVG', 'IFRAME', 'FRAME', 'FRAMESET',
|
const childRect = element.getBoundingClientRect();
|
||||||
'LI', 'UL', 'OL', 'DL', 'DT', 'DD', 'HR', 'P', 'PRE', 'LISTING', 'PLAINTEXT', 'A'
|
|
||||||
];
|
|
||||||
while (element.parentElement) {
|
|
||||||
const parentRect = element.parentElement.getBoundingClientRect();
|
|
||||||
const childRect = element.getBoundingClientRect();
|
|
||||||
|
|
||||||
if (!containerTags.includes(element.parentElement.tagName)) {
|
if (!containerTags.includes(element.parentElement.tagName)) {
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
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.5;
|
||||||
|
|
||||||
|
if (fullyContained && significantOverlap) {
|
||||||
|
element = element.parentElement;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const rectangle = element?.getBoundingClientRect();
|
||||||
|
|
||||||
const fullyContained =
|
if (rectangle) {
|
||||||
parentRect.left <= childRect.left &&
|
return {
|
||||||
parentRect.right >= childRect.right &&
|
x: rectangle.x,
|
||||||
parentRect.top <= childRect.top &&
|
y: rectangle.y,
|
||||||
parentRect.bottom >= childRect.bottom;
|
width: rectangle.width,
|
||||||
|
height: rectangle.height,
|
||||||
const significantOverlap =
|
top: rectangle.top,
|
||||||
(childRect.width * childRect.height) /
|
right: rectangle.right,
|
||||||
(parentRect.width * parentRect.height) > 0.5;
|
bottom: rectangle.bottom,
|
||||||
|
left: rectangle.left,
|
||||||
if (fullyContained && significantOverlap) {
|
};
|
||||||
element = element.parentElement;
|
}
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
// }
|
|
||||||
}}
|
|
||||||
|
|
||||||
//element = element?.parentElement?.tagName === 'A' ? element?.parentElement : element;
|
|
||||||
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 },
|
{ x: coordinates.x, y: coordinates.y },
|
||||||
);
|
);
|
||||||
return rect;
|
return rect;
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const { message, stack } = error as Error;
|
const { message, stack } = error as Error;
|
||||||
logger.log('error', `Error while retrieving selector: ${message}`);
|
logger.log('error', `Error while retrieving selector: ${message}`);
|
||||||
logger.log('error', `Stack: ${stack}`);
|
logger.log('error', `Stack: ${stack}`);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user