@godscene/shared 1.7.11

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.
Files changed (236) hide show
  1. package/README.md +9 -0
  2. package/dist/es/baseDB.mjs +109 -0
  3. package/dist/es/cli/cli-args.mjs +95 -0
  4. package/dist/es/cli/cli-error.mjs +24 -0
  5. package/dist/es/cli/cli-runner.mjs +122 -0
  6. package/dist/es/cli/index.mjs +4 -0
  7. package/dist/es/common.mjs +37 -0
  8. package/dist/es/constants/example-code.mjs +227 -0
  9. package/dist/es/constants/index.mjs +124 -0
  10. package/dist/es/env/basic.mjs +6 -0
  11. package/dist/es/env/constants.mjs +110 -0
  12. package/dist/es/env/global-config-manager.mjs +94 -0
  13. package/dist/es/env/helper.mjs +43 -0
  14. package/dist/es/env/index.mjs +5 -0
  15. package/dist/es/env/init-debug.mjs +18 -0
  16. package/dist/es/env/model-config-manager.mjs +79 -0
  17. package/dist/es/env/parse-model-config.mjs +165 -0
  18. package/dist/es/env/types.mjs +232 -0
  19. package/dist/es/env/utils.mjs +18 -0
  20. package/dist/es/extractor/constants.mjs +2 -0
  21. package/dist/es/extractor/cs_postmessage.mjs +61 -0
  22. package/dist/es/extractor/customLocator.mjs +641 -0
  23. package/dist/es/extractor/debug.mjs +6 -0
  24. package/dist/es/extractor/dom-util.mjs +96 -0
  25. package/dist/es/extractor/index.mjs +5 -0
  26. package/dist/es/extractor/locator.mjs +250 -0
  27. package/dist/es/extractor/tree.mjs +78 -0
  28. package/dist/es/extractor/util.mjs +245 -0
  29. package/dist/es/extractor/web-extractor.mjs +393 -0
  30. package/dist/es/img/box-select.mjs +824 -0
  31. package/dist/es/img/canvas-fallback.mjs +238 -0
  32. package/dist/es/img/get-photon.mjs +45 -0
  33. package/dist/es/img/get-sharp.mjs +11 -0
  34. package/dist/es/img/index.mjs +4 -0
  35. package/dist/es/img/info.mjs +35 -0
  36. package/dist/es/img/transform.mjs +275 -0
  37. package/dist/es/index.mjs +2 -0
  38. package/dist/es/key-alias-utils.mjs +19 -0
  39. package/dist/es/logger.mjs +64 -0
  40. package/dist/es/mcp/base-server.mjs +282 -0
  41. package/dist/es/mcp/base-tools.mjs +159 -0
  42. package/dist/es/mcp/chrome-path.mjs +35 -0
  43. package/dist/es/mcp/cli-report-session.mjs +78 -0
  44. package/dist/es/mcp/error-formatter.mjs +19 -0
  45. package/dist/es/mcp/index.mjs +9 -0
  46. package/dist/es/mcp/init-arg-utils.mjs +38 -0
  47. package/dist/es/mcp/inject-report-html-plugin.mjs +53 -0
  48. package/dist/es/mcp/launcher-helper.mjs +52 -0
  49. package/dist/es/mcp/tool-generator.mjs +419 -0
  50. package/dist/es/mcp/types.mjs +3 -0
  51. package/dist/es/node/fs.mjs +44 -0
  52. package/dist/es/node/index.mjs +2 -0
  53. package/dist/es/node/port.mjs +24 -0
  54. package/dist/es/polyfills/async-hooks.mjs +2 -0
  55. package/dist/es/polyfills/index.mjs +1 -0
  56. package/dist/es/types/index.mjs +3 -0
  57. package/dist/es/us-keyboard-layout.mjs +1414 -0
  58. package/dist/es/us-keyboard-layout.mjs.LICENSE.txt +5 -0
  59. package/dist/es/utils.mjs +72 -0
  60. package/dist/es/zod-schema-utils.mjs +54 -0
  61. package/dist/lib/baseDB.js +149 -0
  62. package/dist/lib/cli/cli-args.js +138 -0
  63. package/dist/lib/cli/cli-error.js +61 -0
  64. package/dist/lib/cli/cli-runner.js +181 -0
  65. package/dist/lib/cli/index.js +53 -0
  66. package/dist/lib/common.js +93 -0
  67. package/dist/lib/constants/example-code.js +264 -0
  68. package/dist/lib/constants/index.js +221 -0
  69. package/dist/lib/env/basic.js +40 -0
  70. package/dist/lib/env/constants.js +153 -0
  71. package/dist/lib/env/global-config-manager.js +128 -0
  72. package/dist/lib/env/helper.js +80 -0
  73. package/dist/lib/env/index.js +90 -0
  74. package/dist/lib/env/init-debug.js +52 -0
  75. package/dist/lib/env/model-config-manager.js +113 -0
  76. package/dist/lib/env/parse-model-config.js +211 -0
  77. package/dist/lib/env/types.js +572 -0
  78. package/dist/lib/env/utils.js +61 -0
  79. package/dist/lib/extractor/constants.js +42 -0
  80. package/dist/lib/extractor/cs_postmessage.js +98 -0
  81. package/dist/lib/extractor/customLocator.js +693 -0
  82. package/dist/lib/extractor/debug.js +12 -0
  83. package/dist/lib/extractor/dom-util.js +157 -0
  84. package/dist/lib/extractor/index.js +87 -0
  85. package/dist/lib/extractor/locator.js +296 -0
  86. package/dist/lib/extractor/tree.js +124 -0
  87. package/dist/lib/extractor/util.js +336 -0
  88. package/dist/lib/extractor/web-extractor.js +442 -0
  89. package/dist/lib/img/box-select.js +875 -0
  90. package/dist/lib/img/canvas-fallback.js +305 -0
  91. package/dist/lib/img/get-photon.js +82 -0
  92. package/dist/lib/img/get-sharp.js +45 -0
  93. package/dist/lib/img/index.js +95 -0
  94. package/dist/lib/img/info.js +92 -0
  95. package/dist/lib/img/transform.js +364 -0
  96. package/dist/lib/index.js +36 -0
  97. package/dist/lib/key-alias-utils.js +62 -0
  98. package/dist/lib/logger.js +114 -0
  99. package/dist/lib/mcp/base-server.js +332 -0
  100. package/dist/lib/mcp/base-tools.js +193 -0
  101. package/dist/lib/mcp/chrome-path.js +72 -0
  102. package/dist/lib/mcp/cli-report-session.js +121 -0
  103. package/dist/lib/mcp/error-formatter.js +53 -0
  104. package/dist/lib/mcp/index.js +114 -0
  105. package/dist/lib/mcp/init-arg-utils.js +78 -0
  106. package/dist/lib/mcp/inject-report-html-plugin.js +98 -0
  107. package/dist/lib/mcp/launcher-helper.js +86 -0
  108. package/dist/lib/mcp/tool-generator.js +456 -0
  109. package/dist/lib/mcp/types.js +40 -0
  110. package/dist/lib/node/fs.js +97 -0
  111. package/dist/lib/node/index.js +65 -0
  112. package/dist/lib/node/port.js +61 -0
  113. package/dist/lib/polyfills/async-hooks.js +36 -0
  114. package/dist/lib/polyfills/index.js +58 -0
  115. package/dist/lib/types/index.js +37 -0
  116. package/dist/lib/us-keyboard-layout.js +1457 -0
  117. package/dist/lib/us-keyboard-layout.js.LICENSE.txt +5 -0
  118. package/dist/lib/utils.js +148 -0
  119. package/dist/lib/zod-schema-utils.js +97 -0
  120. package/dist/types/baseDB.d.ts +25 -0
  121. package/dist/types/cli/cli-args.d.ts +8 -0
  122. package/dist/types/cli/cli-error.d.ts +5 -0
  123. package/dist/types/cli/cli-runner.d.ts +19 -0
  124. package/dist/types/cli/index.d.ts +4 -0
  125. package/dist/types/common.d.ts +12 -0
  126. package/dist/types/constants/example-code.d.ts +2 -0
  127. package/dist/types/constants/index.d.ts +61 -0
  128. package/dist/types/env/basic.d.ts +6 -0
  129. package/dist/types/env/constants.d.ts +50 -0
  130. package/dist/types/env/global-config-manager.d.ts +32 -0
  131. package/dist/types/env/helper.d.ts +4 -0
  132. package/dist/types/env/index.d.ts +4 -0
  133. package/dist/types/env/init-debug.d.ts +1 -0
  134. package/dist/types/env/model-config-manager.d.ts +25 -0
  135. package/dist/types/env/parse-model-config.d.ts +31 -0
  136. package/dist/types/env/types.d.ts +339 -0
  137. package/dist/types/env/utils.d.ts +7 -0
  138. package/dist/types/extractor/constants.d.ts +1 -0
  139. package/dist/types/extractor/cs_postmessage.d.ts +2 -0
  140. package/dist/types/extractor/customLocator.d.ts +69 -0
  141. package/dist/types/extractor/debug.d.ts +1 -0
  142. package/dist/types/extractor/dom-util.d.ts +57 -0
  143. package/dist/types/extractor/index.d.ts +33 -0
  144. package/dist/types/extractor/locator.d.ts +9 -0
  145. package/dist/types/extractor/tree.d.ts +6 -0
  146. package/dist/types/extractor/util.d.ts +47 -0
  147. package/dist/types/extractor/web-extractor.d.ts +24 -0
  148. package/dist/types/img/box-select.d.ts +26 -0
  149. package/dist/types/img/canvas-fallback.d.ts +105 -0
  150. package/dist/types/img/get-photon.d.ts +19 -0
  151. package/dist/types/img/get-sharp.d.ts +3 -0
  152. package/dist/types/img/index.d.ts +3 -0
  153. package/dist/types/img/info.d.ts +34 -0
  154. package/dist/types/img/transform.d.ts +98 -0
  155. package/dist/types/index.d.ts +2 -0
  156. package/dist/types/key-alias-utils.d.ts +9 -0
  157. package/dist/types/logger.d.ts +5 -0
  158. package/dist/types/mcp/base-server.d.ts +93 -0
  159. package/dist/types/mcp/base-tools.d.ts +148 -0
  160. package/dist/types/mcp/chrome-path.d.ts +2 -0
  161. package/dist/types/mcp/cli-report-session.d.ts +12 -0
  162. package/dist/types/mcp/error-formatter.d.ts +12 -0
  163. package/dist/types/mcp/index.d.ts +9 -0
  164. package/dist/types/mcp/init-arg-utils.d.ts +13 -0
  165. package/dist/types/mcp/inject-report-html-plugin.d.ts +18 -0
  166. package/dist/types/mcp/launcher-helper.d.ts +94 -0
  167. package/dist/types/mcp/tool-generator.d.ts +10 -0
  168. package/dist/types/mcp/types.d.ts +113 -0
  169. package/dist/types/node/fs.d.ts +15 -0
  170. package/dist/types/node/index.d.ts +2 -0
  171. package/dist/types/node/port.d.ts +8 -0
  172. package/dist/types/polyfills/async-hooks.d.ts +6 -0
  173. package/dist/types/polyfills/index.d.ts +4 -0
  174. package/dist/types/types/index.d.ts +36 -0
  175. package/dist/types/us-keyboard-layout.d.ts +32 -0
  176. package/dist/types/utils.d.ts +34 -0
  177. package/dist/types/zod-schema-utils.d.ts +23 -0
  178. package/package.json +125 -0
  179. package/src/baseDB.ts +158 -0
  180. package/src/cli/cli-args.ts +173 -0
  181. package/src/cli/cli-error.ts +24 -0
  182. package/src/cli/cli-runner.ts +230 -0
  183. package/src/cli/index.ts +4 -0
  184. package/src/common.ts +67 -0
  185. package/src/constants/example-code.ts +227 -0
  186. package/src/constants/index.ts +139 -0
  187. package/src/env/basic.ts +12 -0
  188. package/src/env/constants.ts +303 -0
  189. package/src/env/global-config-manager.ts +191 -0
  190. package/src/env/helper.ts +58 -0
  191. package/src/env/index.ts +4 -0
  192. package/src/env/init-debug.ts +34 -0
  193. package/src/env/model-config-manager.ts +149 -0
  194. package/src/env/parse-model-config.ts +357 -0
  195. package/src/env/types.ts +583 -0
  196. package/src/env/utils.ts +39 -0
  197. package/src/extractor/constants.ts +5 -0
  198. package/src/extractor/cs_postmessage.ts +136 -0
  199. package/src/extractor/customLocator.ts +1245 -0
  200. package/src/extractor/debug.ts +10 -0
  201. package/src/extractor/dom-util.ts +231 -0
  202. package/src/extractor/index.ts +50 -0
  203. package/src/extractor/locator.ts +469 -0
  204. package/src/extractor/tree.ts +179 -0
  205. package/src/extractor/util.ts +482 -0
  206. package/src/extractor/web-extractor.ts +617 -0
  207. package/src/img/box-select.ts +588 -0
  208. package/src/img/canvas-fallback.ts +393 -0
  209. package/src/img/get-photon.ts +108 -0
  210. package/src/img/get-sharp.ts +18 -0
  211. package/src/img/index.ts +27 -0
  212. package/src/img/info.ts +102 -0
  213. package/src/img/transform.ts +553 -0
  214. package/src/index.ts +1 -0
  215. package/src/key-alias-utils.ts +23 -0
  216. package/src/logger.ts +96 -0
  217. package/src/mcp/base-server.ts +500 -0
  218. package/src/mcp/base-tools.ts +391 -0
  219. package/src/mcp/chrome-path.ts +48 -0
  220. package/src/mcp/cli-report-session.ts +130 -0
  221. package/src/mcp/error-formatter.ts +52 -0
  222. package/src/mcp/index.ts +9 -0
  223. package/src/mcp/init-arg-utils.ts +105 -0
  224. package/src/mcp/inject-report-html-plugin.ts +119 -0
  225. package/src/mcp/launcher-helper.ts +200 -0
  226. package/src/mcp/tool-generator.ts +658 -0
  227. package/src/mcp/types.ts +131 -0
  228. package/src/node/fs.ts +84 -0
  229. package/src/node/index.ts +2 -0
  230. package/src/node/port.ts +37 -0
  231. package/src/polyfills/async-hooks.ts +6 -0
  232. package/src/polyfills/index.ts +4 -0
  233. package/src/types/index.ts +54 -0
  234. package/src/us-keyboard-layout.ts +723 -0
  235. package/src/utils.ts +149 -0
  236. package/src/zod-schema-utils.ts +133 -0
