@sqaitech/shared 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -0
- package/dist/es/baseDB.mjs +109 -0
- package/dist/es/build/copy-static.mjs +29 -0
- package/dist/es/common.mjs +37 -0
- package/dist/es/constants/example-code.mjs +202 -0
- package/dist/es/constants/index.mjs +24 -0
- package/dist/es/env/basic.mjs +6 -0
- package/dist/es/env/constants.mjs +97 -0
- package/dist/es/env/decide-model-config.mjs +172 -0
- package/dist/es/env/global-config-manager.mjs +91 -0
- package/dist/es/env/helper.mjs +48 -0
- package/dist/es/env/index.mjs +5 -0
- package/dist/es/env/init-debug.mjs +18 -0
- package/dist/es/env/model-config-manager.mjs +99 -0
- package/dist/es/env/parse.mjs +69 -0
- package/dist/es/env/types.mjs +253 -0
- package/dist/es/env/utils.mjs +18 -0
- package/dist/es/extractor/constants.mjs +2 -0
- package/dist/es/extractor/debug.mjs +6 -0
- package/dist/es/extractor/dom-util.mjs +92 -0
- package/dist/es/extractor/index.mjs +6 -0
- package/dist/es/extractor/locator.mjs +95 -0
- package/dist/es/extractor/tree.mjs +81 -0
- package/dist/es/extractor/util.mjs +244 -0
- package/dist/es/extractor/web-extractor.mjs +310 -0
- package/dist/es/img/box-select.mjs +184 -0
- package/dist/es/img/draw-box.mjs +42 -0
- package/dist/es/img/get-jimp.mjs +10 -0
- package/dist/es/img/get-photon.mjs +19 -0
- package/dist/es/img/get-sharp.mjs +11 -0
- package/dist/es/img/index.mjs +5 -0
- package/dist/es/img/info.mjs +32 -0
- package/dist/es/img/transform.mjs +192 -0
- package/dist/es/index.mjs +3 -0
- package/dist/es/logger.mjs +61 -0
- package/dist/es/node/fs.mjs +44 -0
- package/dist/es/node/index.mjs +1 -0
- package/dist/es/polyfills/async-hooks.mjs +2 -0
- package/dist/es/polyfills/index.mjs +1 -0
- package/dist/es/types/index.mjs +3 -0
- package/dist/es/us-keyboard-layout.mjs +1414 -0
- package/dist/es/us-keyboard-layout.mjs.LICENSE.txt +5 -0
- package/dist/es/utils.mjs +66 -0
- package/dist/lib/baseDB.js +149 -0
- package/dist/lib/build/copy-static.js +77 -0
- package/dist/lib/common.js +93 -0
- package/dist/lib/constants/example-code.js +239 -0
- package/dist/lib/constants/index.js +100 -0
- package/dist/lib/env/basic.js +40 -0
- package/dist/lib/env/constants.js +143 -0
- package/dist/lib/env/decide-model-config.js +212 -0
- package/dist/lib/env/global-config-manager.js +125 -0
- package/dist/lib/env/helper.js +89 -0
- package/dist/lib/env/index.js +94 -0
- package/dist/lib/env/init-debug.js +52 -0
- package/dist/lib/env/model-config-manager.js +133 -0
- package/dist/lib/env/parse.js +106 -0
- package/dist/lib/env/types.js +635 -0
- package/dist/lib/env/utils.js +61 -0
- package/dist/lib/extractor/constants.js +42 -0
- package/dist/lib/extractor/debug.js +12 -0
- package/dist/lib/extractor/dom-util.js +150 -0
- package/dist/lib/extractor/index.js +88 -0
- package/dist/lib/extractor/locator.js +141 -0
- package/dist/lib/extractor/tree.js +127 -0
- package/dist/lib/extractor/util.js +335 -0
- package/dist/lib/extractor/web-extractor.js +356 -0
- package/dist/lib/img/box-select.js +232 -0
- package/dist/lib/img/draw-box.js +89 -0
- package/dist/lib/img/get-jimp.js +72 -0
- package/dist/lib/img/get-photon.js +76 -0
- package/dist/lib/img/get-sharp.js +63 -0
- package/dist/lib/img/index.js +102 -0
- package/dist/lib/img/info.js +86 -0
- package/dist/lib/img/transform.js +279 -0
- package/dist/lib/index.js +43 -0
- package/dist/lib/logger.js +114 -0
- package/dist/lib/node/fs.js +97 -0
- package/dist/lib/node/index.js +60 -0
- package/dist/lib/polyfills/async-hooks.js +36 -0
- package/dist/lib/polyfills/index.js +60 -0
- package/dist/lib/types/index.js +37 -0
- package/dist/lib/us-keyboard-layout.js +1457 -0
- package/dist/lib/us-keyboard-layout.js.LICENSE.txt +5 -0
- package/dist/lib/utils.js +136 -0
- package/dist/types/baseDB.d.ts +25 -0
- package/dist/types/build/copy-static.d.ts +31 -0
- package/dist/types/common.d.ts +12 -0
- package/dist/types/constants/example-code.d.ts +2 -0
- package/dist/types/constants/index.d.ts +22 -0
- package/dist/types/env/basic.d.ts +6 -0
- package/dist/types/env/constants.d.ts +40 -0
- package/dist/types/env/decide-model-config.d.ts +14 -0
- package/dist/types/env/global-config-manager.d.ts +32 -0
- package/dist/types/env/helper.d.ts +6 -0
- package/dist/types/env/index.d.ts +4 -0
- package/dist/types/env/init-debug.d.ts +1 -0
- package/dist/types/env/model-config-manager.d.ts +24 -0
- package/dist/types/env/parse.d.ts +12 -0
- package/dist/types/env/types.d.ts +294 -0
- package/dist/types/env/utils.d.ts +7 -0
- package/dist/types/extractor/constants.d.ts +1 -0
- package/dist/types/extractor/debug.d.ts +1 -0
- package/dist/types/extractor/dom-util.d.ts +26 -0
- package/dist/types/extractor/index.d.ts +33 -0
- package/dist/types/extractor/locator.d.ts +7 -0
- package/dist/types/extractor/tree.d.ts +9 -0
- package/dist/types/extractor/util.d.ts +43 -0
- package/dist/types/extractor/web-extractor.d.ts +19 -0
- package/dist/types/img/box-select.d.ts +25 -0
- package/dist/types/img/draw-box.d.ts +15 -0
- package/dist/types/img/get-jimp.d.ts +2 -0
- package/dist/types/img/get-photon.d.ts +8 -0
- package/dist/types/img/get-sharp.d.ts +3 -0
- package/dist/types/img/index.d.ts +4 -0
- package/dist/types/img/info.d.ts +29 -0
- package/dist/types/img/transform.d.ts +88 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/types/logger.d.ts +4 -0
- package/dist/types/node/fs.d.ts +15 -0
- package/dist/types/node/index.d.ts +1 -0
- package/dist/types/polyfills/async-hooks.d.ts +6 -0
- package/dist/types/polyfills/index.d.ts +4 -0
- package/dist/types/types/index.d.ts +34 -0
- package/dist/types/us-keyboard-layout.d.ts +32 -0
- package/dist/types/utils.d.ts +22 -0
- package/package.json +106 -0
- package/src/baseDB.ts +158 -0
- package/src/build/copy-static.ts +62 -0
- package/src/common.ts +67 -0
- package/src/constants/example-code.ts +202 -0
- package/src/constants/index.ts +30 -0
- package/src/env/basic.ts +12 -0
- package/src/env/constants.ts +291 -0
- package/src/env/decide-model-config.ts +319 -0
- package/src/env/global-config-manager.ts +174 -0
- package/src/env/helper.ts +79 -0
- package/src/env/index.ts +4 -0
- package/src/env/init-debug.ts +29 -0
- package/src/env/model-config-manager.ts +145 -0
- package/src/env/parse.ts +131 -0
- package/src/env/types.ts +560 -0
- package/src/env/utils.ts +39 -0
- package/src/extractor/constants.ts +5 -0
- package/src/extractor/debug.ts +10 -0
- package/src/extractor/dom-util.ts +152 -0
- package/src/extractor/index.ts +50 -0
- package/src/extractor/locator.ts +179 -0
- package/src/extractor/tree.ts +179 -0
- package/src/extractor/util.ts +468 -0
- package/src/extractor/web-extractor.ts +481 -0
- package/src/img/box-select.ts +346 -0
- package/src/img/draw-box.ts +60 -0
- package/src/img/get-jimp.ts +12 -0
- package/src/img/get-photon.ts +48 -0
- package/src/img/get-sharp.ts +18 -0
- package/src/img/index.ts +24 -0
- package/src/img/info.ts +79 -0
- package/src/img/jimp.d.ts +4 -0
- package/src/img/transform.ts +396 -0
- package/src/index.ts +6 -0
- package/src/logger.ts +93 -0
- package/src/node/fs.ts +84 -0
- package/src/node/index.ts +1 -0
- package/src/polyfills/async-hooks.ts +6 -0
- package/src/polyfills/index.ts +4 -0
- package/src/types/index.ts +47 -0
- package/src/us-keyboard-layout.ts +723 -0
- package/src/utils.ts +127 -0
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { NodeType } from '../constants';
|
|
2
|
+
import { generateHashId } from '../utils';
|
|
3
|
+
|
|
4
|
+
export function isFormElement(node: globalThis.Node) {
|
|
5
|
+
return (
|
|
6
|
+
node instanceof HTMLElement &&
|
|
7
|
+
(node.tagName.toLowerCase() === 'input' ||
|
|
8
|
+
node.tagName.toLowerCase() === 'textarea' ||
|
|
9
|
+
node.tagName.toLowerCase() === 'select' ||
|
|
10
|
+
node.tagName.toLowerCase() === 'option')
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function isButtonElement(
|
|
15
|
+
node: globalThis.Node,
|
|
16
|
+
): node is globalThis.HTMLButtonElement {
|
|
17
|
+
return node instanceof HTMLElement && node.tagName.toLowerCase() === 'button';
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function isAElement(
|
|
21
|
+
node: globalThis.Node,
|
|
22
|
+
): node is globalThis.HTMLButtonElement {
|
|
23
|
+
return node instanceof HTMLElement && node.tagName.toLowerCase() === 'a';
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function isSvgElement(
|
|
27
|
+
node: globalThis.Node,
|
|
28
|
+
): node is globalThis.SVGSVGElement {
|
|
29
|
+
return node instanceof SVGElement;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function isImgElement(
|
|
33
|
+
node: globalThis.Node,
|
|
34
|
+
): node is globalThis.HTMLImageElement {
|
|
35
|
+
// check if the node is an image element
|
|
36
|
+
if (!includeBaseElement(node) && node instanceof Element) {
|
|
37
|
+
const computedStyle = window.getComputedStyle(node);
|
|
38
|
+
const backgroundImage = computedStyle.getPropertyValue('background-image');
|
|
39
|
+
if (backgroundImage !== 'none') {
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (isIconfont(node)) {
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
(node instanceof HTMLElement && node.tagName.toLowerCase() === 'img') ||
|
|
50
|
+
(node instanceof SVGElement && node.tagName.toLowerCase() === 'svg')
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function isIconfont(node: globalThis.Node): boolean {
|
|
55
|
+
if (node instanceof Element) {
|
|
56
|
+
const computedStyle = window.getComputedStyle(node);
|
|
57
|
+
const fontFamilyValue = computedStyle.fontFamily || '';
|
|
58
|
+
return fontFamilyValue.toLowerCase().indexOf('iconfont') >= 0;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function isNotContainerElement(node: globalThis.Node) {
|
|
65
|
+
return (
|
|
66
|
+
isTextElement(node) ||
|
|
67
|
+
isIconfont(node) ||
|
|
68
|
+
isImgElement(node) ||
|
|
69
|
+
isButtonElement(node) ||
|
|
70
|
+
isAElement(node) ||
|
|
71
|
+
isFormElement(node)
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function isTextElement(
|
|
76
|
+
node: globalThis.Node,
|
|
77
|
+
): node is globalThis.HTMLTextAreaElement {
|
|
78
|
+
if (node instanceof Element) {
|
|
79
|
+
if (node?.childNodes?.length === 1 && node?.childNodes[0] instanceof Text) {
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return node.nodeName?.toLowerCase?.() === '#text' && !isIconfont(node);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function isContainerElement(
|
|
88
|
+
node: globalThis.Node,
|
|
89
|
+
): node is globalThis.HTMLElement {
|
|
90
|
+
if (!(node instanceof HTMLElement)) return false;
|
|
91
|
+
|
|
92
|
+
// include other base elements
|
|
93
|
+
if (includeBaseElement(node)) {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const computedStyle = window.getComputedStyle(node);
|
|
98
|
+
const backgroundColor = computedStyle.getPropertyValue('background-color');
|
|
99
|
+
if (backgroundColor) {
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function includeBaseElement(node: globalThis.Node) {
|
|
107
|
+
if (!(node instanceof HTMLElement)) return false;
|
|
108
|
+
|
|
109
|
+
// include text
|
|
110
|
+
if (node.innerText) {
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const includeList = [
|
|
115
|
+
'svg',
|
|
116
|
+
'button',
|
|
117
|
+
'input',
|
|
118
|
+
'textarea',
|
|
119
|
+
'select',
|
|
120
|
+
'option',
|
|
121
|
+
'img',
|
|
122
|
+
'a',
|
|
123
|
+
];
|
|
124
|
+
|
|
125
|
+
for (const tagName of includeList) {
|
|
126
|
+
const element = node.querySelectorAll(tagName);
|
|
127
|
+
if (element.length > 0) {
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function generateElementByPosition(position: { x: number; y: number }) {
|
|
136
|
+
const rect = {
|
|
137
|
+
left: Math.max(position.x - 4, 0),
|
|
138
|
+
top: Math.max(position.y - 4, 0),
|
|
139
|
+
width: 8,
|
|
140
|
+
height: 8,
|
|
141
|
+
};
|
|
142
|
+
const id = generateHashId(rect);
|
|
143
|
+
const element = {
|
|
144
|
+
id,
|
|
145
|
+
attributes: { nodeType: NodeType.POSITION },
|
|
146
|
+
rect,
|
|
147
|
+
content: '',
|
|
148
|
+
center: [position.x, position.y],
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
return element;
|
|
152
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { NodeType } from '../constants/index';
|
|
2
|
+
|
|
3
|
+
export interface ElementInfo {
|
|
4
|
+
id: string;
|
|
5
|
+
indexId: number;
|
|
6
|
+
nodeHashId: string;
|
|
7
|
+
xpaths?: string[];
|
|
8
|
+
attributes: {
|
|
9
|
+
nodeType: NodeType;
|
|
10
|
+
[key: string]: string;
|
|
11
|
+
};
|
|
12
|
+
nodeType: NodeType;
|
|
13
|
+
content: string;
|
|
14
|
+
rect: { left: number; top: number; width: number; height: number };
|
|
15
|
+
center: [number, number];
|
|
16
|
+
isVisible: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface ElementNode {
|
|
20
|
+
node: ElementInfo | null;
|
|
21
|
+
children: ElementNode[];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export {
|
|
25
|
+
descriptionOfTree,
|
|
26
|
+
traverseTree,
|
|
27
|
+
treeToList,
|
|
28
|
+
truncateText,
|
|
29
|
+
trimAttributes,
|
|
30
|
+
} from './tree';
|
|
31
|
+
|
|
32
|
+
export { extractTextWithPosition as webExtractTextWithPosition } from './web-extractor';
|
|
33
|
+
|
|
34
|
+
export { extractTreeNode as webExtractNodeTree } from './web-extractor';
|
|
35
|
+
|
|
36
|
+
export { extractTreeNodeAsString as webExtractNodeTreeAsString } from './web-extractor';
|
|
37
|
+
|
|
38
|
+
export { setNodeHashCacheListOnWindow, getNodeFromCacheList } from './util';
|
|
39
|
+
|
|
40
|
+
export {
|
|
41
|
+
getXpathsById,
|
|
42
|
+
getXpathsByPoint,
|
|
43
|
+
getNodeInfoByXpath,
|
|
44
|
+
getElementInfoByXpath,
|
|
45
|
+
getElementXpath,
|
|
46
|
+
} from './locator';
|
|
47
|
+
|
|
48
|
+
export { generateElementByPosition } from './dom-util';
|
|
49
|
+
|
|
50
|
+
export { isNotContainerElement } from './dom-util';
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import type { ElementInfo } from '.';
|
|
2
|
+
import type { Point } from '../types';
|
|
3
|
+
import { isSvgElement } from './dom-util';
|
|
4
|
+
import { getNodeFromCacheList } from './util';
|
|
5
|
+
import { getRect, isElementPartiallyInViewport } from './util';
|
|
6
|
+
import { collectElementInfo } from './web-extractor';
|
|
7
|
+
|
|
8
|
+
const getElementXpathIndex = (element: Element): number => {
|
|
9
|
+
let index = 1;
|
|
10
|
+
let prev = element.previousElementSibling;
|
|
11
|
+
|
|
12
|
+
while (prev) {
|
|
13
|
+
if (prev.nodeName.toLowerCase() === element.nodeName.toLowerCase()) {
|
|
14
|
+
index++;
|
|
15
|
+
}
|
|
16
|
+
prev = prev.previousElementSibling;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return index;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const normalizeXpathText = (text: string): string => {
|
|
23
|
+
if (typeof text !== 'string') {
|
|
24
|
+
return '';
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return text.replace(/\s+/g, ' ').trim();
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const buildCurrentElementXpath = (
|
|
31
|
+
element: Element,
|
|
32
|
+
isOrderSensitive: boolean,
|
|
33
|
+
isLeafElement: boolean,
|
|
34
|
+
): string => {
|
|
35
|
+
// Build parent path - inline the buildParentXpath logic
|
|
36
|
+
const parentPath = element.parentNode
|
|
37
|
+
? getElementXpath(element.parentNode, isOrderSensitive)
|
|
38
|
+
: '';
|
|
39
|
+
const prefix = parentPath ? `${parentPath}/` : '/';
|
|
40
|
+
const tagName = element.nodeName.toLowerCase();
|
|
41
|
+
const textContent = element.textContent?.trim();
|
|
42
|
+
|
|
43
|
+
// Order-sensitive mode: always use index
|
|
44
|
+
if (isOrderSensitive) {
|
|
45
|
+
const index = getElementXpathIndex(element);
|
|
46
|
+
return `${prefix}${tagName}[${index}]`;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Order-insensitive mode:
|
|
50
|
+
// - Leaf elements: try text first, fallback to index if no text
|
|
51
|
+
// - Non-leaf elements: always use index
|
|
52
|
+
if (isLeafElement && textContent) {
|
|
53
|
+
return `${prefix}${tagName}[normalize-space()="${normalizeXpathText(textContent)}"]`;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Fallback to index (for non-leaf elements or leaf elements without text)
|
|
57
|
+
const index = getElementXpathIndex(element);
|
|
58
|
+
return `${prefix}${tagName}[${index}]`;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export const getElementXpath = (
|
|
62
|
+
element: Node,
|
|
63
|
+
isOrderSensitive = false,
|
|
64
|
+
isLeafElement = false,
|
|
65
|
+
): string => {
|
|
66
|
+
// process text node
|
|
67
|
+
if (element.nodeType === Node.TEXT_NODE) {
|
|
68
|
+
const parentNode = element.parentNode;
|
|
69
|
+
if (parentNode && parentNode.nodeType === Node.ELEMENT_NODE) {
|
|
70
|
+
// For text nodes, treat parent as leaf element to enable text matching
|
|
71
|
+
const parentXPath = getElementXpath(parentNode, isOrderSensitive, true);
|
|
72
|
+
const textContent = element.textContent?.trim();
|
|
73
|
+
if (textContent) {
|
|
74
|
+
return `${parentXPath}/text()[normalize-space()="${normalizeXpathText(textContent)}"]`;
|
|
75
|
+
}
|
|
76
|
+
return `${parentXPath}/text()`;
|
|
77
|
+
}
|
|
78
|
+
return '';
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// process element node
|
|
82
|
+
if (element.nodeType !== Node.ELEMENT_NODE) return '';
|
|
83
|
+
|
|
84
|
+
// process element node - at this point, element should be an Element
|
|
85
|
+
const el = element as Element;
|
|
86
|
+
|
|
87
|
+
// special element handling
|
|
88
|
+
if (el === document.documentElement) return '/html';
|
|
89
|
+
if (el === document.body) return '/html/body';
|
|
90
|
+
|
|
91
|
+
// if the element is any SVG element, find the nearest non-SVG ancestor
|
|
92
|
+
if (isSvgElement(el)) {
|
|
93
|
+
let parent = el.parentNode;
|
|
94
|
+
while (parent && parent.nodeType === Node.ELEMENT_NODE) {
|
|
95
|
+
if (!isSvgElement(parent)) {
|
|
96
|
+
return getElementXpath(parent, isOrderSensitive, isLeafElement);
|
|
97
|
+
}
|
|
98
|
+
parent = parent.parentNode;
|
|
99
|
+
}
|
|
100
|
+
// fallback if no non-SVG parent found
|
|
101
|
+
return getElementXpath(el.parentNode!, isOrderSensitive, isLeafElement);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// decide which format to use
|
|
105
|
+
return buildCurrentElementXpath(el, isOrderSensitive, isLeafElement);
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
export function getXpathsById(id: string): string[] | null {
|
|
109
|
+
const node = getNodeFromCacheList(id);
|
|
110
|
+
|
|
111
|
+
if (!node) {
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const fullXPath = getElementXpath(node, false, true);
|
|
116
|
+
return [fullXPath];
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function getXpathsByPoint(
|
|
120
|
+
point: Point,
|
|
121
|
+
isOrderSensitive: boolean,
|
|
122
|
+
): string[] | null {
|
|
123
|
+
const element = document.elementFromPoint(point.left, point.top);
|
|
124
|
+
|
|
125
|
+
if (!element) {
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const fullXPath = getElementXpath(element, isOrderSensitive, true);
|
|
130
|
+
return [fullXPath];
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function getNodeInfoByXpath(xpath: string): Node | null {
|
|
134
|
+
const xpathResult = document.evaluate(
|
|
135
|
+
xpath,
|
|
136
|
+
document,
|
|
137
|
+
null,
|
|
138
|
+
XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
|
|
139
|
+
null,
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
if (xpathResult.snapshotLength !== 1) {
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const node = xpathResult.snapshotItem(0);
|
|
147
|
+
|
|
148
|
+
return node;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function getElementInfoByXpath(xpath: string): ElementInfo | null {
|
|
152
|
+
const node = getNodeInfoByXpath(xpath);
|
|
153
|
+
|
|
154
|
+
if (!node) {
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (node instanceof HTMLElement) {
|
|
159
|
+
// only when the element is not completely in the viewport, call scrollIntoView
|
|
160
|
+
const rect = getRect(node, 1, window);
|
|
161
|
+
const isVisible = isElementPartiallyInViewport(rect, window, document, 1);
|
|
162
|
+
|
|
163
|
+
if (!isVisible) {
|
|
164
|
+
node.scrollIntoView({ behavior: 'instant', block: 'center' });
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return collectElementInfo(
|
|
169
|
+
node,
|
|
170
|
+
window,
|
|
171
|
+
document,
|
|
172
|
+
1,
|
|
173
|
+
{
|
|
174
|
+
left: 0,
|
|
175
|
+
top: 0,
|
|
176
|
+
},
|
|
177
|
+
true,
|
|
178
|
+
);
|
|
179
|
+
}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import type { BaseElement, ElementTreeNode } from '../types';
|
|
2
|
+
|
|
3
|
+
export function truncateText(
|
|
4
|
+
text: string | number | object | undefined,
|
|
5
|
+
maxLength = 150,
|
|
6
|
+
) {
|
|
7
|
+
if (typeof text === 'undefined') {
|
|
8
|
+
return '';
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
if (typeof text === 'object') {
|
|
12
|
+
text = JSON.stringify(text);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (typeof text === 'number') {
|
|
16
|
+
return text.toString();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (typeof text === 'string' && text.length > maxLength) {
|
|
20
|
+
return `${text.slice(0, maxLength)}...`;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (typeof text === 'string') {
|
|
24
|
+
return text.trim();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return '';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function trimAttributes(
|
|
31
|
+
attributes: Record<string, any>,
|
|
32
|
+
truncateTextLength?: number,
|
|
33
|
+
) {
|
|
34
|
+
const tailorAttributes = Object.keys(attributes).reduce(
|
|
35
|
+
(res, currentKey: string) => {
|
|
36
|
+
const attributeVal = (attributes as any)[currentKey];
|
|
37
|
+
if (
|
|
38
|
+
currentKey === 'style' ||
|
|
39
|
+
currentKey === 'htmlTagName' ||
|
|
40
|
+
currentKey === 'nodeType'
|
|
41
|
+
) {
|
|
42
|
+
return res;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
res[currentKey] = truncateText(attributeVal, truncateTextLength);
|
|
46
|
+
return res;
|
|
47
|
+
},
|
|
48
|
+
{} as BaseElement['attributes'],
|
|
49
|
+
);
|
|
50
|
+
return tailorAttributes;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const nodeSizeThreshold = 4;
|
|
54
|
+
export function descriptionOfTree<
|
|
55
|
+
ElementType extends BaseElement = BaseElement,
|
|
56
|
+
>(
|
|
57
|
+
tree: ElementTreeNode<ElementType>,
|
|
58
|
+
truncateTextLength?: number,
|
|
59
|
+
filterNonTextContent = false,
|
|
60
|
+
visibleOnly = true,
|
|
61
|
+
) {
|
|
62
|
+
const attributesString = (kv: Record<string, any>) => {
|
|
63
|
+
return Object.entries(kv)
|
|
64
|
+
.map(
|
|
65
|
+
([key, value]) => `${key}="${truncateText(value, truncateTextLength)}"`,
|
|
66
|
+
)
|
|
67
|
+
.join(' ');
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
function buildContentTree(
|
|
71
|
+
node: ElementTreeNode<ElementType>,
|
|
72
|
+
indent = 0,
|
|
73
|
+
visibleOnly = true,
|
|
74
|
+
): string {
|
|
75
|
+
let before = '';
|
|
76
|
+
let contentWithIndent = '';
|
|
77
|
+
let after = '';
|
|
78
|
+
let emptyNode = true;
|
|
79
|
+
const indentStr = ' '.repeat(indent);
|
|
80
|
+
|
|
81
|
+
let children = '';
|
|
82
|
+
for (let i = 0; i < (node.children || []).length; i++) {
|
|
83
|
+
const childContent = buildContentTree(
|
|
84
|
+
node.children[i],
|
|
85
|
+
indent + 1,
|
|
86
|
+
visibleOnly,
|
|
87
|
+
);
|
|
88
|
+
if (childContent) {
|
|
89
|
+
children += `\n${childContent}`;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (
|
|
94
|
+
node.node &&
|
|
95
|
+
node.node.rect.width > nodeSizeThreshold &&
|
|
96
|
+
node.node.rect.height > nodeSizeThreshold &&
|
|
97
|
+
(!filterNonTextContent || (filterNonTextContent && node.node.content)) &&
|
|
98
|
+
(!visibleOnly || (visibleOnly && node.node.isVisible))
|
|
99
|
+
) {
|
|
100
|
+
emptyNode = false;
|
|
101
|
+
let nodeTypeString: string;
|
|
102
|
+
if (node.node.attributes?.htmlTagName) {
|
|
103
|
+
nodeTypeString = node.node.attributes.htmlTagName.replace(/[<>]/g, '');
|
|
104
|
+
} else {
|
|
105
|
+
nodeTypeString = node.node.attributes.nodeType
|
|
106
|
+
.replace(/\sNode$/, '')
|
|
107
|
+
.toLowerCase();
|
|
108
|
+
}
|
|
109
|
+
const markerId = node.node.indexId;
|
|
110
|
+
const markerIdString = markerId ? `markerId="${markerId}"` : '';
|
|
111
|
+
const rectAttribute = node.node.rect
|
|
112
|
+
? {
|
|
113
|
+
left: node.node.rect.left,
|
|
114
|
+
top: node.node.rect.top,
|
|
115
|
+
width: node.node.rect.width,
|
|
116
|
+
height: node.node.rect.height,
|
|
117
|
+
}
|
|
118
|
+
: {};
|
|
119
|
+
before = `<${nodeTypeString} id="${node.node.id}" ${markerIdString} ${attributesString(trimAttributes(node.node.attributes || {}, truncateTextLength))} ${attributesString(rectAttribute)}>`;
|
|
120
|
+
const content = truncateText(node.node.content, truncateTextLength);
|
|
121
|
+
contentWithIndent = content ? `\n${indentStr} ${content}` : '';
|
|
122
|
+
after = `</${nodeTypeString}>`;
|
|
123
|
+
} else if (!filterNonTextContent) {
|
|
124
|
+
if (!children.trim().startsWith('<>')) {
|
|
125
|
+
before = '<>';
|
|
126
|
+
contentWithIndent = '';
|
|
127
|
+
after = '</>';
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (emptyNode && !children.trim()) {
|
|
132
|
+
return '';
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const result = `${indentStr}${before}${contentWithIndent}${children}\n${indentStr}${after}`;
|
|
136
|
+
if (result.trim()) {
|
|
137
|
+
return result;
|
|
138
|
+
}
|
|
139
|
+
return '';
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const result = buildContentTree(tree, 0, visibleOnly);
|
|
143
|
+
return result.replace(/^\s*\n/gm, '');
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function treeToList<T extends BaseElement>(
|
|
147
|
+
tree: ElementTreeNode<T>,
|
|
148
|
+
): T[] {
|
|
149
|
+
const result: T[] = [];
|
|
150
|
+
function dfs(node: ElementTreeNode<T>) {
|
|
151
|
+
if (node.node) {
|
|
152
|
+
result.push(node.node);
|
|
153
|
+
}
|
|
154
|
+
for (const child of node.children) {
|
|
155
|
+
dfs(child);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
dfs(tree);
|
|
159
|
+
return result;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export function traverseTree<
|
|
163
|
+
T extends BaseElement,
|
|
164
|
+
ReturnNodeType extends BaseElement,
|
|
165
|
+
>(
|
|
166
|
+
tree: ElementTreeNode<T>,
|
|
167
|
+
onNode: (node: T) => ReturnNodeType,
|
|
168
|
+
): ElementTreeNode<ReturnNodeType> {
|
|
169
|
+
function dfs(node: ElementTreeNode<T>) {
|
|
170
|
+
if (node.node) {
|
|
171
|
+
node.node = onNode(node.node) as any;
|
|
172
|
+
}
|
|
173
|
+
for (const child of node.children) {
|
|
174
|
+
dfs(child);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
dfs(tree);
|
|
178
|
+
return tree as any;
|
|
179
|
+
}
|