@rpascene/shared 0.30.8

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