feat: shadow dom selection
This commit is contained in:
@@ -23,7 +23,28 @@ export const getElementInformation = async (
|
|||||||
if (!getList || listSelector !== '') {
|
if (!getList || listSelector !== '') {
|
||||||
const elementInfo = await page.evaluate(
|
const elementInfo = await page.evaluate(
|
||||||
async ({ x, y }) => {
|
async ({ x, y }) => {
|
||||||
const el = document.elementFromPoint(x, y) as HTMLElement;
|
// Helper function to get element from point including shadow DOM
|
||||||
|
const getDeepestElementFromPoint = (x: number, y: number): HTMLElement | null => {
|
||||||
|
let element = document.elementFromPoint(x, y) as HTMLElement;
|
||||||
|
if (!element) return null;
|
||||||
|
|
||||||
|
// Traverse through shadow roots
|
||||||
|
let current = element;
|
||||||
|
while (current) {
|
||||||
|
// Check if element has shadow root
|
||||||
|
const shadowRoot = current.shadowRoot;
|
||||||
|
if (!shadowRoot) break;
|
||||||
|
|
||||||
|
// Try to find deeper element in shadow DOM
|
||||||
|
const shadowElement = shadowRoot.elementFromPoint(x, y) as HTMLElement;
|
||||||
|
if (!shadowElement || shadowElement === current) break;
|
||||||
|
|
||||||
|
current = shadowElement;
|
||||||
|
}
|
||||||
|
return current;
|
||||||
|
};
|
||||||
|
|
||||||
|
const el = getDeepestElementFromPoint(x, y);
|
||||||
if (el) {
|
if (el) {
|
||||||
const { parentElement } = el;
|
const { parentElement } = el;
|
||||||
const element = parentElement?.tagName === 'A' ? parentElement : el;
|
const element = parentElement?.tagName === 'A' ? parentElement : el;
|
||||||
@@ -36,9 +57,12 @@ export const getElementInformation = async (
|
|||||||
attributes?: Record<string, string>;
|
attributes?: Record<string, string>;
|
||||||
innerHTML?: string;
|
innerHTML?: string;
|
||||||
outerHTML?: string;
|
outerHTML?: string;
|
||||||
|
isShadowRoot?: boolean;
|
||||||
} = {
|
} = {
|
||||||
tagName: element?.tagName ?? '',
|
tagName: element?.tagName ?? '',
|
||||||
|
isShadowRoot: !!element?.shadowRoot
|
||||||
};
|
};
|
||||||
|
|
||||||
if (element) {
|
if (element) {
|
||||||
info.attributes = Array.from(element.attributes).reduce(
|
info.attributes = Array.from(element.attributes).reduce(
|
||||||
(acc, attr) => {
|
(acc, attr) => {
|
||||||
@@ -48,6 +72,7 @@ export const getElementInformation = async (
|
|||||||
{} as Record<string, string>
|
{} as Record<string, string>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gather specific information based on the tag
|
// Gather specific information based on the tag
|
||||||
if (element?.tagName === 'A') {
|
if (element?.tagName === 'A') {
|
||||||
info.url = (element as HTMLAnchorElement).href;
|
info.url = (element as HTMLAnchorElement).href;
|
||||||
@@ -61,7 +86,7 @@ export const getElementInformation = async (
|
|||||||
...info.attributes,
|
...info.attributes,
|
||||||
selectedValue: selectElement.value,
|
selectedValue: selectElement.value,
|
||||||
};
|
};
|
||||||
} else if (element?.tagName === 'INPUT' && (element as HTMLInputElement).type === 'time' || (element as HTMLInputElement).type === 'date') {
|
} else if (element?.tagName === 'INPUT' && ((element as HTMLInputElement).type === 'time' || (element as HTMLInputElement).type === 'date')) {
|
||||||
info.innerText = (element as HTMLInputElement).value;
|
info.innerText = (element as HTMLInputElement).value;
|
||||||
} else {
|
} else {
|
||||||
info.hasOnlyText = element?.children?.length === 0 &&
|
info.hasOnlyText = element?.children?.length === 0 &&
|
||||||
@@ -80,7 +105,26 @@ export const getElementInformation = async (
|
|||||||
} else {
|
} else {
|
||||||
const elementInfo = await page.evaluate(
|
const elementInfo = await page.evaluate(
|
||||||
async ({ x, y }) => {
|
async ({ x, y }) => {
|
||||||
const originalEl = document.elementFromPoint(x, y) as HTMLElement;
|
// Helper function to get element from point including shadow DOM
|
||||||
|
const getDeepestElementFromPoint = (x: number, y: number): HTMLElement | null => {
|
||||||
|
let element = document.elementFromPoint(x, y) as HTMLElement;
|
||||||
|
if (!element) return null;
|
||||||
|
|
||||||
|
// Traverse through shadow roots
|
||||||
|
let current = element;
|
||||||
|
while (current) {
|
||||||
|
const shadowRoot = current.shadowRoot;
|
||||||
|
if (!shadowRoot) break;
|
||||||
|
|
||||||
|
const shadowElement = shadowRoot.elementFromPoint(x, y) as HTMLElement;
|
||||||
|
if (!shadowElement || shadowElement === current) break;
|
||||||
|
|
||||||
|
current = shadowElement;
|
||||||
|
}
|
||||||
|
return current;
|
||||||
|
};
|
||||||
|
|
||||||
|
const originalEl = getDeepestElementFromPoint(x, y);
|
||||||
if (originalEl) {
|
if (originalEl) {
|
||||||
let element = originalEl;
|
let element = originalEl;
|
||||||
|
|
||||||
@@ -114,8 +158,10 @@ export const getElementInformation = async (
|
|||||||
attributes?: Record<string, string>;
|
attributes?: Record<string, string>;
|
||||||
innerHTML?: string;
|
innerHTML?: string;
|
||||||
outerHTML?: string;
|
outerHTML?: string;
|
||||||
|
isShadowRoot?: boolean;
|
||||||
} = {
|
} = {
|
||||||
tagName: element?.tagName ?? '',
|
tagName: element?.tagName ?? '',
|
||||||
|
isShadowRoot: !!element?.shadowRoot
|
||||||
};
|
};
|
||||||
|
|
||||||
if (element) {
|
if (element) {
|
||||||
@@ -156,24 +202,33 @@ export const getElementInformation = async (
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a {@link Rectangle} object representing
|
|
||||||
* the coordinates, width, height and corner points of the element.
|
|
||||||
* If an element is not found, returns null.
|
|
||||||
* @param page The page instance.
|
|
||||||
* @param coordinates Coordinates of an element.
|
|
||||||
* @category WorkflowManagement-Selectors
|
|
||||||
* @returns {Promise<Rectangle|undefined|null>}
|
|
||||||
*/
|
|
||||||
export const getRect = async (page: Page, coordinates: Coordinates, listSelector: string, getList: boolean) => {
|
export const getRect = async (page: Page, coordinates: Coordinates, listSelector: string, getList: boolean) => {
|
||||||
try {
|
try {
|
||||||
if (!getList || listSelector !== '') {
|
if (!getList || listSelector !== '') {
|
||||||
const rect = await page.evaluate(
|
const rect = await page.evaluate(
|
||||||
async ({ x, y }) => {
|
async ({ x, y }) => {
|
||||||
const el = document.elementFromPoint(x, y) as HTMLElement;
|
// Helper function to get element from point including shadow DOM
|
||||||
|
const getDeepestElementFromPoint = (x: number, y: number): HTMLElement | null => {
|
||||||
|
let element = document.elementFromPoint(x, y) as HTMLElement;
|
||||||
|
if (!element) return null;
|
||||||
|
|
||||||
|
// Traverse through shadow roots
|
||||||
|
let current = element;
|
||||||
|
while (current) {
|
||||||
|
const shadowRoot = current.shadowRoot;
|
||||||
|
if (!shadowRoot) break;
|
||||||
|
|
||||||
|
const shadowElement = shadowRoot.elementFromPoint(x, y) as HTMLElement;
|
||||||
|
if (!shadowElement || shadowElement === current) break;
|
||||||
|
|
||||||
|
current = shadowElement;
|
||||||
|
}
|
||||||
|
return current;
|
||||||
|
};
|
||||||
|
|
||||||
|
const el = getDeepestElementFromPoint(x, y);
|
||||||
if (el) {
|
if (el) {
|
||||||
const { parentElement } = el;
|
const { parentElement } = el;
|
||||||
// Match the logic in recorder.ts for link clicks
|
|
||||||
const element = parentElement?.tagName === 'A' ? parentElement : el;
|
const element = parentElement?.tagName === 'A' ? parentElement : el;
|
||||||
const rectangle = element?.getBoundingClientRect();
|
const rectangle = element?.getBoundingClientRect();
|
||||||
if (rectangle) {
|
if (rectangle) {
|
||||||
@@ -196,7 +251,26 @@ export const getRect = async (page: Page, coordinates: Coordinates, listSelector
|
|||||||
} else {
|
} else {
|
||||||
const rect = await page.evaluate(
|
const rect = await page.evaluate(
|
||||||
async ({ x, y }) => {
|
async ({ x, y }) => {
|
||||||
const originalEl = document.elementFromPoint(x, y) as HTMLElement;
|
// Helper function to get element from point including shadow DOM
|
||||||
|
const getDeepestElementFromPoint = (x: number, y: number): HTMLElement | null => {
|
||||||
|
let element = document.elementFromPoint(x, y) as HTMLElement;
|
||||||
|
if (!element) return null;
|
||||||
|
|
||||||
|
// Traverse through shadow roots
|
||||||
|
let current = element;
|
||||||
|
while (current) {
|
||||||
|
const shadowRoot = current.shadowRoot;
|
||||||
|
if (!shadowRoot) break;
|
||||||
|
|
||||||
|
const shadowElement = shadowRoot.elementFromPoint(x, y) as HTMLElement;
|
||||||
|
if (!shadowElement || shadowElement === current) break;
|
||||||
|
|
||||||
|
current = shadowElement;
|
||||||
|
}
|
||||||
|
return current;
|
||||||
|
};
|
||||||
|
|
||||||
|
const originalEl = getDeepestElementFromPoint(x, y);
|
||||||
if (originalEl) {
|
if (originalEl) {
|
||||||
let element = originalEl;
|
let element = originalEl;
|
||||||
|
|
||||||
@@ -249,7 +323,6 @@ export const getRect = async (page: Page, coordinates: Coordinates, listSelector
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the best and unique css {@link Selectors} for the element on the page.
|
* Returns the best and unique css {@link Selectors} for the element on the page.
|
||||||
* Internally uses a finder function from https://github.com/antonmedv/finder/blob/master/finder.ts
|
* Internally uses a finder function from https://github.com/antonmedv/finder/blob/master/finder.ts
|
||||||
|
|||||||
Reference in New Issue
Block a user