@@ -0,0 +1,617 @@
1
+ import {
2
+ CONTAINER_MINI_HEIGHT,
3
+ CONTAINER_MINI_WIDTH,
4
+ NodeType,
5
+ } from '../constants/index';
6
+ import type { WebElementInfo } from '../types';
7
+ import type { Point } from '../types';
8
+ import {
9
+ isAElement,
10
+ isButtonElement,
11
+ isContainerElement,
12
+ isFormElement,
13
+ isImgElement,
14
+ isTextElement,
15
+ } from './dom-util';
16
+ import { descriptionOfTree } from './tree';
17
+ import {
18
+ elementRect,
19
+ getNodeAttributes,
20
+ getPseudoElementContent,
21
+ getRect,
22
+ getTopDocument,
23
+ logger,
24
+ midsceneGenerateHash,
25
+ setDebugMode,
26
+ } from './util';
27
+ import { onWindowMessage, postWindowMessage } from './cs_postmessage';
28
+ import { getContainerPath, getLocators } from './customLocator';
29
+
30
+ let indexId = 0;
31
+
32
+ function tagNameOfNode(node: globalThis.Node): string {
33
+ let tagName = '';
34
+ if (node instanceof HTMLElement) {
35
+ tagName = node.tagName?.toLowerCase();
36
+ } else {
37
+ const parentElement = node.parentElement;
38
+ if (parentElement && parentElement instanceof HTMLElement) {
39
+ tagName = parentElement.tagName?.toLowerCase();
40
+ }
41
+ }
42
+
43
+ return tagName ? `<${tagName}>` : '';
44
+ }
45
+
46
+ export function collectElementInfo(
47
+ node: Node,
48
+ currentWindow: typeof window,
49
+ currentDocument: typeof document,
50
+ baseZoom = 1,
51
+ basePoint: Point = { left: 0, top: 0 },
52
+ isContainer = false, // if true, the element will be considered as a container
53
+ ): WebElementInfo | null | any {
54
+ const rect = elementRect(node, currentWindow, currentDocument, baseZoom);
55
+
56
+ if (!rect) {
57
+ return null;
58
+ }
59
+
60
+ if (
61
+ rect.width < CONTAINER_MINI_WIDTH ||
62
+ rect.height < CONTAINER_MINI_HEIGHT
63
+ ) {
64
+ return null;
65
+ }
66
+
67
+ if (basePoint.left !== 0 || basePoint.top !== 0) {
68
+ rect.left += basePoint.left;
69
+ rect.top += basePoint.top;
70
+ }
71
+ // Skip elements that cover the entire viewport, as they are likely background containers
72
+ // rather than meaningful interactive elements
73
+ if (rect.height >= window.innerHeight && rect.width >= window.innerWidth) {
74
+ return null;
75
+ }
76
+
77
+ if (isFormElement(node)) {
78
+ const attributes = getNodeAttributes(node, currentWindow);
79
+ let valueContent =
80
+ attributes.value || attributes.placeholder || node.textContent || '';
81
+ const nodeHashId = midsceneGenerateHash(node, valueContent, rect);
82
+ const tagName = (node as HTMLElement).tagName.toLowerCase();
83
+ if ((node as HTMLElement).tagName.toLowerCase() === 'select') {
84
+ // Get the selected option using the selectedIndex property
85
+ const selectedOption = (node as HTMLSelectElement).options[
86
+ (node as HTMLSelectElement).selectedIndex
87
+ ];
88
+
89
+ // Retrieve the text content of the selected option
90
+ valueContent = selectedOption?.textContent || '';
91
+ }
92
+
93
+ if (
94
+ ((node as HTMLElement).tagName.toLowerCase() === 'input' ||
95
+ (node as HTMLElement).tagName.toLowerCase() === 'textarea') &&
96
+ (node as HTMLInputElement).value
97
+ ) {
98
+ valueContent = (node as HTMLInputElement).value;
99
+ }
100
+
101
+ const elementInfo: WebElementInfo = {
102
+ id: nodeHashId,
103
+ nodeHashId,
104
+ nodeType: NodeType.FORM_ITEM,
105
+ indexId: indexId++,
106
+ attributes: {
107
+ ...attributes,
108
+ htmlTagName: `<${tagName}>`,
109
+ nodeType: NodeType.FORM_ITEM,
110
+ },
111
+ content: valueContent.trim(),
112
+ rect,
113
+ center: [
114
+ Math.round(rect.left + rect.width / 2),
115
+ Math.round(rect.top + rect.height / 2),
116
+ ],
117
+ zoom: rect.zoom,
118
+ isVisible: rect.isVisible,
119
+ };
120
+ return elementInfo;
121
+ }
122
+
123
+ if (isButtonElement(node)) {
124
+ const rect = mergeElementAndChildrenRects(
125
+ node,
126
+ currentWindow,
127
+ currentDocument,
128
+ baseZoom,
129
+ );
130
+ if (!rect) {
131
+ return null;
132
+ }
133
+ const attributes = getNodeAttributes(node, currentWindow);
134
+ const pseudo = getPseudoElementContent(node, currentWindow);
135
+ const content = node.innerText || pseudo.before || pseudo.after || '';
136
+ const nodeHashId = midsceneGenerateHash(node, content, rect);
137
+ const elementInfo: WebElementInfo = {
138
+ id: nodeHashId,
139
+ indexId: indexId++,
140
+ nodeHashId,
141
+ nodeType: NodeType.BUTTON,
142
+ attributes: {
143
+ ...attributes,
144
+ htmlTagName: tagNameOfNode(node),
145
+ nodeType: NodeType.BUTTON,
146
+ },
147
+ content,
148
+ rect,
149
+ center: [
150
+ Math.round(rect.left + rect.width / 2),
151
+ Math.round(rect.top + rect.height / 2),
152
+ ],
153
+ zoom: rect.zoom,
154
+ isVisible: rect.isVisible,
155
+ };
156
+ return elementInfo;
157
+ }
158
+
159
+ if (isImgElement(node)) {
160
+ const attributes = getNodeAttributes(node, currentWindow);
161
+ const nodeHashId = midsceneGenerateHash(node, '', rect);
162
+ const elementInfo: WebElementInfo = {
163
+ id: nodeHashId,
164
+ indexId: indexId++,
165
+ nodeHashId,
166
+ attributes: {
167
+ ...attributes,
168
+ ...(node.nodeName?.toLowerCase() === 'svg'
169
+ ? {
170
+ svgContent: 'true',
171
+ }
172
+ : {}),
173
+ nodeType: NodeType.IMG,
174
+ htmlTagName: tagNameOfNode(node),
175
+ },
176
+ nodeType: NodeType.IMG,
177
+ content: '',
178
+ rect,
179
+ center: [
180
+ Math.round(rect.left + rect.width / 2),
181
+ Math.round(rect.top + rect.height / 2),
182
+ ],
183
+ zoom: rect.zoom,
184
+ isVisible: rect.isVisible,
185
+ };
186
+ return elementInfo;
187
+ }
188
+
189
+ if (isTextElement(node)) {
190
+ const text = node.textContent?.trim().replace(/\n+/g, ' ');
191
+ if (!text) {
192
+ return null;
193
+ }
194
+ const attributes = getNodeAttributes(node, currentWindow);
195
+ const attributeKeys = Object.keys(attributes);
196
+ if (!text.trim() && attributeKeys.length === 0) {
197
+ return null;
198
+ }
199
+ const nodeHashId = midsceneGenerateHash(node, text, rect);
200
+ const elementInfo: WebElementInfo = {
201
+ id: nodeHashId,
202
+ indexId: indexId++,
203
+ nodeHashId,
204
+ nodeType: NodeType.TEXT,
205
+ attributes: {
206
+ ...attributes,
207
+ nodeType: NodeType.TEXT,
208
+ htmlTagName: tagNameOfNode(node),
209
+ },
210
+ center: [
211
+ Math.round(rect.left + rect.width / 2),
212
+ Math.round(rect.top + rect.height / 2),
213
+ ],
214
+ content: text,
215
+ rect,
216
+ zoom: rect.zoom,
217
+ isVisible: rect.isVisible,
218
+ };
219
+ return elementInfo;
220
+ }
221
+
222
+ if (isAElement(node)) {
223
+ const attributes = getNodeAttributes(node, currentWindow);
224
+ const pseudo = getPseudoElementContent(node, currentWindow);
225
+ const content = node.innerText || pseudo.before || pseudo.after || '';
226
+ const nodeHashId = midsceneGenerateHash(node, content, rect);
227
+ const elementInfo: WebElementInfo = {
228
+ id: nodeHashId,
229
+ indexId: indexId++,
230
+ nodeHashId,
231
+ nodeType: NodeType.A,
232
+ attributes: {
233
+ ...attributes,
234
+ htmlTagName: tagNameOfNode(node),
235
+ nodeType: NodeType.A,
236
+ },
237
+ content,
238
+ rect,
239
+ center: [
240
+ Math.round(rect.left + rect.width / 2),
241
+ Math.round(rect.top + rect.height / 2),
242
+ ],
243
+ zoom: rect.zoom,
244
+ isVisible: rect.isVisible,
245
+ };
246
+ return elementInfo;
247
+ }
248
+
249
+ // else, consider as a container
250
+ if (isContainerElement(node) || isContainer) {
251
+ const attributes = getNodeAttributes(node, currentWindow);
252
+ const nodeHashId = midsceneGenerateHash(node, '', rect);
253
+ const elementInfo: WebElementInfo = {
254
+ id: nodeHashId,
255
+ nodeHashId,
256
+ indexId: indexId++,
257
+ nodeType: NodeType.CONTAINER,
258
+ attributes: {
259
+ ...attributes,
260
+ nodeType: NodeType.CONTAINER,
261
+ htmlTagName: tagNameOfNode(node),
262
+ },
263
+ content: '',
264
+ rect,
265
+ center: [
266
+ Math.round(rect.left + rect.width / 2),
267
+ Math.round(rect.top + rect.height / 2),
268
+ ],
269
+ zoom: rect.zoom,
270
+ isVisible: rect.isVisible,
271
+ };
272
+ return elementInfo;
273
+ }
274
+ return null;
275
+ }
276
+
277
+ interface WebElementNode {
278
+ node: WebElementInfo | null;
279
+ children: WebElementNode[];
280
+ }
281
+
282
+ // @deprecated
283
+ export async function extractTextWithPosition(
284
+ initNode: globalThis.Node,
285
+ debugMode = false,
286
+ ): Promise<WebElementInfo[]> {
287
+ const elementNode = await extractTreeNode(initNode, debugMode);
288
+
289
+ // dfs topChildren
290
+ const elementInfoArray: WebElementInfo[] = [];
291
+ function dfsTopChildren(node: WebElementNode) {
292
+ if (node.node) {
293
+ elementInfoArray.push(node.node);
294
+ }
295
+ for (let i = 0; i < node.children.length; i++) {
296
+ dfsTopChildren(node.children[i]);
297
+ }
298
+ }
299
+ dfsTopChildren({ children: elementNode.children, node: elementNode.node });
300
+ return elementInfoArray;
301
+ }
302
+
303
+ export async function extractTreeNodeAsString(
304
+ initNode: globalThis.Node,
305
+ visibleOnly = false,
306
+ debugMode = false,
307
+ ): Promise<string> {
308
+ const elementNode = await extractTreeNode(initNode, debugMode);
309
+ return descriptionOfTree(elementNode, undefined, false, visibleOnly);
310
+ }
311
+
312
+ // 提取树节点
313
+ export async function extractTreeNode(
314
+ initNode: globalThis.Node,
315
+ debugMode = false,
316
+ basePoint: Point = { left: 0, top: 0 },
317
+ baseZoom = 1,
318
+ ): Promise<WebElementNode> {
319
+ setDebugMode(debugMode);
320
+ indexId = 0;
321
+ const topDocument = getTopDocument();
322
+ const startNode = initNode || topDocument;
323
+ const topChildren: WebElementNode[] = [];
324
+
325
+ // 递归函数dfs
326
+ function dfs(
327
+ node: globalThis.Node,
328
+ currentWindow: typeof globalThis.window,
329
+ currentDocument: typeof globalThis.document,
330
+ baseZoom = 1,
331
+ basePoint: Point = { left: 0, top: 0 },
332
+ ): WebElementNode | WebElementNode[] | null {
333
+ if (!node) {
334
+ return null;
335
+ }
336
+
337
+ if (node.nodeType && node.nodeType === 10) {
338
+ // Doctype node
339
+ return null;
340
+ }
341
+
342
+ const elementInfo = collectElementInfo(
343
+ node,
344
+ currentWindow,
345
+ currentDocument,
346
+ baseZoom,
347
+ basePoint,
348
+ false,
349
+ );
350
+
351
+ if (node instanceof currentWindow.HTMLIFrameElement) {
352
+ if (
353
+ (node as HTMLIFrameElement).contentWindow &&
354
+ (node as HTMLIFrameElement).contentWindow
355
+ ) {
356
+ return null;
357
+ }
358
+ }
359
+
360
+ const nodeInfo: WebElementNode = {
361
+ node: elementInfo,
362
+ children: [],
363
+ };
364
+ // stop collecting if the node is a Button/Image/Text/FormItem/Container
365
+ if (
366
+ elementInfo?.nodeType === NodeType.BUTTON ||
367
+ elementInfo?.nodeType === NodeType.IMG ||
368
+ elementInfo?.nodeType === NodeType.TEXT ||
369
+ elementInfo?.nodeType === NodeType.FORM_ITEM ||
370
+ elementInfo?.nodeType === NodeType.CONTAINER // TODO: need return the container node?
371
+ ) {
372
+ return nodeInfo;
373
+ }
374
+
375
+ const rect = getRect(node, baseZoom, currentWindow);
376
+ for (let i = 0; i < node.childNodes.length; i++) {
377
+ logger('will dfs', node.childNodes[i]);
378
+ const childNodeInfo = dfs(
379
+ node.childNodes[i],
380
+ currentWindow,
381
+ currentDocument,
382
+ rect.zoom,
383
+ basePoint,
384
+ );
385
+ if (Array.isArray(childNodeInfo)) {
386
+ // if the recursive return is an array, expand and merge it into children
387
+ nodeInfo.children.push(...childNodeInfo);
388
+ } else if (childNodeInfo) {
389
+ nodeInfo.children.push(childNodeInfo);
390
+ }
391
+ }
392
+
393
+ // if nodeInfo.node is null
394
+ if (nodeInfo.node === null) {
395
+ if (nodeInfo.children.length === 0) {
396
+ return null;
397
+ }
398
+ // promote children to the upper layer
399
+ return nodeInfo.children;
400
+ }
401
+
402
+ return nodeInfo;
403
+ }
404
+
405
+ /**
406
+ * 获取css样式中元素的transform: scale(.8);
407
+ * @param {*} ele
408
+ * @returns
409
+ */
410
+ function getCssScale(ele: any) {
411
+ try {
412
+ const { width, height } = ele.getBoundingClientRect();
413
+ return {
414
+ cssScaleX: Number((width / ele.offsetWidth).toFixed(1)),
415
+ cssScaleY: Number((height / ele.offsetHeight).toFixed(1)),
416
+ };
417
+ } catch (error) {
418
+ return {
419
+ cssScaleX: 1,
420
+ cssScaleY: 1,
421
+ };
422
+ }
423
+ }
424
+
425
+ const rootNodeInfo = dfs(startNode, window, document, baseZoom, basePoint);
426
+ if (Array.isArray(rootNodeInfo)) {
427
+ topChildren.push(...rootNodeInfo);
428
+ } else if (rootNodeInfo) {
429
+ topChildren.push(rootNodeInfo);
430
+ }
431
+ if (startNode === topDocument) {
432
+ // find all the same-origin iframes
433
+ const iframes = document.querySelectorAll('iframe');
434
+ for (let i = 0; i < iframes.length; i++) {
435
+ const iframe = iframes[i];
436
+ const iframeInfo = collectElementInfo(iframe, window, document, 1);
437
+ // 这里不再生成containerPath
438
+ const baseZoom = getCssScale(iframe).cssScaleX;
439
+ if (iframeInfo) {
440
+ const iframeChildren = await postWindowMessage(
441
+ iframe.contentWindow,
442
+ window,
443
+ {
444
+ action: 'extractTreeNode',
445
+ data: {
446
+ basePoint: {
447
+ left: iframeInfo.rect.left,
448
+ top: iframeInfo.rect.top,
449
+ },
450
+ baseZoom,
451
+ },
452
+ },
453
+ );
454
+ if (Array.isArray(iframeChildren)) {
455
+ topChildren.push(...iframeChildren);
456
+ } else if (iframeChildren) {
457
+ topChildren.push(iframeChildren);
458
+ }
459
+ }
460
+ }
461
+ }
462
+
463
+ return {
464
+ node: null,
465
+ children: topChildren,
466
+ };
467
+ }
468
+
469
+ export function mergeElementAndChildrenRects(
470
+ node: Node,
471
+ currentWindow: typeof window,
472
+ currentDocument: typeof document,
473
+ baseZoom = 1,
474
+ ) {
475
+ const selfRect = elementRect(node, currentWindow, currentDocument, baseZoom);
476
+ if (!selfRect) return null;
477
+
478
+ let minLeft = selfRect.left;
479
+ let minTop = selfRect.top;
480
+ let maxRight = selfRect.left + selfRect.width;
481
+ let maxBottom = selfRect.top + selfRect.height;
482
+
483
+ function traverse(child: Node) {
484
+ for (let i = 0; i < child.childNodes.length; i++) {
485
+ const sub = child.childNodes[i];
486
+ if (sub.nodeType === 1) {
487
+ const rect = elementRect(sub, currentWindow, currentDocument, baseZoom);
488
+ if (rect) {
489
+ minLeft = Math.min(minLeft, rect.left);
490
+ minTop = Math.min(minTop, rect.top);
491
+ maxRight = Math.max(maxRight, rect.left + rect.width);
492
+ maxBottom = Math.max(maxBottom, rect.top + rect.height);
493
+ }
494
+ traverse(sub);
495
+ }
496
+ }
497
+ }
498
+ traverse(node);
499
+
500
+ return {
501
+ ...selfRect,
502
+ left: minLeft,
503
+ top: minTop,
504
+ width: maxRight - minLeft,
505
+ height: maxBottom - minTop,
506
+ };
507
+ }
508
+
509
+ if (typeof window !== 'undefined') {
510
+ onWindowMessage(window, async ({ action, data }: any, { source }: any) => {
511
+ if (action === 'extractTreeNode') {
512
+ // @ts-ignore
513
+ try {
514
+ return await extractTreeNode(
515
+ document.body,
516
+ false,
517
+ data.basePoint,
518
+ data.baseZoom,
519
+ );
520
+ } catch (error) {
521
+ return {
522
+ error: 'error message',
523
+ };
524
+ }
525
+ } else if (action === 'getElementFromPoint') {
526
+ // @ts-ignore
527
+ try {
528
+ return await getElementFromPoint({
529
+ x: data.x,
530
+ y: data.y,
531
+ containerPaths: data.containerPaths,
532
+ });
533
+ } catch (error) {
534
+ return {
535
+ error: 'error message',
536
+ };
537
+ }
538
+ }
539
+ });
540
+ }
541
+
542
+ const getElementOffset = (element: any) => {
543
+ const pLeft =
544
+ Number.parseInt(
545
+ window.getComputedStyle(element, null).getPropertyValue('padding-left'),
546
+ 10,
547
+ ) || 0;
548
+ const pTop =
549
+ Number.parseInt(
550
+ window.getComputedStyle(element, null).getPropertyValue('padding-top'),
551
+ 10,
552
+ ) || 0;
553
+ const bLeft =
554
+ Number.parseInt(
555
+ window
556
+ .getComputedStyle(element, null)
557
+ .getPropertyValue('border-left-width'),
558
+ 10,
559
+ ) || 0;
560
+ const bTop =
561
+ Number.parseInt(
562
+ window
563
+ .getComputedStyle(element, null)
564
+ .getPropertyValue('border-top-width'),
565
+ 10,
566
+ ) || 0;
567
+ return {
568
+ x: pLeft + bLeft,
569
+ y: pTop + bTop,
570
+ };
571
+ };
572
+
573
+ const getCssScale = (element: any) => {
574
+ try {
575
+ const { width, height } = element.getBoundingClientRect();
576
+ return {
577
+ cssScaleX: Number((width / element.offsetWidth).toFixed(1)),
578
+ cssScaleY: Number((height / element.offsetHeight).toFixed(1)),
579
+ };
580
+ } catch (error) {
581
+ return {
582
+ cssScaleX: 1,
583
+ cssScaleY: 1,
584
+ };
585
+ }
586
+ };
587
+
588
+ export const getElementFromPoint = async (args: {
589
+ x: number;
590
+ y: number;
591
+ containerPaths: string[];
592
+ }) => {
593
+ if (!args.containerPaths) {
594
+ args.containerPaths = [];
595
+ }
596
+ const dom: any = document.elementFromPoint(args.x, args.y);
597
+ if (dom?.nodeName === 'IFRAME') {
598
+ const containerPath: any = getContainerPath(dom);
599
+ const bounding = dom.getBoundingClientRect();
600
+ const offset = getElementOffset(dom);
601
+ const { cssScaleX, cssScaleY } = getCssScale(dom);
602
+ return await postWindowMessage(dom.contentWindow, window, {
603
+ action: 'getElementFromPoint',
604
+ data: {
605
+ x: (args.x - bounding.x - offset.x) / cssScaleX,
606
+ y: (args.y - bounding.y - offset.y) / cssScaleY,
607
+ containerPaths: args.containerPaths.concat(containerPath),
608
+ },
609
+ });
610
+ } else {
611
+ const elementInfo = {
612
+ allPaths: getLocators(dom),
613
+ containerPaths: args.containerPaths,
614
+ };
615
+ return elementInfo;
616
+ }
617
+ };