@midscene/shared 0.17.2 → 0.17.3-beta-20250526042111.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/dist/es/common.d.ts +1 -2
- package/dist/es/common.js +1 -3
- package/dist/es/constants.d.ts +1 -0
- package/dist/es/constants.js +1 -0
- package/dist/es/extractor-debug.js +92 -7
- package/dist/es/extractor.js +98 -35
- package/dist/es/logger.js +4 -2
- package/dist/lib/common.d.ts +1 -2
- package/dist/lib/common.js +2 -5
- package/dist/lib/constants.d.ts +1 -0
- package/dist/lib/constants.js +1 -0
- package/dist/lib/extractor-debug.js +92 -7
- package/dist/lib/extractor.js +98 -35
- package/dist/lib/logger.js +4 -2
- package/dist/script/htmlElement.js +97 -35
- package/dist/script/htmlElementDebug.js +90 -7
- package/dist/types/common.d.ts +1 -2
- package/dist/types/constants.d.ts +1 -0
- package/package.json +1 -1
- package/src/common.ts +0 -2
- package/src/constants/index.ts +1 -0
- package/src/extractor/dom-util.ts +7 -0
- package/src/extractor/locator.ts +9 -46
- package/src/extractor/web-extractor.ts +94 -1
- package/src/logger.ts +5 -2
package/src/extractor/locator.ts
CHANGED
|
@@ -17,20 +17,6 @@ const getElementIndex = (element: Element): number => {
|
|
|
17
17
|
return index;
|
|
18
18
|
};
|
|
19
19
|
|
|
20
|
-
// Find the first ancestor with an ID
|
|
21
|
-
const findFirstAncestorWithId = (element: Element): Element | null => {
|
|
22
|
-
let current = element;
|
|
23
|
-
|
|
24
|
-
while (current?.parentElement) {
|
|
25
|
-
if (current.id) {
|
|
26
|
-
return current;
|
|
27
|
-
}
|
|
28
|
-
current = current.parentElement;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
return null;
|
|
32
|
-
};
|
|
33
|
-
|
|
34
20
|
// Get the index of a text node among its siblings of the same type
|
|
35
21
|
const getTextNodeIndex = (textNode: Node): number => {
|
|
36
22
|
let index = 1;
|
|
@@ -54,6 +40,12 @@ const getElementXPath = (element: Node): string => {
|
|
|
54
40
|
if (parentNode && parentNode.nodeType === Node.ELEMENT_NODE) {
|
|
55
41
|
const parentXPath = getElementXPath(parentNode);
|
|
56
42
|
const textIndex = getTextNodeIndex(element);
|
|
43
|
+
const textContent = element.textContent?.trim();
|
|
44
|
+
|
|
45
|
+
// If we have text content, include it in the xpath for better matching
|
|
46
|
+
if (textContent) {
|
|
47
|
+
return `${parentXPath}/text()[${textIndex}][normalize-space()="${textContent}"]`;
|
|
48
|
+
}
|
|
57
49
|
return `${parentXPath}/text()[${textIndex}]`;
|
|
58
50
|
}
|
|
59
51
|
return '';
|
|
@@ -72,36 +64,7 @@ const getElementXPath = (element: Node): string => {
|
|
|
72
64
|
return '/html/body';
|
|
73
65
|
}
|
|
74
66
|
|
|
75
|
-
// If
|
|
76
|
-
if (el.id) {
|
|
77
|
-
return `//*[@id="${el.id}"]`;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const ancestorWithId = findFirstAncestorWithId(el);
|
|
81
|
-
|
|
82
|
-
if (ancestorWithId) {
|
|
83
|
-
// Start from the ancestor with ID
|
|
84
|
-
const ancestorPath = `//*[@id="${ancestorWithId.id}"]`;
|
|
85
|
-
|
|
86
|
-
// Build the path from the ancestor to this element
|
|
87
|
-
let current: Element | null = el;
|
|
88
|
-
const pathParts: string[] = [];
|
|
89
|
-
|
|
90
|
-
while (current && current !== ancestorWithId) {
|
|
91
|
-
const index = getElementIndex(current);
|
|
92
|
-
const tagName = current.nodeName.toLowerCase();
|
|
93
|
-
pathParts.unshift(`${tagName}[${index}]`);
|
|
94
|
-
current = current.parentElement;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// Combine the ancestor path with the rest of the path
|
|
98
|
-
return pathParts.length > 0
|
|
99
|
-
? `${ancestorPath}/${pathParts.join('/')}`
|
|
100
|
-
: ancestorPath;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// If no parent node, or we need a full path and haven't returned yet,
|
|
104
|
-
// start building the full path
|
|
67
|
+
// If no parent node, return just the tag name
|
|
105
68
|
if (!el.parentNode) {
|
|
106
69
|
return `/${el.nodeName.toLowerCase()}`;
|
|
107
70
|
}
|
|
@@ -120,9 +83,9 @@ const getElementXPath = (element: Node): string => {
|
|
|
120
83
|
function generateXPaths(node: Node | null): string[] {
|
|
121
84
|
if (!node) return [];
|
|
122
85
|
|
|
123
|
-
const
|
|
86
|
+
const fullXPath = getElementXPath(node);
|
|
124
87
|
|
|
125
|
-
return [
|
|
88
|
+
return [fullXPath];
|
|
126
89
|
}
|
|
127
90
|
|
|
128
91
|
export function getXpathsById(id: string): string[] | null {
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
import type { WebElementInfo } from '../types';
|
|
7
7
|
import type { Point } from '../types';
|
|
8
8
|
import {
|
|
9
|
+
isAElement,
|
|
9
10
|
isButtonElement,
|
|
10
11
|
isContainerElement,
|
|
11
12
|
isFormElement,
|
|
@@ -126,6 +127,16 @@ export function collectElementInfo(
|
|
|
126
127
|
}
|
|
127
128
|
|
|
128
129
|
if (isButtonElement(node)) {
|
|
130
|
+
const rect = mergeElementAndChildrenRects(
|
|
131
|
+
node,
|
|
132
|
+
currentWindow,
|
|
133
|
+
currentDocument,
|
|
134
|
+
baseZoom,
|
|
135
|
+
visibleOnly,
|
|
136
|
+
);
|
|
137
|
+
if (!rect) {
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
129
140
|
const attributes = getNodeAttributes(node, currentWindow);
|
|
130
141
|
const pseudo = getPseudoElementContent(node, currentWindow);
|
|
131
142
|
const content = node.innerText || pseudo.before || pseudo.after || '';
|
|
@@ -218,6 +229,34 @@ export function collectElementInfo(
|
|
|
218
229
|
return elementInfo;
|
|
219
230
|
}
|
|
220
231
|
|
|
232
|
+
if (isAElement(node)) {
|
|
233
|
+
const attributes = getNodeAttributes(node, currentWindow);
|
|
234
|
+
const pseudo = getPseudoElementContent(node, currentWindow);
|
|
235
|
+
const content = node.innerText || pseudo.before || pseudo.after || '';
|
|
236
|
+
const nodeHashId = midsceneGenerateHash(node, content, rect);
|
|
237
|
+
const selector = setDataForNode(node, nodeHashId, false, currentWindow);
|
|
238
|
+
const elementInfo: WebElementInfo = {
|
|
239
|
+
id: nodeHashId,
|
|
240
|
+
indexId: indexId++,
|
|
241
|
+
nodeHashId,
|
|
242
|
+
nodeType: NodeType.A,
|
|
243
|
+
locator: selector,
|
|
244
|
+
attributes: {
|
|
245
|
+
...attributes,
|
|
246
|
+
htmlTagName: tagNameOfNode(node),
|
|
247
|
+
nodeType: NodeType.A,
|
|
248
|
+
},
|
|
249
|
+
content,
|
|
250
|
+
rect,
|
|
251
|
+
center: [
|
|
252
|
+
Math.round(rect.left + rect.width / 2),
|
|
253
|
+
Math.round(rect.top + rect.height / 2),
|
|
254
|
+
],
|
|
255
|
+
zoom: rect.zoom,
|
|
256
|
+
};
|
|
257
|
+
return elementInfo;
|
|
258
|
+
}
|
|
259
|
+
|
|
221
260
|
// else, consider as a container
|
|
222
261
|
if (isContainerElement(node)) {
|
|
223
262
|
const attributes = getNodeAttributes(node, currentWindow);
|
|
@@ -329,11 +368,12 @@ export function extractTreeNode(
|
|
|
329
368
|
node: elementInfo,
|
|
330
369
|
children: [],
|
|
331
370
|
};
|
|
332
|
-
// stop collecting if the node is a Button/Image/Text/FormItem/Container
|
|
371
|
+
// stop collecting if the node is a Button/Image/Text/A/FormItem/Container
|
|
333
372
|
if (
|
|
334
373
|
elementInfo?.nodeType === NodeType.BUTTON ||
|
|
335
374
|
elementInfo?.nodeType === NodeType.IMG ||
|
|
336
375
|
elementInfo?.nodeType === NodeType.TEXT ||
|
|
376
|
+
elementInfo?.nodeType === NodeType.A ||
|
|
337
377
|
elementInfo?.nodeType === NodeType.FORM_ITEM ||
|
|
338
378
|
elementInfo?.nodeType === NodeType.CONTAINER
|
|
339
379
|
) {
|
|
@@ -397,3 +437,56 @@ export function extractTreeNode(
|
|
|
397
437
|
children: topChildren,
|
|
398
438
|
};
|
|
399
439
|
}
|
|
440
|
+
|
|
441
|
+
export function mergeElementAndChildrenRects(
|
|
442
|
+
node: Node,
|
|
443
|
+
currentWindow: typeof window,
|
|
444
|
+
currentDocument: typeof document,
|
|
445
|
+
baseZoom = 1,
|
|
446
|
+
visibleOnly = true,
|
|
447
|
+
) {
|
|
448
|
+
const selfRect = elementRect(
|
|
449
|
+
node,
|
|
450
|
+
currentWindow,
|
|
451
|
+
currentDocument,
|
|
452
|
+
baseZoom,
|
|
453
|
+
visibleOnly,
|
|
454
|
+
);
|
|
455
|
+
if (!selfRect) return null;
|
|
456
|
+
|
|
457
|
+
let minLeft = selfRect.left;
|
|
458
|
+
let minTop = selfRect.top;
|
|
459
|
+
let maxRight = selfRect.left + selfRect.width;
|
|
460
|
+
let maxBottom = selfRect.top + selfRect.height;
|
|
461
|
+
|
|
462
|
+
function traverse(child: Node) {
|
|
463
|
+
for (let i = 0; i < child.childNodes.length; i++) {
|
|
464
|
+
const sub = child.childNodes[i];
|
|
465
|
+
if (sub.nodeType === 1) {
|
|
466
|
+
const rect = elementRect(
|
|
467
|
+
sub,
|
|
468
|
+
currentWindow,
|
|
469
|
+
currentDocument,
|
|
470
|
+
baseZoom,
|
|
471
|
+
visibleOnly,
|
|
472
|
+
);
|
|
473
|
+
if (rect) {
|
|
474
|
+
minLeft = Math.min(minLeft, rect.left);
|
|
475
|
+
minTop = Math.min(minTop, rect.top);
|
|
476
|
+
maxRight = Math.max(maxRight, rect.left + rect.width);
|
|
477
|
+
maxBottom = Math.max(maxBottom, rect.top + rect.height);
|
|
478
|
+
}
|
|
479
|
+
traverse(sub);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
traverse(node);
|
|
484
|
+
|
|
485
|
+
return {
|
|
486
|
+
...selfRect,
|
|
487
|
+
left: minLeft,
|
|
488
|
+
top: minTop,
|
|
489
|
+
width: maxRight - minLeft,
|
|
490
|
+
height: maxBottom - minTop,
|
|
491
|
+
};
|
|
492
|
+
}
|
package/src/logger.ts
CHANGED
|
@@ -2,7 +2,7 @@ import fs from 'node:fs';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import util from 'node:util';
|
|
4
4
|
import debug from 'debug';
|
|
5
|
-
import {
|
|
5
|
+
import { getMidsceneRunSubDir, isNodeEnv } from './common';
|
|
6
6
|
|
|
7
7
|
const topicPrefix = 'midscene';
|
|
8
8
|
// Map to store file streams
|
|
@@ -12,7 +12,10 @@ const logStreams = new Map<string, fs.WriteStream>();
|
|
|
12
12
|
function getLogStream(topic: string): fs.WriteStream {
|
|
13
13
|
const topicFileName = topic.replace(/:/g, '-');
|
|
14
14
|
if (!logStreams.has(topicFileName)) {
|
|
15
|
-
const logFile = path.join(
|
|
15
|
+
const logFile = path.join(
|
|
16
|
+
getMidsceneRunSubDir('log'),
|
|
17
|
+
`${topicFileName}.log`,
|
|
18
|
+
);
|
|
16
19
|
const stream = fs.createWriteStream(logFile, { flags: 'a' });
|
|
17
20
|
logStreams.set(topicFileName, stream);
|
|
18
21
|
}
|