@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.
- package/README.md +9 -0
- package/dist/es/baseDB.mjs +109 -0
- package/dist/es/build/copy-static.mjs +29 -0
- package/dist/es/common.mjs +37 -0
- package/dist/es/constants/example-code.mjs +202 -0
- package/dist/es/constants/index.mjs +74 -0
- package/dist/es/env/basic.mjs +6 -0
- package/dist/es/env/constants.mjs +97 -0
- package/dist/es/env/decide-model-config.mjs +172 -0
- package/dist/es/env/global-config-manager.mjs +82 -0
- package/dist/es/env/helper.mjs +45 -0
- package/dist/es/env/index.mjs +5 -0
- package/dist/es/env/init-debug.mjs +18 -0
- package/dist/es/env/model-config-manager.mjs +99 -0
- package/dist/es/env/parse.mjs +69 -0
- package/dist/es/env/types.mjs +265 -0
- package/dist/es/env/utils.mjs +18 -0
- package/dist/es/extractor/constants.mjs +2 -0
- package/dist/es/extractor/cs_postmessage.mjs +61 -0
- package/dist/es/extractor/customLocator.mjs +646 -0
- package/dist/es/extractor/debug.mjs +6 -0
- package/dist/es/extractor/dom-util.mjs +92 -0
- package/dist/es/extractor/index.mjs +7 -0
- package/dist/es/extractor/locator.mjs +95 -0
- package/dist/es/extractor/tree.mjs +81 -0
- package/dist/es/extractor/util.mjs +244 -0
- package/dist/es/extractor/web-extractor.mjs +361 -0
- package/dist/es/img/box-select.mjs +184 -0
- package/dist/es/img/draw-box.mjs +42 -0
- package/dist/es/img/get-jimp.mjs +10 -0
- package/dist/es/img/get-photon.mjs +19 -0
- package/dist/es/img/get-sharp.mjs +11 -0
- package/dist/es/img/index.mjs +5 -0
- package/dist/es/img/info.mjs +32 -0
- package/dist/es/img/transform.mjs +192 -0
- package/dist/es/index.mjs +3 -0
- package/dist/es/logger.mjs +61 -0
- package/dist/es/node/fs.mjs +44 -0
- package/dist/es/node/index.mjs +1 -0
- package/dist/es/polyfills/async-hooks.mjs +2 -0
- package/dist/es/polyfills/index.mjs +1 -0
- package/dist/es/types/index.mjs +3 -0
- package/dist/es/us-keyboard-layout.mjs +1414 -0
- package/dist/es/us-keyboard-layout.mjs.LICENSE.txt +5 -0
- package/dist/es/utils.mjs +66 -0
- package/dist/lib/baseDB.js +149 -0
- package/dist/lib/build/copy-static.js +77 -0
- package/dist/lib/common.js +93 -0
- package/dist/lib/constants/example-code.js +239 -0
- package/dist/lib/constants/index.js +153 -0
- package/dist/lib/env/basic.js +40 -0
- package/dist/lib/env/constants.js +143 -0
- package/dist/lib/env/decide-model-config.js +212 -0
- package/dist/lib/env/global-config-manager.js +116 -0
- package/dist/lib/env/helper.js +85 -0
- package/dist/lib/env/index.js +94 -0
- package/dist/lib/env/init-debug.js +52 -0
- package/dist/lib/env/model-config-manager.js +133 -0
- package/dist/lib/env/parse.js +106 -0
- package/dist/lib/env/types.js +650 -0
- package/dist/lib/env/utils.js +61 -0
- package/dist/lib/extractor/constants.js +42 -0
- package/dist/lib/extractor/cs_postmessage.js +98 -0
- package/dist/lib/extractor/customLocator.js +698 -0
- package/dist/lib/extractor/debug.js +12 -0
- package/dist/lib/extractor/dom-util.js +150 -0
- package/dist/lib/extractor/index.js +153 -0
- package/dist/lib/extractor/locator.js +141 -0
- package/dist/lib/extractor/tree.js +127 -0
- package/dist/lib/extractor/util.js +335 -0
- package/dist/lib/extractor/web-extractor.js +407 -0
- package/dist/lib/img/box-select.js +232 -0
- package/dist/lib/img/draw-box.js +89 -0
- package/dist/lib/img/get-jimp.js +72 -0
- package/dist/lib/img/get-photon.js +76 -0
- package/dist/lib/img/get-sharp.js +63 -0
- package/dist/lib/img/index.js +102 -0
- package/dist/lib/img/info.js +86 -0
- package/dist/lib/img/transform.js +279 -0
- package/dist/lib/index.js +43 -0
- package/dist/lib/logger.js +114 -0
- package/dist/lib/node/fs.js +97 -0
- package/dist/lib/node/index.js +60 -0
- package/dist/lib/polyfills/async-hooks.js +36 -0
- package/dist/lib/polyfills/index.js +60 -0
- package/dist/lib/types/index.js +37 -0
- package/dist/lib/us-keyboard-layout.js +1457 -0
- package/dist/lib/us-keyboard-layout.js.LICENSE.txt +5 -0
- package/dist/lib/utils.js +136 -0
- package/dist/types/baseDB.d.ts +25 -0
- package/dist/types/build/copy-static.d.ts +31 -0
- package/dist/types/common.d.ts +12 -0
- package/dist/types/constants/example-code.d.ts +2 -0
- package/dist/types/constants/index.d.ts +23 -0
- package/dist/types/env/basic.d.ts +6 -0
- package/dist/types/env/constants.d.ts +40 -0
- package/dist/types/env/decide-model-config.d.ts +14 -0
- package/dist/types/env/global-config-manager.d.ts +32 -0
- package/dist/types/env/helper.d.ts +6 -0
- package/dist/types/env/index.d.ts +4 -0
- package/dist/types/env/init-debug.d.ts +1 -0
- package/dist/types/env/model-config-manager.d.ts +24 -0
- package/dist/types/env/parse.d.ts +12 -0
- package/dist/types/env/types.d.ts +295 -0
- package/dist/types/env/utils.d.ts +7 -0
- package/dist/types/extractor/constants.d.ts +1 -0
- package/dist/types/extractor/cs_postmessage.d.ts +2 -0
- package/dist/types/extractor/customLocator.d.ts +69 -0
- package/dist/types/extractor/debug.d.ts +1 -0
- package/dist/types/extractor/dom-util.d.ts +26 -0
- package/dist/types/extractor/index.d.ts +36 -0
- package/dist/types/extractor/locator.d.ts +7 -0
- package/dist/types/extractor/tree.d.ts +9 -0
- package/dist/types/extractor/util.d.ts +43 -0
- package/dist/types/extractor/web-extractor.d.ts +19 -0
- package/dist/types/img/box-select.d.ts +25 -0
- package/dist/types/img/draw-box.d.ts +15 -0
- package/dist/types/img/get-jimp.d.ts +2 -0
- package/dist/types/img/get-photon.d.ts +8 -0
- package/dist/types/img/get-sharp.d.ts +3 -0
- package/dist/types/img/index.d.ts +4 -0
- package/dist/types/img/info.d.ts +29 -0
- package/dist/types/img/transform.d.ts +88 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/types/logger.d.ts +4 -0
- package/dist/types/node/fs.d.ts +15 -0
- package/dist/types/node/index.d.ts +1 -0
- package/dist/types/polyfills/async-hooks.d.ts +6 -0
- package/dist/types/polyfills/index.d.ts +4 -0
- package/dist/types/types/index.d.ts +37 -0
- package/dist/types/us-keyboard-layout.d.ts +32 -0
- package/dist/types/utils.d.ts +22 -0
- package/package.json +102 -0
- package/src/baseDB.ts +158 -0
- package/src/build/copy-static.ts +62 -0
- package/src/common.ts +67 -0
- package/src/constants/example-code.ts +202 -0
- package/src/constants/index.ts +81 -0
- package/src/env/basic.ts +12 -0
- package/src/env/constants.ts +291 -0
- package/src/env/decide-model-config.ts +319 -0
- package/src/env/global-config-manager.ts +174 -0
- package/src/env/helper.ts +80 -0
- package/src/env/index.ts +4 -0
- package/src/env/init-debug.ts +29 -0
- package/src/env/model-config-manager.ts +145 -0
- package/src/env/parse.ts +131 -0
- package/src/env/types.ts +573 -0
- package/src/env/utils.ts +39 -0
- package/src/extractor/constants.ts +5 -0
- package/src/extractor/cs_postmessage.ts +101 -0
- package/src/extractor/customLocator.ts +1138 -0
- package/src/extractor/debug.ts +10 -0
- package/src/extractor/dom-util.ts +141 -0
- package/src/extractor/index.ts +54 -0
- package/src/extractor/locator.ts +179 -0
- package/src/extractor/tree.ts +179 -0
- package/src/extractor/util.ts +468 -0
- package/src/extractor/web-extractor.ts +559 -0
- package/src/img/box-select.ts +346 -0
- package/src/img/draw-box.ts +60 -0
- package/src/img/get-jimp.ts +12 -0
- package/src/img/get-photon.ts +48 -0
- package/src/img/get-sharp.ts +18 -0
- package/src/img/index.ts +24 -0
- package/src/img/info.ts +79 -0
- package/src/img/jimp.d.ts +4 -0
- package/src/img/transform.ts +396 -0
- package/src/index.ts +6 -0
- package/src/logger.ts +93 -0
- package/src/node/fs.ts +84 -0
- package/src/node/index.ts +1 -0
- package/src/polyfills/async-hooks.ts +6 -0
- package/src/polyfills/index.ts +4 -0
- package/src/types/index.ts +53 -0
- package/src/us-keyboard-layout.ts +723 -0
- 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
|
+
};
|