2024-06-05 06:26:13 +05:30
|
|
|
import { Page } from "playwright";
|
|
|
|
|
import { Action, ActionType, Coordinates, TagName } from "../types";
|
|
|
|
|
import { WhereWhatPair, WorkflowFile } from "@wbr-project/wbr-interpret";
|
|
|
|
|
import logger from "../logger";
|
|
|
|
|
import { getBestSelectorForAction } from "./utils";
|
|
|
|
|
|
2024-06-05 06:34:46 +05:30
|
|
|
type Workflow = WorkflowFile["workflow"];
|
|
|
|
|
|
2024-06-05 06:26:13 +05:30
|
|
|
/**
|
|
|
|
|
* 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) => {
|
|
|
|
|
try {
|
|
|
|
|
const rect = await page.evaluate(
|
|
|
|
|
async ({ x, y }) => {
|
|
|
|
|
const el = document.elementFromPoint(x, y) as HTMLElement;
|
2024-06-05 06:27:17 +05:30
|
|
|
if (el) {
|
|
|
|
|
const { parentElement } = el;
|
2024-06-05 06:28:49 +05:30
|
|
|
// Match the logic in recorder.ts for link clicks
|
|
|
|
|
const element = parentElement?.tagName === 'A' ? parentElement : el;
|
2024-06-05 06:31:58 +05:30
|
|
|
const rectangle = element?.getBoundingClientRect();
|
2024-06-05 06:32:45 +05:30
|
|
|
// @ts-ignore
|
|
|
|
|
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,
|
|
|
|
|
};
|
|
|
|
|
}
|
2024-06-05 06:27:17 +05:30
|
|
|
}},
|
2024-06-05 06:32:45 +05:30
|
|
|
{ x: coordinates.x, y: coordinates.y },
|
2024-06-05 06:26:13 +05:30
|
|
|
);
|
2024-06-05 06:32:45 +05:30
|
|
|
return rect;
|
2024-06-05 06:26:13 +05:30
|
|
|
} catch (error) {
|
|
|
|
|
const { message, stack } = error as Error;
|
|
|
|
|
logger.log('error', `Error while retrieving selector: ${message}`);
|
|
|
|
|
logger.log('error', `Stack: ${stack}`);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-05 07:05:53 +05:30
|
|
|
/**
|
|
|
|
|
* Checks the basic info about an element and returns a {@link BaseActionInfo} object.
|
|
|
|
|
* If the element is not found, returns undefined.
|
|
|
|
|
* @param page The page instance.
|
|
|
|
|
* @param coordinates Coordinates of an element.
|
|
|
|
|
* @category WorkflowManagement-Selectors
|
|
|
|
|
* @returns {Promise<BaseActionInfo|undefined>}
|
|
|
|
|
*/
|
2024-06-05 07:05:20 +05:30
|
|
|
export const getElementInformation = async (
|
|
|
|
|
page: Page,
|
|
|
|
|
coordinates: Coordinates
|
|
|
|
|
) => {
|
|
|
|
|
try {
|
|
|
|
|
const elementInfo = await page.evaluate(
|
|
|
|
|
async ({ x, y }) => {
|
|
|
|
|
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;
|
|
|
|
|
return {
|
|
|
|
|
tagName: element?.tagName ?? '',
|
|
|
|
|
hasOnlyText: element?.children?.length === 0 &&
|
|
|
|
|
element?.innerText?.length > 0,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
{ x: coordinates.x, y: coordinates.y },
|
|
|
|
|
);
|
|
|
|
|
return elementInfo;
|
|
|
|
|
} catch (error) {
|
|
|
|
|
const { message, stack } = error as Error;
|
|
|
|
|
logger.log('error', `Error while retrieving selector: ${message}`);
|
|
|
|
|
logger.log('error', `Stack: ${stack}`);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-05 10:34:17 +05:30
|
|
|
/**
|
|
|
|
|
* 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
|
|
|
|
|
* available as a npm package: @medv/finder
|
|
|
|
|
*
|
|
|
|
|
* The finder needs to be executed and defined inside a browser context. Meaning,
|
|
|
|
|
* the code needs to be available inside a page evaluate function.
|
|
|
|
|
* @param page The page instance.
|
|
|
|
|
* @param coordinates Coordinates of an element.
|
|
|
|
|
* @category WorkflowManagement-Selectors
|
|
|
|
|
* @returns {Promise<Selectors|null|undefined>}
|
|
|
|
|
*/
|
|
|
|
|
export const getSelectors = async (page: Page, coordinates: Coordinates) => {
|
|
|
|
|
try {
|
|
|
|
|
const selectors : any = await page.evaluate(async ({ x, y }) => {
|
2024-06-05 07:05:20 +05:30
|
|
|
|
2024-06-05 10:34:17 +05:30
|
|
|
type Options = {
|
|
|
|
|
root: Element;
|
|
|
|
|
idName: (name: string) => boolean;
|
|
|
|
|
className: (name: string) => boolean;
|
|
|
|
|
tagName: (name: string) => boolean;
|
|
|
|
|
attr: (name: string, value: string) => boolean;
|
|
|
|
|
seedMinLength: number;
|
|
|
|
|
optimizedMinLength: number;
|
|
|
|
|
threshold: number;
|
|
|
|
|
maxNumberOfTries: number;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
2024-06-05 06:26:13 +05:30
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|