@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,1245 @@
1
+ /**
2
+ *
3
+ * 属性值中存在单双引号的处理
4
+ * XPath中使用
5
+ * @param {*} value
6
+ * @returns
7
+ */
8
+ const attributeValue = (value: string) => {
9
+ if (!value) return '';
10
+ if (value.indexOf("'") < 0) {
11
+ return "'" + value + "'";
12
+ } else if (value.indexOf('"') < 0) {
13
+ return '"' + value + '"';
14
+ } else {
15
+ let result = 'concat(';
16
+ let part = '';
17
+ let didReachEndOfValue = false;
18
+ while (!didReachEndOfValue) {
19
+ const apos = value.indexOf("'");
20
+ const quot = value.indexOf('"');
21
+ if (apos < 0) {
22
+ result += "'" + value + "'";
23
+ didReachEndOfValue = true;
24
+ break;
25
+ } else if (quot < 0) {
26
+ result += '"' + value + '"';
27
+ didReachEndOfValue = true;
28
+ break;
29
+ } else if (quot < apos) {
30
+ part = value.substring(0, apos);
31
+ result += "'" + part + "'";
32
+ value = value.substring(part.length);
33
+ } else {
34
+ part = value.substring(0, quot);
35
+ result += '"' + part + '"';
36
+ value = value.substring(part.length);
37
+ }
38
+ result += ',';
39
+ }
40
+ result += ')';
41
+ return result;
42
+ }
43
+ };
44
+
45
+ /**
46
+ * 验证XPath是否可以定位到唯一元素
47
+ * @param {*} xpath
48
+ * @param {*} dom
49
+ * @returns
50
+ */
51
+ const verifyXPath = (xpath: string, dom: any) => {
52
+ const root = dom.getRootNode();
53
+ if (isShadowRoot(root)) {
54
+ return null;
55
+ }
56
+ if (xpath) {
57
+ const doms = getElementsByLocator({
58
+ type: 'xpath',
59
+ value: xpath,
60
+ });
61
+ if (doms.length === 1 && doms[0] === dom) {
62
+ return xpath;
63
+ }
64
+ }
65
+ return null;
66
+ };
67
+
68
+ /**
69
+ * 验证selector是否可以定位到唯一元素
70
+ * @param {*} selector
71
+ * @param {*} dom
72
+ * @returns
73
+ */
74
+ const verifySelector = (selector: string, dom: any, type = 'querySelect') => {
75
+ const root = dom.getRootNode();
76
+ const doms = getElementsByLocator(
77
+ {
78
+ type,
79
+ value: selector,
80
+ },
81
+ root,
82
+ );
83
+ if (doms.length === 1 && doms[0] === dom) {
84
+ return selector;
85
+ }
86
+
87
+ return null;
88
+ };
89
+
90
+ /**
91
+ * 是否为宿主元素
92
+ * @param {*} dom
93
+ * @returns
94
+ */
95
+ const isHostElement = (dom: any) => {
96
+ return dom.shadowRoot?.nodeName === '#document-fragment';
97
+ };
98
+
99
+ /**
100
+ * 是否为shadowRoot的rootNode
101
+ * @param {*} dom
102
+ * @returns
103
+ */
104
+ const isShadowRoot = (node: any) => {
105
+ // node.host
106
+ return node.nodeName === '#document-fragment';
107
+ };
108
+
109
+ /**
110
+ * 当前节点的相对selector
111
+ * @param {*} dom
112
+ * @returns
113
+ */
114
+ const getRelativeSelector = (dom: any, useFieldId = true) => {
115
+ try {
116
+ const id = dom.id;
117
+ const fieldid = dom.getAttribute('fieldid');
118
+ const children = Array.from(dom.parentNode.childNodes).filter(
119
+ ($el: any) => $el.nodeType === 1,
120
+ );
121
+ const index = children.findIndex((item) => item === dom) + 1;
122
+
123
+ let extra = '';
124
+ if (useFieldId && fieldid) {
125
+ // 有fieldid
126
+ const sameAttrChildren = children.filter(
127
+ ($el: any) =>
128
+ $el.tagName === dom.tagName &&
129
+ $el.getAttribute('fieldid') === fieldid,
130
+ );
131
+ if (sameAttrChildren.length === 1) {
132
+ extra = `[fieldid='${fieldid}']`;
133
+ } else {
134
+ extra = `[fieldid='${fieldid}']:nth-child(${index})`;
135
+ }
136
+ } else if (!useFieldId && id) {
137
+ // 有id
138
+ const sameAttrChildren = children.filter(
139
+ ($el: any) => $el.tagName === dom.tagName && $el.id === id,
140
+ );
141
+ if (sameAttrChildren.length === 1) {
142
+ extra = `[id='${id}']`;
143
+ } else {
144
+ extra = `[id='${id}']:nth-child(${index})`;
145
+ }
146
+ } else {
147
+ // 同tagName的兄弟元素
148
+ const sameTagChildren = children.filter(
149
+ ($el: any) => $el.tagName === dom.tagName,
150
+ );
151
+ if (sameTagChildren.length === 1) {
152
+ // 使用tagName
153
+ extra = '';
154
+ } else {
155
+ // 使用索引
156
+ extra = `:nth-child(${index})`;
157
+ }
158
+ }
159
+ return dom.tagName.toLowerCase() + extra;
160
+ } catch (error) {
161
+ console.error(error);
162
+ return '';
163
+ }
164
+ };
165
+
166
+ /**
167
+ * 当前节点的相对XPath
168
+ * @param {*} dom
169
+ * @returns "/div"
170
+ */
171
+ const getRelativeXpath = (dom: any, useFieldId = true) => {
172
+ try {
173
+ if (!dom || dom.nodeType !== 1) return '';
174
+ if (dom.tagName === 'OPTION') return '';
175
+ const root = dom.getRootNode();
176
+ if (isShadowRoot(root)) {
177
+ return '';
178
+ }
179
+ const id = dom.id;
180
+ const fieldid = dom.getAttribute('fieldid');
181
+ const children = Array.from(dom.parentNode.childNodes).filter(
182
+ ($el: any) => $el.nodeType === 1,
183
+ );
184
+ const tagName = dom.tagName.toLowerCase();
185
+ const isDescendantOfSVG = dom.closest('svg') !== null;
186
+ let extra = '';
187
+ if (useFieldId && fieldid) {
188
+ // 有fieldid
189
+ const sameAttrChildren = children.filter(
190
+ ($el: any) =>
191
+ $el.tagName === dom.tagName &&
192
+ $el.getAttribute('fieldid') === fieldid,
193
+ );
194
+ if (isDescendantOfSVG) {
195
+ if (sameAttrChildren.length === 1) {
196
+ return `/*[name()='${tagName}' and @fieldid='${fieldid}']`;
197
+ }
198
+ const index = sameAttrChildren.findIndex((item) => item === dom) + 1;
199
+ return `/*[name()='${tagName}' and @fieldid='${fieldid}'][${index}]`;
200
+ }
201
+ if (sameAttrChildren.length === 1) {
202
+ extra = `[@fieldid='${fieldid}']`;
203
+ } else {
204
+ const index = sameAttrChildren.findIndex((item) => item === dom) + 1;
205
+ extra = `[@fieldid='${fieldid}'][${index}]`;
206
+ }
207
+ } else if (!useFieldId && id) {
208
+ // 有id
209
+ const sameAttrChildren = children.filter(
210
+ ($el: any) => $el.tagName === dom.tagName && $el.id === id,
211
+ );
212
+ if (isDescendantOfSVG) {
213
+ if (sameAttrChildren.length === 1) {
214
+ return `/*[name()='${tagName}' and @id='${id}']`;
215
+ }
216
+ const index = sameAttrChildren.findIndex((item) => item === dom) + 1;
217
+ return `/*[name()='${tagName}' and @id='${id}'][${index}]`;
218
+ }
219
+ if (sameAttrChildren.length === 1) {
220
+ extra = `[@id='${id}']`;
221
+ } else {
222
+ const index = sameAttrChildren.findIndex((item) => item === dom) + 1;
223
+ extra = `[@id='${id}'][${index}]`;
224
+ }
225
+ } else {
226
+ // 其他
227
+ const sameAttrChildren = children.filter(
228
+ ($el: any) => $el.tagName === dom.tagName,
229
+ );
230
+ if (isDescendantOfSVG) {
231
+ if (sameAttrChildren.length === 1) {
232
+ return `/*[name()='${tagName}']`;
233
+ }
234
+ const index = sameAttrChildren.findIndex((item) => item === dom) + 1;
235
+ return `/*[name()='${tagName}'][${index}]`;
236
+ }
237
+ if (sameAttrChildren.length === 1) {
238
+ extra = '';
239
+ } else {
240
+ const index = sameAttrChildren.findIndex((item) => item === dom) + 1;
241
+ extra = `[${index}]`;
242
+ }
243
+ }
244
+
245
+ return '/' + dom.tagName.toLowerCase() + extra;
246
+ } catch (error) {
247
+ console.error(error);
248
+ return '';
249
+ }
250
+ };
251
+
252
+ /**
253
+ * 在document按照xpath查找元素
254
+ * 注意:shadowroot中不支持xpath
255
+ * @param {*} xpath
256
+ * @param {*} contextNode
257
+ * @returns
258
+ */
259
+ const getElementsByXPath = (xpath: string, contextNode: any = document) => {
260
+ try {
261
+ if (
262
+ contextNode.nodeName !== '#document' &&
263
+ contextNode.getRootNode().nodeName !== '#document'
264
+ ) {
265
+ return null;
266
+ }
267
+ const snapshot = document.evaluate(
268
+ xpath,
269
+ contextNode,
270
+ null,
271
+ XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
272
+ null,
273
+ );
274
+ const list = [];
275
+
276
+ for (let i = 0, len = snapshot.snapshotLength; i < len; i++) {
277
+ list.push(snapshot.snapshotItem(i));
278
+ }
279
+ return list;
280
+ } catch (error) {
281
+ console.error(error);
282
+ return [];
283
+ }
284
+ };
285
+
286
+ /**
287
+ * 获取元素
288
+ * @param {*} locator
289
+ * @param {*} docOrHost
290
+ * @returns
291
+ */
292
+ const getElementsByLocator = (
293
+ locator: { type: string; value: string },
294
+ docOrHost: any = document,
295
+ ) => {
296
+ try {
297
+ const { type, value } = locator;
298
+ let eles = [];
299
+
300
+ const contextNode = docOrHost.shadowRoot || docOrHost;
301
+
302
+ if (type === 'id') {
303
+ eles = contextNode.querySelectorAll(`*[id='${value}']`);
304
+ } else if (type === 'className') {
305
+ eles = contextNode.querySelectorAll(`*[class='${value}']`);
306
+ } else if (type === 'name') {
307
+ eles = contextNode.querySelectorAll(`*[name='${value}']`);
308
+ } else if (type === 'querySelect') {
309
+ eles = contextNode.querySelectorAll(value);
310
+ } else if (type === 'xpath') {
311
+ eles = getElementsByXPath(value, contextNode) || [];
312
+ }
313
+ return Array.from(eles);
314
+ } catch (error) {
315
+ console.error(error);
316
+ return [];
317
+ }
318
+ };
319
+
320
+ /**
321
+ * 获取基础选择器
322
+ * @param {*} dom
323
+ * @returns
324
+ */
325
+ const getBaseSelectors = (dom: any) => {
326
+ const locators = [];
327
+ const { id, name, classList } = dom;
328
+
329
+ if (id) {
330
+ locators.push({
331
+ type: 'id',
332
+ value: id,
333
+ });
334
+ }
335
+
336
+ if (name) {
337
+ locators.push({
338
+ type: 'name',
339
+ value: name,
340
+ });
341
+ }
342
+
343
+ // const classListValue = Array.from(classList).filter(c => c !== 'custom-hover-style'); // 云测 鼠标放上去会变
344
+ // if (classListValue.length > 0) {
345
+ // locators.push({
346
+ // type: 'className',
347
+ // value: classListValue.join(' '),
348
+ // });
349
+ // }
350
+
351
+ return locators;
352
+ };
353
+
354
+ /**
355
+ *
356
+ * 直到遇到fieldid或id为止
357
+ * @param {*} dom
358
+ * @param {*} onece
359
+ * @returns
360
+ */
361
+
362
+ const getLocatorUntilFieldid = (dom: any, type?: string) => {
363
+ const getXpathUntilFieldid: any = (dom: any) => {
364
+ try {
365
+ if (!dom || dom.nodeType !== 1) return '';
366
+ const useFieldId = !!document.querySelector('*[fieldid]');
367
+
368
+ const relativeXpath = getRelativeXpath(dom, useFieldId);
369
+ if (useFieldId && relativeXpath.includes('textCol|')) {
370
+ // 云测特殊需求:fieldid中包含textCol | 按不唯一处理,
371
+ return '';
372
+ }
373
+ if (/\[@/g.test(relativeXpath)) {
374
+ return '/' + relativeXpath;
375
+ }
376
+ return getXpathUntilFieldid(dom.parentNode) + relativeXpath;
377
+ } catch (error) {
378
+ console.error(error);
379
+ return '';
380
+ }
381
+ };
382
+ const getSelectorUntilFieldid: any = (dom: any) => {
383
+ try {
384
+ if (!dom || dom.nodeType !== 1) return '';
385
+ const useFieldId = !!document.querySelector('*[fieldid]');
386
+ const relativeSelector = getRelativeSelector(dom, useFieldId);
387
+ if (useFieldId && relativeSelector.includes('textCol|')) {
388
+ // 云测特殊需求:fieldid中包含textCol | 按不唯一处理,
389
+ return '';
390
+ }
391
+ if (
392
+ relativeSelector.includes('id=') ||
393
+ relativeSelector.includes('fieldid=')
394
+ ) {
395
+ return relativeSelector;
396
+ }
397
+ const parentSelector = getSelectorUntilFieldid(dom.parentNode);
398
+ return parentSelector
399
+ ? parentSelector + ' > ' + relativeSelector
400
+ : relativeSelector;
401
+ } catch (error) {
402
+ console.error(error);
403
+ return '';
404
+ }
405
+ };
406
+
407
+ if (type === 'xpath') {
408
+ return {
409
+ type: 'xpath',
410
+ value: getXpathUntilFieldid(dom),
411
+ };
412
+ } else if (type === 'querySelect') {
413
+ return {
414
+ type: 'querySelect',
415
+ value: getSelectorUntilFieldid(dom),
416
+ };
417
+ }
418
+ const value = getXpathUntilFieldid(dom);
419
+ if (value) {
420
+ return {
421
+ type: 'xpath',
422
+ value,
423
+ };
424
+ }
425
+ return {
426
+ type: 'querySelect',
427
+ value: getSelectorUntilFieldid(dom),
428
+ };
429
+ };
430
+
431
+ /**
432
+ * 全路径,适用于静态元素,当元素动态改变,而节点位置不变,可能不准确,比如百度的搜索结果,位置可能发生改变,指向一个固定的项
433
+ * 实现浏览器自带的Copy fullXPath方法
434
+ * 一直递归到 /html
435
+ * 忽略id
436
+ * 其他同上XPath
437
+ * @param {*} dom
438
+ * @returns string
439
+ */
440
+ const getFullLocator = (dom: any, type?: string) => {
441
+ const getFullXpath: any = (dom: any) => {
442
+ try {
443
+ if (!dom || dom.nodeType !== 1) return '';
444
+ if (dom.tagName === 'OPTION') return '';
445
+
446
+ const root = dom.getRootNode();
447
+ if (root.nodeName === '#document-fragment') {
448
+ return '';
449
+ }
450
+
451
+ if (dom.tagName === 'BODY') return '/html/body';
452
+ if (dom.tagName === 'HTML') return '/html';
453
+ // 同tagName的兄弟元素
454
+ const sameTagChildren = Array.from(dom.parentNode.childNodes).filter(
455
+ ($el: any) => $el.nodeType === 1 && $el.tagName === dom.tagName,
456
+ );
457
+ let extra = '';
458
+ if (sameTagChildren.length === 1) {
459
+ extra = '';
460
+ } else {
461
+ const index = sameTagChildren.findIndex((item) => item === dom) + 1;
462
+ extra = `[${index}]`;
463
+ }
464
+ const lowerTagName = dom.tagName.toLowerCase();
465
+ const isDescendantOfSVG = dom.closest('svg') !== null;
466
+ const me =
467
+ (isDescendantOfSVG ? `*[name()='${lowerTagName}']` : lowerTagName) +
468
+ extra;
469
+ return getFullXpath(dom.parentNode) + '/' + me;
470
+ } catch (error) {
471
+ console.error(error);
472
+ return '';
473
+ }
474
+ };
475
+ const getFullSelector: any = (dom: any) => {
476
+ try {
477
+ if (!dom || dom.nodeType !== 1) return '';
478
+ if (dom.tagName === 'OPTION') return '';
479
+
480
+ if (dom.tagName === 'BODY') return 'body';
481
+ if (dom.tagName === 'HTML') return 'html';
482
+
483
+ const children = Array.from(dom.parentNode.childNodes).filter(
484
+ ($el: any) => $el.nodeType === 1,
485
+ );
486
+ // 同tagName的兄弟元素
487
+ const sameTagChildren = children.filter(
488
+ ($el: any) => $el.tagName === dom.tagName,
489
+ );
490
+
491
+ let extra = '';
492
+
493
+ if (sameTagChildren.length === 1) {
494
+ extra = '';
495
+ } else {
496
+ // 获取索引,从1开始
497
+ const index = children.findIndex((item) => item === dom) + 1;
498
+ extra = `:nth-child(${index})`;
499
+ }
500
+
501
+ const me = dom.tagName.toLowerCase() + extra;
502
+ const parentSelector = getFullSelector(dom.parentNode);
503
+ return parentSelector ? parentSelector + ' > ' + me : me;
504
+ } catch (error) {
505
+ console.error(error);
506
+ return '';
507
+ }
508
+ };
509
+ if (type === 'xpath') {
510
+ return {
511
+ type: 'xpath',
512
+ value: getFullXpath(dom),
513
+ };
514
+ } else if (type === 'querySelect') {
515
+ return {
516
+ type: 'querySelect',
517
+ value: getFullSelector(dom),
518
+ };
519
+ }
520
+ const value = getFullXpath(dom);
521
+ if (value) {
522
+ return {
523
+ type: 'xpath',
524
+ value,
525
+ };
526
+ }
527
+ return {
528
+ type: 'querySelect',
529
+ value: getFullSelector(dom),
530
+ };
531
+ };
532
+
533
+ /**
534
+ *
535
+ * 向上遍历直到唯一,可能不含fieldid
536
+ * @param {*} dom
537
+ * @returns
538
+ */
539
+ const getPositionLocator = (ele: any) => {
540
+ const getPositionXPath = (ele: any) => {
541
+ try {
542
+ if (!ele || ele.nodeType !== 1) return '';
543
+ if (ele.tagName === 'OPTION') {
544
+ return '';
545
+ }
546
+ const root = ele.getRootNode();
547
+ if (isShadowRoot(root)) {
548
+ return '';
549
+ }
550
+ const useFieldId = !!document.querySelector('*[fieldid]');
551
+
552
+ let path = '';
553
+ let dom = ele;
554
+
555
+ while (dom && dom.nodeType === 1) {
556
+ let currentPath = '';
557
+ if (dom.tagName === 'BODY') {
558
+ currentPath = '/body';
559
+ } else if (dom.tagName === 'HTML') {
560
+ currentPath = '/html';
561
+ } else {
562
+ currentPath = getRelativeXpath(dom, useFieldId);
563
+ }
564
+
565
+ path = currentPath + path;
566
+ const locator = '/' + path;
567
+
568
+ if (useFieldId && locator.includes('textCol|')) {
569
+ // 云测特殊需求:fieldid中包含textCol | 按不唯一处理,
570
+ return '';
571
+ }
572
+
573
+ if (verifyXPath(locator, ele)) {
574
+ return locator;
575
+ }
576
+
577
+ dom = dom.parentNode;
578
+ }
579
+ return path;
580
+ } catch (error) {
581
+ console.error(error);
582
+ return '';
583
+ }
584
+ };
585
+ const getPositionSelector = (ele: any) => {
586
+ try {
587
+ let path = '';
588
+ let dom = ele;
589
+ if (dom && dom.tagName === 'OPTION') {
590
+ return '';
591
+ }
592
+ const useFieldId = !!document.querySelector('*[fieldid]');
593
+
594
+ while (dom && dom.nodeType === 1) {
595
+ let currentPath;
596
+ if (dom.tagName === 'BODY') {
597
+ currentPath = 'body';
598
+ } else if (dom.tagName === 'HTML') {
599
+ currentPath = 'html';
600
+ } else {
601
+ currentPath = getRelativeSelector(dom, useFieldId);
602
+ }
603
+ path = path ? currentPath + ' > ' + path : currentPath;
604
+ if (useFieldId && path.includes('textCol|')) {
605
+ // 云测特殊需求:fieldid中包含textCol | 按不唯一处理,
606
+ return '';
607
+ }
608
+ if (verifySelector(path, ele)) {
609
+ return path;
610
+ }
611
+ dom = dom.parentNode;
612
+ }
613
+ return path;
614
+ } catch (error) {
615
+ console.error(error);
616
+ return '';
617
+ }
618
+ };
619
+ const value = getPositionXPath(ele);
620
+ if (value) {
621
+ return {
622
+ type: 'xpath',
623
+ value,
624
+ };
625
+ }
626
+ return {
627
+ type: 'querySelect',
628
+ value: getPositionSelector(ele),
629
+ };
630
+ };
631
+
632
+ /**
633
+ * xpath中使用的text()
634
+ * 某个匹配就可以了
635
+ * @param {*} ele
636
+ * @returns string
637
+ */
638
+ const getFirstText = (ele: any) => {
639
+ const texts: any[] = Array.from(ele.childNodes)
640
+ .filter((n: any) => n.nodeType === 3)
641
+ .filter(
642
+ (node: any) =>
643
+ node.textContent &&
644
+ node.textContent.trim() &&
645
+ !/[\{\}\"\'\\\/]/.test(node.textContent) &&
646
+ node.textContent.trim().length < 30,
647
+ );
648
+
649
+ return texts[0]?.textContent || null;
650
+ };
651
+
652
+ /**
653
+ * 多语不适用
654
+ * 带text()的XPath
655
+ * 如果找到唯一元素就采用,否则舍弃
656
+ */
657
+ const getXpathWidthText = (dom: any) => {
658
+ if (!dom || dom.nodeType !== 1) return '';
659
+ const root = dom.getRootNode();
660
+ if (isShadowRoot(root)) {
661
+ return '';
662
+ }
663
+ let locator = '';
664
+ const text = getFirstText(dom);
665
+ if (text) {
666
+ if (text.length < 30) {
667
+ locator = `//${dom.nodeName.toLowerCase()}[text()=${attributeValue(text)}]`;
668
+ } else {
669
+ locator = `//${dom.nodeName.toLowerCase()}[contains(text(),${attributeValue(text.slice(0, 30))})]`;
670
+ }
671
+ }
672
+ return verifyXPath(locator, dom);
673
+ };
674
+
675
+ /**
676
+ * 多语不适用
677
+ * 带innerText的XPath
678
+ * innerText不能太长
679
+ * todo: 如果与父元素的tagName相同,父元素也会找到
680
+ * text-transform: lowercase; 样式会影响innerText的值,但不影响textContent的值
681
+ */
682
+ const getXpathWidthInnerText = (dom: any) => {
683
+ try {
684
+ if (!dom || dom.nodeType !== 1) return null;
685
+ const root = dom.getRootNode();
686
+ if (isShadowRoot(root)) {
687
+ return null;
688
+ }
689
+
690
+ // text-transform: lowercase; 会转换innerText的值,所以要使用textContent
691
+ const text = dom.textContent.replace(/\s+/g, ' '); // 合并连续空格
692
+ if (text && text.length < 30) {
693
+ // 处理文本中的特殊字符
694
+ const escapedText = text.replace(/"/g, '\\"').replace(/'/g, "\\'");
695
+ const tagName = dom.tagName.toLowerCase();
696
+ let parentNode = dom.parentNode;
697
+ let extra = `/${tagName}`;
698
+ while (parentNode) {
699
+ const parentNodeName = parentNode.nodeName.toLowerCase();
700
+ if (parentNodeName !== tagName) {
701
+ break;
702
+ }
703
+ extra = `/${parentNodeName}${extra}`;
704
+ parentNode = parentNode.parentNode;
705
+ }
706
+ const locator = `/${extra}[contains(.,'${escapedText}')]`;
707
+ return verifyXPath(locator, dom); // 合并连续空格 后不一定能找到元素,需要校验一下
708
+ }
709
+ return null;
710
+ } catch (error) {
711
+ console.error(error);
712
+ return null;
713
+ }
714
+ };
715
+
716
+ /**
717
+ * 找到第一个fieldid,然后一直往上找,直到找到所有的fieldid
718
+ * @param {*} dom
719
+ * @returns
720
+ */
721
+ const getLocatorUntilAllFieldid = (ele: any) => {
722
+ const getXpathUntilAllFieldid = (ele: any) => {
723
+ try {
724
+ if (!ele || ele.nodeType !== 1) return '';
725
+ const root = ele.getRootNode();
726
+ if (isShadowRoot(root)) {
727
+ return '';
728
+ }
729
+ const useFieldId = !!document.querySelector('*[fieldid]');
730
+
731
+ let path = '';
732
+ let dom = ele;
733
+
734
+ while (dom && dom.nodeType === 1) {
735
+ if (
736
+ useFieldId &&
737
+ path.includes('textCol|') &&
738
+ !dom.getAttribute('childrenfield')
739
+ ) {
740
+ // 云测特殊需求:fieldid中包含textCol | 按不唯一处理,
741
+ // 需要加上祖先中存在childrenfield属性的fieldid
742
+ dom = dom.parentNode;
743
+ continue;
744
+ }
745
+
746
+ const currentPath = getRelativeXpath(dom, useFieldId);
747
+ if (currentPath) {
748
+ if (!/\[@/g.test(path)) {
749
+ path = currentPath + path;
750
+ } else if (
751
+ /\[@/g.test(path) &&
752
+ /\[@/g.test(currentPath) &&
753
+ !path.startsWith('//')
754
+ ) {
755
+ path = '/' + currentPath + '/' + path;
756
+ } else if (/\[@/g.test(path) && /\[@/g.test(currentPath)) {
757
+ path = '/' + currentPath + path;
758
+ }
759
+ }
760
+ const count = (path.match(/\[@/g) || []).length;
761
+ if (count >= 2) {
762
+ // 2层后停止
763
+ break;
764
+ }
765
+ dom = dom.parentNode;
766
+ }
767
+ if (path && !path.startsWith('//')) {
768
+ path = '/' + path;
769
+ }
770
+ return path;
771
+ } catch (error) {
772
+ console.error(error);
773
+ return '';
774
+ }
775
+ };
776
+
777
+ const getSelectorUntilAllFieldid = (ele: any) => {
778
+ try {
779
+ if (!ele || ele.nodeType !== 1) return '';
780
+ const root = ele.getRootNode();
781
+ if (isShadowRoot(root)) {
782
+ return '';
783
+ }
784
+ const useFieldId = !!document.querySelector('*[fieldid]');
785
+
786
+ let path = '';
787
+ let dom = ele;
788
+
789
+ while (dom && dom.nodeType === 1) {
790
+ if (
791
+ useFieldId &&
792
+ path.includes('textCol|') &&
793
+ !dom.getAttribute('childrenfield')
794
+ ) {
795
+ // 云测特殊需求:fieldid中包含textCol | 按不唯一处理,
796
+ // 需要加上祖先中存在childrenfield属性的fieldid
797
+ dom = dom.parentNode;
798
+ continue;
799
+ }
800
+
801
+ const currentPath = getRelativeSelector(dom, useFieldId);
802
+ if (currentPath) {
803
+ if (!/\[fieldid|\[id/g.test(path)) {
804
+ path = path ? currentPath + ' > ' + path : currentPath;
805
+ } else if (
806
+ /\[fieldid|\[id/g.test(path) &&
807
+ /\[fieldid|\[id/g.test(currentPath)
808
+ ) {
809
+ path = currentPath + ' ' + path;
810
+ }
811
+ }
812
+
813
+ const count = (path.match(/\[fieldid|\[id/g) || []).length;
814
+ if (count >= 2) {
815
+ // 2层后停止
816
+ break;
817
+ }
818
+ dom = dom.parentNode;
819
+ }
820
+
821
+ return path;
822
+ } catch (error) {
823
+ console.error(error);
824
+ return '';
825
+ }
826
+ };
827
+
828
+ const value = getXpathUntilAllFieldid(ele);
829
+ if (value) {
830
+ return {
831
+ type: 'xpath',
832
+ value,
833
+ };
834
+ }
835
+ return {
836
+ type: 'querySelect',
837
+ value: getSelectorUntilAllFieldid(ele),
838
+ };
839
+ };
840
+
841
+ /**
842
+ * getXpathUntilAllFieldid的简化版,容器使用只有一层fieldid足够
843
+ * 使用所有fieldid生成xpath
844
+ * @param {*} dom
845
+ * @returns //div[@fieldid='3']//iframe
846
+ */
847
+ const getContainerLocatorUntilAllFieldid = (ele: any) => {
848
+ const getContainerXpathUntilAllFieldid = (ele: any) => {
849
+ try {
850
+ if (!ele || ele.nodeType !== 1) return '';
851
+ const root = ele.getRootNode();
852
+ if (isShadowRoot(root)) {
853
+ return '';
854
+ }
855
+ const useFieldId = !!document.querySelector('*[fieldid]');
856
+ let path = '';
857
+ let dom = ele;
858
+
859
+ while (dom && dom.nodeType === 1) {
860
+ const currentPath = getRelativeXpath(dom, useFieldId);
861
+
862
+ if (
863
+ dom.tagName === 'IFRAME' ||
864
+ dom.tagName === 'FRAME' ||
865
+ dom.shadowRoot ||
866
+ /\[@/g.test(currentPath)
867
+ ) {
868
+ path = '/' + currentPath + path;
869
+ }
870
+ const count = (path.match(/\[@/g) || []).length;
871
+ if (count >= 1) {
872
+ // Container1层后停止
873
+ break;
874
+ }
875
+ dom = dom.parentNode;
876
+ }
877
+ if (!path.startsWith('//')) {
878
+ path = '/' + path;
879
+ }
880
+ return path;
881
+ } catch (error) {
882
+ console.error(error);
883
+ return '';
884
+ }
885
+ };
886
+ const getContainerSelectorUntilAllFieldid = (ele: any) => {
887
+ try {
888
+ if (!ele || ele.nodeType !== 1) return '';
889
+ const root = ele.getRootNode();
890
+ if (isShadowRoot(root)) {
891
+ return '';
892
+ }
893
+ const useFieldId = !!document.querySelector('*[fieldid]');
894
+ let path = '';
895
+ let dom = ele;
896
+
897
+ while (dom && dom.nodeType === 1) {
898
+ const currentPath = getRelativeSelector(dom, useFieldId);
899
+ if (
900
+ dom.tagName === 'IFRAME' ||
901
+ dom.tagName === 'FRAME' ||
902
+ dom.shadowRoot ||
903
+ /\[fieldid|\[id/g.test(currentPath)
904
+ ) {
905
+ path = path ? currentPath + ' ' + path : currentPath;
906
+ }
907
+ const count = (path.match(/\[fieldid|\[id/g) || []).length;
908
+ if (count >= 1) {
909
+ // Container1层后停止
910
+ break;
911
+ }
912
+ dom = dom.parentNode;
913
+ }
914
+ return path;
915
+ } catch (error) {
916
+ console.error(error);
917
+ return '';
918
+ }
919
+ };
920
+ const value = getContainerXpathUntilAllFieldid(ele);
921
+ if (value) {
922
+ return {
923
+ type: 'xpath',
924
+ value,
925
+ };
926
+ }
927
+ return {
928
+ type: 'querySelect',
929
+ value: getContainerSelectorUntilAllFieldid(ele),
930
+ };
931
+ };
932
+
933
+ const notExit = (locators: any[], type: string, value?: string | null) => {
934
+ if (!value) {
935
+ return false;
936
+ }
937
+ return !locators.find((item) => item.type === type && item.value === value);
938
+ };
939
+
940
+ /**
941
+ * 对外的方法,依次调用上面的方法,生成可以的列表
942
+ * return []
943
+ * */
944
+ const getLocators = (dom: any) => {
945
+ const locators = getBaseSelectors(dom);
946
+
947
+ // 直到fieldid,有n<=1个fieldid
948
+ const locatorByGetLocatorUntilFieldid = getLocatorUntilFieldid(dom);
949
+ if (
950
+ notExit(
951
+ locators,
952
+ locatorByGetLocatorUntilFieldid.type,
953
+ locatorByGetLocatorUntilFieldid.value,
954
+ )
955
+ ) {
956
+ locators.push(locatorByGetLocatorUntilFieldid);
957
+ }
958
+
959
+ // 全部fieldid的结构,有n>=0个fieldid
960
+ const locatorByGetLocatorUntilAllFieldid = getLocatorUntilAllFieldid(dom);
961
+ if (
962
+ notExit(
963
+ locators,
964
+ locatorByGetLocatorUntilAllFieldid.type,
965
+ locatorByGetLocatorUntilAllFieldid.value,
966
+ )
967
+ ) {
968
+ locators.push(locatorByGetLocatorUntilAllFieldid);
969
+ }
970
+
971
+ // 向上递归直到唯一,可能不含fieldid
972
+ const locatorByGetPositionLocator = getPositionLocator(dom);
973
+ if (
974
+ notExit(
975
+ locators,
976
+ locatorByGetPositionLocator.type,
977
+ locatorByGetPositionLocator.value,
978
+ )
979
+ ) {
980
+ locators.push(locatorByGetPositionLocator);
981
+ }
982
+
983
+ // text()
984
+ const textXPathValue = getXpathWidthText(dom);
985
+ if (notExit(locators, 'xpath', textXPathValue)) {
986
+ locators.push({
987
+ type: 'xpath',
988
+ value: textXPathValue,
989
+ });
990
+ }
991
+
992
+ // innerText
993
+ const innerTextXpathValue = getXpathWidthInnerText(dom);
994
+ if (notExit(locators, 'xpath', innerTextXpathValue)) {
995
+ locators.push({
996
+ type: 'xpath',
997
+ value: innerTextXpathValue,
998
+ });
999
+ }
1000
+
1001
+ const sortedLocators = sortLocators(locators);
1002
+ const list = getLocatorsWidthIndex(sortedLocators, dom);
1003
+ // console.log('list', list);
1004
+ return list;
1005
+ };
1006
+
1007
+ // 排序
1008
+ const sortLocators = (locators: any[]) => {
1009
+ return locators.slice().sort((a, b) => {
1010
+ const valueA = a.value;
1011
+ const valueB = b.value;
1012
+
1013
+ // 检查是否包含 fieldid 或 id
1014
+ const hasFieldIdA = valueA.includes('@fieldid');
1015
+ const hasFieldIdB = valueB.includes('@fieldid');
1016
+ const hasIdA = valueA.includes('@id') && !hasFieldIdA; // 排除 fieldid 的干扰
1017
+ const hasIdB = valueB.includes('@id') && !hasFieldIdB;
1018
+
1019
+ // 分类优先级:fieldid > id > 其他
1020
+ if (hasFieldIdA !== hasFieldIdB) {
1021
+ return hasFieldIdA ? -1 : 1;
1022
+ }
1023
+ if (hasIdA !== hasIdB) {
1024
+ return hasIdA ? -1 : 1;
1025
+ }
1026
+ if (a.type !== b.type) {
1027
+ // 先xpath后querySelect,id等
1028
+ return a.type === 'xpath' ? -1 : 1;
1029
+ }
1030
+ // 同类排序规则
1031
+ if (hasFieldIdA) {
1032
+ const countA = (valueA.match(/\[@fieldid/g) || []).length;
1033
+ const countB = (valueB.match(/\[@fieldid/g) || []).length;
1034
+ return countA - countB;
1035
+ } else if (hasIdA) {
1036
+ const countA = (valueA.match(/\[@id/g) || []).length;
1037
+ const countB = (valueB.match(/\[@id/g) || []).length;
1038
+ return countA - countB;
1039
+ } else {
1040
+ return valueA.length - valueB.length;
1041
+ }
1042
+ });
1043
+ };
1044
+
1045
+ /**
1046
+ * 获取容器路径
1047
+ * @param {*} dom
1048
+ * @returns
1049
+ */
1050
+ const getContainerLocators = (dom: any) => {
1051
+ if (
1052
+ !dom ||
1053
+ (!isHostElement(dom) &&
1054
+ dom.nodeName !== 'IFRAME' &&
1055
+ dom.nodeName !== 'FRAME')
1056
+ ) {
1057
+ return [];
1058
+ }
1059
+
1060
+ const locators = getBaseSelectors(dom);
1061
+
1062
+ // 多fieldid的locator
1063
+ const locatorByGetContainerLocatorUntilAllFieldid =
1064
+ getContainerLocatorUntilAllFieldid(dom);
1065
+ if (
1066
+ notExit(
1067
+ locators,
1068
+ locatorByGetContainerLocatorUntilAllFieldid.type,
1069
+ locatorByGetContainerLocatorUntilAllFieldid.value,
1070
+ )
1071
+ ) {
1072
+ locators.push(locatorByGetContainerLocatorUntilAllFieldid);
1073
+ }
1074
+
1075
+ // Position locator
1076
+ const locatorByGetPositionLocator = getPositionLocator(dom);
1077
+ if (
1078
+ notExit(
1079
+ locators,
1080
+ locatorByGetPositionLocator.type,
1081
+ locatorByGetPositionLocator.value,
1082
+ )
1083
+ ) {
1084
+ locators.push(locatorByGetPositionLocator);
1085
+ }
1086
+
1087
+ const sortedLocators = sortLocators(locators);
1088
+ const containerAllPaths = getLocatorsWidthIndex(sortedLocators, dom);
1089
+ return containerAllPaths;
1090
+ };
1091
+
1092
+ /**
1093
+ * 对外的方法,iframe在页面数量少,不需要全路径,要简洁
1094
+ * @param {*} dom
1095
+ */
1096
+ const getContainerLocatorData = (dom: any) => {
1097
+ const containerAllPaths = getContainerLocators(dom);
1098
+ const containerType = dom.shadowRoot ? 'shadowRoot' : 'iframe'; // 类型
1099
+ return {
1100
+ containerAllPaths, // allPaths
1101
+ containerType, // 类型 iframe|shadowroot
1102
+ containerPathType: containerAllPaths[0]?.type || '', // 选中项路径类型 id|name|class|xpath|querySelect
1103
+ containerPathValue: containerAllPaths[0]?.value || '', //选中项路径值 string
1104
+ containerPathIndex: containerAllPaths[0]?.index ?? 1, // 应该有,当前缺失 选中项索引
1105
+ };
1106
+ };
1107
+
1108
+ /**
1109
+ * 给locator添加index
1110
+ * @param {*} locators
1111
+ * @param {*} dom
1112
+ * @returns
1113
+ */
1114
+ const getLocatorsWidthIndex = (locators: any[], dom: any) => {
1115
+ const rootNode = dom.getRootNode();
1116
+ const list: any[] = locators
1117
+ .map((item) => {
1118
+ const eles = getElementsByLocator(item, rootNode);
1119
+ const eleIndex = eles.findIndex((el) => el === dom);
1120
+ if (eleIndex === -1 || eles.length > 5) {
1121
+ return null;
1122
+ }
1123
+ return {
1124
+ ...item,
1125
+ index: eleIndex + 1,
1126
+ };
1127
+ })
1128
+ .filter(Boolean);
1129
+
1130
+ if (list.length === 0) {
1131
+ // 兜底,全路径
1132
+ const locatorByGetFullLocator = getFullLocator(dom);
1133
+ if (locatorByGetFullLocator.value) {
1134
+ list.push({
1135
+ type: locatorByGetFullLocator.type,
1136
+ value: locatorByGetFullLocator.value,
1137
+ index: 1,
1138
+ });
1139
+ }
1140
+ }
1141
+
1142
+ return list.map((item, index) => {
1143
+ return {
1144
+ ...item,
1145
+ isSelected: index === 0,
1146
+ orderNum: index,
1147
+ };
1148
+ });
1149
+ };
1150
+
1151
+ /**
1152
+ * 获取多个元素
1153
+ * @param {*} locators
1154
+ * @param {*} docOrHost
1155
+ * @param {*} selected
1156
+ * @returns
1157
+ */
1158
+ const getManyElementByLocators = (
1159
+ docOrHost = document,
1160
+ locators: any[],
1161
+ selected = true,
1162
+ ) => {
1163
+ if (selected) {
1164
+ const selectedLocator =
1165
+ locators.find((item) => item.isSelected) || locators[0];
1166
+ if (selectedLocator) {
1167
+ return getElementsByLocator(selectedLocator, docOrHost);
1168
+ }
1169
+ } else {
1170
+ const elements = locators.flatMap((item) => {
1171
+ return getElementsByLocator(item, docOrHost);
1172
+ });
1173
+ return Array.from(new Set(elements)); // 去重
1174
+ }
1175
+ return [];
1176
+ };
1177
+
1178
+ /**
1179
+ * 获取单个元素
1180
+ * @param {*} locators
1181
+ * @param {*} docOrHost
1182
+ * @returns
1183
+ */
1184
+ const getOneElementByLocators = (
1185
+ docOrHost: any,
1186
+ locators: any[],
1187
+ selected = true,
1188
+ ) => {
1189
+ if (selected) {
1190
+ const selectedLocator =
1191
+ locators.find((item) => item.isSelected) || locators[0];
1192
+ if (selectedLocator) {
1193
+ const index = Math.max(0, (selectedLocator.index ?? 1) - 1);
1194
+ return getElementsByLocator(selectedLocator, docOrHost)[index] || null;
1195
+ }
1196
+ } else {
1197
+ let ele = null;
1198
+ for (let i = 0; i < locators.length; i += 1) {
1199
+ // 对用户来说index从1开始,对程序内部来说index从0开始
1200
+ const index = Math.max(0, (locators[i].index ?? 1) - 1);
1201
+ ele = getElementsByLocator(locators[i], docOrHost)[index] || null;
1202
+ if (ele) {
1203
+ break;
1204
+ }
1205
+ }
1206
+ return ele;
1207
+ }
1208
+ return null;
1209
+ };
1210
+
1211
+ /**
1212
+ * 录制时先获取shadowRoot的容器树,再添加frame的容器树
1213
+ * 获取shadowRoot的容器树
1214
+ * @param {*} dom
1215
+ * @returns
1216
+ */
1217
+ const getShadowRootContainerStack = (dom: any) => {
1218
+ const containers = [];
1219
+
1220
+ let ele = dom;
1221
+
1222
+ while (ele) {
1223
+ const rootNode = ele.getRootNode();
1224
+ ele = rootNode.host;
1225
+ if (ele) {
1226
+ // 获取shadowRoot的容器节点的xpath
1227
+ const container = getContainerLocatorData(ele);
1228
+ containers.unshift(container);
1229
+ } else {
1230
+ break;
1231
+ }
1232
+ }
1233
+
1234
+ return containers;
1235
+ };
1236
+
1237
+ export {
1238
+ getLocators,
1239
+ getShadowRootContainerStack,
1240
+ getFullLocator,
1241
+ getLocatorUntilFieldid,
1242
+ getContainerLocatorData as getContainerPath,
1243
+ getManyElementByLocators as getElements,
1244
+ getOneElementByLocators as getUniqueElement,
1245
+ };