ff-dom 1.0.17 → 1.0.18-beta.2
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 +93 -93
- package/dist/index.browser.cjs +2773 -0
- package/dist/index.browser.cjs.map +1 -0
- package/dist/index.browser.js +2768 -0
- package/dist/index.browser.js.map +1 -0
- package/dist/index.cdn.js +2775 -0
- package/dist/types/browser/browser/xpath.d.ts +110 -0
- package/dist/types/browser/types/locator.d.ts +26 -0
- package/dist/types/browser/utils/cssSelector.d.ts +18 -0
- package/dist/types/browser/utils/getElementsFromHTML.d.ts +17 -0
- package/dist/types/browser/utils/referenceXpath.d.ts +27 -0
- package/dist/types/browser/utils/xpath.d.ts +29 -0
- package/dist/types/browser/utils/xpathHelpers.d.ts +88 -0
- package/dist/types/node/node/xpath.d.ts +8 -0
- package/dist/types/node/types/locator.d.ts +26 -0
- package/dist/xpath.mjs +61 -0
- package/dist/xpath.mjs.map +1 -0
- package/package.json +58 -8
- package/src/browser/xpath.ts +15 -0
- package/src/index.d.ts +2 -0
- package/src/node/xpath.d.ts +9 -0
- package/src/node/xpath.ts +75 -0
- package/src/types/locator.ts +27 -0
- package/src/utils/cssSelector.ts +275 -0
- package/src/utils/getElementsFromHTML.ts +513 -593
- package/src/utils/referenceXpath.ts +936 -0
- package/src/utils/xpath.ts +1105 -0
- package/src/utils/xpathHelpers.ts +1299 -0
- package/tsconfig.browser.json +11 -0
- package/tsconfig.json +17 -13
- package/tsconfig.node.json +11 -0
- package/dist/index.js +0 -5
- package/dist/utils/getElementsFromHTML.js +0 -469
- package/src/index.ts +0 -3
|
@@ -0,0 +1,2775 @@
|
|
|
1
|
+
(function (exports) {
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const reWhiteSpace$1 = /^[\S]+( [\S]+)*$/gi;
|
|
5
|
+
const xpathCache$1 = {};
|
|
6
|
+
let multiElementReferenceMode = false;
|
|
7
|
+
let relativeXPathCache = new Map();
|
|
8
|
+
let modifiedElementAttributes$1 = [];
|
|
9
|
+
let mutationObserver;
|
|
10
|
+
const createObserver = (addedNodeCallBack) => {
|
|
11
|
+
mutationObserver = new MutationObserver((mutations) => {
|
|
12
|
+
mutations.forEach((mutation) => {
|
|
13
|
+
if (mutation?.addedNodes?.length) {
|
|
14
|
+
addedNodeCallBack(mutation?.addedNodes);
|
|
15
|
+
}
|
|
16
|
+
if (mutation.target instanceof HTMLElement) {
|
|
17
|
+
if (mutation?.type === "attributes" &&
|
|
18
|
+
mutation.attributeName === "class" &&
|
|
19
|
+
((isSvg(mutation.target) &&
|
|
20
|
+
mutation.oldValue?.trim() ===
|
|
21
|
+
mutation.target.classList.value?.trim()) ||
|
|
22
|
+
mutation.target.classList?.value
|
|
23
|
+
?.replace(mutation.oldValue, "")
|
|
24
|
+
.trim() === "marked-element-temp" ||
|
|
25
|
+
mutation?.oldValue
|
|
26
|
+
?.replace(mutation.target.classList?.value, "")
|
|
27
|
+
.trim() === "marked-element-temp")) ;
|
|
28
|
+
else if (mutation?.type === "attributes" &&
|
|
29
|
+
!["flndisabled"].includes(mutation.attributeName)) {
|
|
30
|
+
modifiedElementAttributes$1.push({
|
|
31
|
+
url: mutation.target.baseURI,
|
|
32
|
+
attributeName: mutation.attributeName,
|
|
33
|
+
element: mutation.target,
|
|
34
|
+
doc: mutation.target.ownerDocument,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
};
|
|
41
|
+
const startObserver = (target, options) => {
|
|
42
|
+
mutationObserver?.observe(target, options);
|
|
43
|
+
};
|
|
44
|
+
const stopObserver = () => {
|
|
45
|
+
mutationObserver?.disconnect();
|
|
46
|
+
};
|
|
47
|
+
const isNumberExist = (str) => {
|
|
48
|
+
const hasNumber = /\d/;
|
|
49
|
+
return hasNumber.test(str);
|
|
50
|
+
};
|
|
51
|
+
const buildPattern = (pattern, isSvg, tagName) => {
|
|
52
|
+
return isSvg
|
|
53
|
+
? `//*[local-name()='${tagName}' and ${pattern}]`
|
|
54
|
+
: `//${tagName}[${pattern}]`;
|
|
55
|
+
};
|
|
56
|
+
const getTextContent = (targetElement) => {
|
|
57
|
+
const textContent = targetElement?.textContent;
|
|
58
|
+
{
|
|
59
|
+
return textContent;
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
const getFilteredText = (element) => {
|
|
63
|
+
return element?.childNodes[0]?.nodeValue || "";
|
|
64
|
+
};
|
|
65
|
+
const getCountOfXPath = (xpath, element, docmt) => {
|
|
66
|
+
try {
|
|
67
|
+
let count;
|
|
68
|
+
// Check if result is cached
|
|
69
|
+
if (xpathCache$1[xpath] !== undefined) {
|
|
70
|
+
count = xpathCache$1[xpath];
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
const owner = docmt.nodeType === 9 // DOCUMENT_NODE
|
|
74
|
+
? docmt
|
|
75
|
+
: docmt.ownerDocument;
|
|
76
|
+
count = owner.evaluate(`count(${xpath})`, docmt, null, XPathResult.NUMBER_TYPE, null).numberValue;
|
|
77
|
+
xpathCache$1[xpath] = count;
|
|
78
|
+
}
|
|
79
|
+
if (multiElementReferenceMode && Array.isArray(element)) ;
|
|
80
|
+
else {
|
|
81
|
+
// Short-circuit if we only need to match one element
|
|
82
|
+
if (count === 1) {
|
|
83
|
+
const elementFromXpath = getElementFromXpath(xpath, docmt);
|
|
84
|
+
return elementFromXpath === element ? count : 0; // Return 0 if no match
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return count; // Return the count
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
console.error(`Error evaluating XPath: ${xpath}`, error);
|
|
91
|
+
return 0; // Return 0 on error to avoid re-processing
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
const escapeCharacters = (text) => {
|
|
95
|
+
if (text) {
|
|
96
|
+
if (!(text.indexOf('"') === -1)) {
|
|
97
|
+
return `'${text}'`;
|
|
98
|
+
}
|
|
99
|
+
if (!(text.indexOf("'") === -1)) {
|
|
100
|
+
return `"${text}"`;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return `'${text}'`;
|
|
104
|
+
};
|
|
105
|
+
const removeParenthesis = (xpath) => {
|
|
106
|
+
const charArr = xpath.split("");
|
|
107
|
+
let count = charArr.length;
|
|
108
|
+
const indexArray = [];
|
|
109
|
+
while (charArr[count - 2] !== "[") {
|
|
110
|
+
indexArray.push(charArr[count - 2]);
|
|
111
|
+
count--;
|
|
112
|
+
}
|
|
113
|
+
indexArray.reverse();
|
|
114
|
+
let finalStr = "";
|
|
115
|
+
for (let i = 0; i < indexArray.length; i++) {
|
|
116
|
+
finalStr += indexArray[i];
|
|
117
|
+
}
|
|
118
|
+
const endBracketLength = finalStr.length + 2;
|
|
119
|
+
let firstpart = xpath.slice(0, xpath.length - endBracketLength);
|
|
120
|
+
if (firstpart.startsWith("(") && firstpart.endsWith(")")) {
|
|
121
|
+
firstpart = firstpart.slice(1, -1);
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
firstpart = xpath;
|
|
125
|
+
}
|
|
126
|
+
return firstpart;
|
|
127
|
+
};
|
|
128
|
+
const findXpathWithIndex = (val, node, docmt, count) => {
|
|
129
|
+
try {
|
|
130
|
+
const owner = docmt.nodeType === 9 // DOCUMENT_NODE
|
|
131
|
+
? docmt
|
|
132
|
+
: docmt.ownerDocument;
|
|
133
|
+
let index = 0;
|
|
134
|
+
if (count) {
|
|
135
|
+
if (getCountOfXPath(`${val}[${count}]`, node, docmt) === 1) {
|
|
136
|
+
return `${val}[${count}]`;
|
|
137
|
+
}
|
|
138
|
+
if (getCountOfXPath(`(${val})[${count}]`, node, docmt) === 1) {
|
|
139
|
+
return `(${val})[${count}]`;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
const nodes = owner.evaluate(val, docmt, null, XPathResult.ANY_TYPE, null);
|
|
143
|
+
let nodex;
|
|
144
|
+
while ((nodex = nodes.iterateNext())) {
|
|
145
|
+
index++;
|
|
146
|
+
if (nodex.isSameNode(node)) {
|
|
147
|
+
if (getCountOfXPath(`${val}[${index}]`, node, docmt) === 1) {
|
|
148
|
+
return `${val}[${index}]`;
|
|
149
|
+
}
|
|
150
|
+
if (getCountOfXPath(`(${val})[${index}]`, node, docmt) === 1) {
|
|
151
|
+
return `(${val})[${index}]`;
|
|
152
|
+
}
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
catch (error) {
|
|
158
|
+
console.log(error);
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
const deleteLineGap = (a) => {
|
|
162
|
+
a &&= a.split("\n")[0].length > 0 ? a.split("\n")[0] : a.split("\n")[1];
|
|
163
|
+
return a;
|
|
164
|
+
};
|
|
165
|
+
const deleteGarbageFromInnerText = (a) => {
|
|
166
|
+
a = deleteLineGap(a);
|
|
167
|
+
a = a
|
|
168
|
+
.split(/[^\u0000-\u00ff]/)
|
|
169
|
+
.reduce((b, c) => {
|
|
170
|
+
return b.length > c.length ? b : c;
|
|
171
|
+
}, "")
|
|
172
|
+
.trim();
|
|
173
|
+
return (a = a.split("/")[0].trim());
|
|
174
|
+
};
|
|
175
|
+
const replaceWhiteSpaces = (str) => {
|
|
176
|
+
if (str) {
|
|
177
|
+
return str.replace(/\s\s+/g, " ").trim();
|
|
178
|
+
}
|
|
179
|
+
return str;
|
|
180
|
+
};
|
|
181
|
+
const getShadowRoot = (a) => {
|
|
182
|
+
for (a = a && a.parentElement; a;) {
|
|
183
|
+
if (a.toString() === "[object ShadowRoot]")
|
|
184
|
+
return a;
|
|
185
|
+
a = a.parentElement;
|
|
186
|
+
}
|
|
187
|
+
return null;
|
|
188
|
+
};
|
|
189
|
+
const checkBlockedAttributes = (attribute, targetElement, isTarget) => {
|
|
190
|
+
if (!attribute?.value ||
|
|
191
|
+
typeof attribute?.value === "boolean" ||
|
|
192
|
+
["true", "false", "on", "off", "flntooltip"].some((x) => x === attribute.value) ||
|
|
193
|
+
["type", "style", "locator-data-tooltip", "value"].some((x) => x === attribute.name) ||
|
|
194
|
+
modifiedElementAttributes$1?.find((x) => x.doc === targetElement.ownerDocument &&
|
|
195
|
+
x.element === targetElement &&
|
|
196
|
+
x.attributeName === attribute.name) ||
|
|
197
|
+
(attribute?.name?.indexOf("on") === 0 && attribute?.name?.length > 3) ||
|
|
198
|
+
typeof attribute.value === "function" ||
|
|
199
|
+
(isTarget && isNumberExist(attribute.value))) {
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
202
|
+
return true;
|
|
203
|
+
};
|
|
204
|
+
const getRelationship = (a, b) => {
|
|
205
|
+
let pos = a.compareDocumentPosition(b);
|
|
206
|
+
return pos === 2
|
|
207
|
+
? "preceding"
|
|
208
|
+
: pos === 4
|
|
209
|
+
? "following"
|
|
210
|
+
: pos === 8
|
|
211
|
+
? "ancestor"
|
|
212
|
+
: pos === 16
|
|
213
|
+
? "descendant"
|
|
214
|
+
: pos === 32
|
|
215
|
+
? "self"
|
|
216
|
+
: "";
|
|
217
|
+
};
|
|
218
|
+
const findRoot = (node) => {
|
|
219
|
+
while (node && node.parentNode) {
|
|
220
|
+
node = node.parentNode;
|
|
221
|
+
}
|
|
222
|
+
return node;
|
|
223
|
+
};
|
|
224
|
+
const isSvg = (element) => {
|
|
225
|
+
return element instanceof SVGElement;
|
|
226
|
+
};
|
|
227
|
+
const replaceTempAttributes = (str) => {
|
|
228
|
+
if (!str)
|
|
229
|
+
return str;
|
|
230
|
+
return str.replace(/\b[a-zA-Z_]*disabled\b/gi, "disabled");
|
|
231
|
+
};
|
|
232
|
+
const getElementFromXpath = (xpath, docmt, multi = false) => {
|
|
233
|
+
const owner = docmt.nodeType === 9 // DOCUMENT_NODE
|
|
234
|
+
? docmt
|
|
235
|
+
: docmt.ownerDocument;
|
|
236
|
+
if (multi) {
|
|
237
|
+
return owner.evaluate(xpath, docmt, null, XPathResult.ANY_TYPE, null);
|
|
238
|
+
}
|
|
239
|
+
else {
|
|
240
|
+
return owner.evaluate(xpath, docmt, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
const getPropertyXPath = (element, docmt, prop, value, isIndex, isTarget) => {
|
|
244
|
+
if (value) {
|
|
245
|
+
const { tagName } = element;
|
|
246
|
+
let count;
|
|
247
|
+
let combinePattern = "";
|
|
248
|
+
const mergePattern = [];
|
|
249
|
+
let pattern;
|
|
250
|
+
if (value && (!isTarget || !isNumberExist(value))) {
|
|
251
|
+
if (!reWhiteSpace$1.test(value)) {
|
|
252
|
+
pattern = buildPattern(`normalize-space(${prop})=${escapeCharacters(replaceWhiteSpaces(value)).trim()}`, isSvg(element), element.tagName.toLowerCase());
|
|
253
|
+
}
|
|
254
|
+
else {
|
|
255
|
+
pattern = `//${tagName}[${prop}=${escapeCharacters(value)}]`;
|
|
256
|
+
}
|
|
257
|
+
count = getCountOfXPath(pattern, element, docmt);
|
|
258
|
+
if (count === 1 && !isIndex) {
|
|
259
|
+
return pattern;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
if (value && isTarget) {
|
|
263
|
+
const splitText = value.split(" ");
|
|
264
|
+
if (splitText?.length) {
|
|
265
|
+
if (splitText.length === 1) {
|
|
266
|
+
const contentRes = [...new Set(splitText[0].match(/([^0-9]+)/g))];
|
|
267
|
+
if (contentRes?.length >= 1) {
|
|
268
|
+
if (contentRes[0] &&
|
|
269
|
+
replaceWhiteSpaces(contentRes[0].trim())?.length > 1) {
|
|
270
|
+
if (value.startsWith(contentRes[0])) {
|
|
271
|
+
if (!reWhiteSpace$1.test(contentRes[0])) {
|
|
272
|
+
combinePattern = `starts-with(${prop},${escapeCharacters(replaceWhiteSpaces(contentRes[0])).trim()})`;
|
|
273
|
+
}
|
|
274
|
+
else {
|
|
275
|
+
combinePattern = `starts-with(${prop},${escapeCharacters(contentRes[0]).trim()})`;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
if (contentRes?.length > 1) {
|
|
281
|
+
if (contentRes[contentRes.length - 1] &&
|
|
282
|
+
replaceWhiteSpaces(contentRes[contentRes.length - 1].trim())
|
|
283
|
+
?.length > 1) {
|
|
284
|
+
if (value.endsWith(contentRes[contentRes.length - 1])) {
|
|
285
|
+
if (!reWhiteSpace$1.test(contentRes[contentRes.length - 1])) {
|
|
286
|
+
combinePattern = `ends-with(${prop},${escapeCharacters(replaceWhiteSpaces(contentRes[contentRes.length - 1])).trim()})`;
|
|
287
|
+
}
|
|
288
|
+
else {
|
|
289
|
+
combinePattern = `ends-with(${prop},${escapeCharacters(contentRes[contentRes.length - 1]).trim()})`;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
if (combinePattern?.length) {
|
|
295
|
+
if (isSvg(element)) {
|
|
296
|
+
pattern = `//*[local-name()='${tagName}' and ${combinePattern}]`;
|
|
297
|
+
}
|
|
298
|
+
else {
|
|
299
|
+
pattern = `//${tagName}[${combinePattern}]`;
|
|
300
|
+
}
|
|
301
|
+
count = getCountOfXPath(pattern, element, docmt);
|
|
302
|
+
if (count === 1 && !isIndex) {
|
|
303
|
+
return pattern;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
else {
|
|
308
|
+
const endIndex = splitText.length % 2 === 0
|
|
309
|
+
? splitText.length / 2
|
|
310
|
+
: splitText.length % 2;
|
|
311
|
+
const startIndexString = splitText.slice(0, endIndex).join(" ");
|
|
312
|
+
let contentRes = [...new Set(startIndexString.match(/([^0-9]+)/g))];
|
|
313
|
+
if (contentRes?.length) {
|
|
314
|
+
if (contentRes[0] &&
|
|
315
|
+
replaceWhiteSpaces(contentRes[0].trim())?.length) {
|
|
316
|
+
if (value.startsWith(contentRes[0])) {
|
|
317
|
+
if (!reWhiteSpace$1.test(contentRes[0])) {
|
|
318
|
+
combinePattern = `starts-with(${prop},${escapeCharacters(replaceWhiteSpaces(contentRes[0])).trim()})`;
|
|
319
|
+
}
|
|
320
|
+
else {
|
|
321
|
+
combinePattern = `starts-with(${prop},${escapeCharacters(contentRes[0]).trim()})`;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
if (combinePattern?.length) {
|
|
327
|
+
if (isSvg(element)) {
|
|
328
|
+
pattern = `//*[local-name()='${tagName}' and ${combinePattern}]`;
|
|
329
|
+
}
|
|
330
|
+
else {
|
|
331
|
+
pattern = `//${tagName}[${combinePattern}]`;
|
|
332
|
+
}
|
|
333
|
+
count = getCountOfXPath(pattern, element, docmt);
|
|
334
|
+
if (count === 1 && !isIndex) {
|
|
335
|
+
return pattern;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
const endIndexString = splitText
|
|
339
|
+
.slice(endIndex, splitText.length - 1)
|
|
340
|
+
.join(" ");
|
|
341
|
+
contentRes = [...new Set(endIndexString.match(/([^0-9]+)/g))];
|
|
342
|
+
if (contentRes?.length) {
|
|
343
|
+
if (contentRes[0] &&
|
|
344
|
+
replaceWhiteSpaces(contentRes[0].trim())?.length > 3) {
|
|
345
|
+
if (value.endsWith(contentRes[0])) {
|
|
346
|
+
if (!reWhiteSpace$1.test(contentRes[0])) {
|
|
347
|
+
combinePattern = `ends-with(${prop},${escapeCharacters(replaceWhiteSpaces(contentRes[0])).trim()})`;
|
|
348
|
+
}
|
|
349
|
+
else {
|
|
350
|
+
combinePattern = `ends-with(${prop},${escapeCharacters(contentRes[0]).trim()})`;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
if (combinePattern?.length) {
|
|
356
|
+
if (isSvg(element)) {
|
|
357
|
+
pattern = `//*[local-name()='${tagName}' and ${combinePattern}]`;
|
|
358
|
+
}
|
|
359
|
+
else {
|
|
360
|
+
pattern = `//${tagName}[${combinePattern}]`;
|
|
361
|
+
}
|
|
362
|
+
count = getCountOfXPath(pattern, element, docmt);
|
|
363
|
+
if (count === 1 && !isIndex) {
|
|
364
|
+
return pattern;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
if (value && isTarget && isNumberExist(value)) {
|
|
371
|
+
const contentRes = [...new Set(value.match(/([^0-9]+)/g))];
|
|
372
|
+
if (contentRes?.length) {
|
|
373
|
+
for (let i = 0; i < contentRes?.length; i++) {
|
|
374
|
+
if (contentRes[i] &&
|
|
375
|
+
replaceWhiteSpaces(contentRes[i].trim())?.length > 1) {
|
|
376
|
+
if (!reWhiteSpace$1.test(contentRes[i])) {
|
|
377
|
+
mergePattern.push(`contains(${prop},${escapeCharacters(replaceWhiteSpaces(contentRes[i])).trim()})`);
|
|
378
|
+
}
|
|
379
|
+
else {
|
|
380
|
+
mergePattern.push(`contains(${prop},${escapeCharacters(contentRes[i].trim()).trim()})`);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
if (mergePattern?.length) {
|
|
386
|
+
if (isSvg(element)) {
|
|
387
|
+
pattern = `//*[local-name()='${tagName}' and ${mergePattern.join(" and ")}]`;
|
|
388
|
+
}
|
|
389
|
+
else {
|
|
390
|
+
pattern = `//${tagName}[${mergePattern.join(" and ")}]`;
|
|
391
|
+
}
|
|
392
|
+
count = getCountOfXPath(pattern, element, docmt);
|
|
393
|
+
if (count === 1 && !isIndex) {
|
|
394
|
+
return pattern;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
if (isSvg(element)) {
|
|
399
|
+
pattern = `//*[local-name()='${tagName}' and text()]`;
|
|
400
|
+
}
|
|
401
|
+
else {
|
|
402
|
+
pattern = `//${tagName}[text()]`;
|
|
403
|
+
}
|
|
404
|
+
count = getCountOfXPath(pattern, element, docmt);
|
|
405
|
+
if (count === 1 && !isIndex) {
|
|
406
|
+
return pattern;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
};
|
|
410
|
+
const getAbsoluteXPath = (domNode, docmt) => {
|
|
411
|
+
try {
|
|
412
|
+
if (!domNode) {
|
|
413
|
+
return "";
|
|
414
|
+
}
|
|
415
|
+
let xpathe = isSvg(domNode)
|
|
416
|
+
? `/*[local-name()='${domNode.tagName}']`
|
|
417
|
+
: `/${domNode.tagName}`;
|
|
418
|
+
// // If this node has siblings of the same tagName, get the index of this node
|
|
419
|
+
if (domNode.parentElement) {
|
|
420
|
+
// Get the siblings
|
|
421
|
+
const childNodes = Array.prototype.slice
|
|
422
|
+
.call(domNode.parentElement.children, 0)
|
|
423
|
+
.filter((childNode) => childNode.tagName === domNode.tagName);
|
|
424
|
+
// // If there's more than one sibling, append the index
|
|
425
|
+
if (childNodes.length > 1) {
|
|
426
|
+
const index = childNodes.indexOf(domNode);
|
|
427
|
+
xpathe += `[${index + 1}]`;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
else if (domNode instanceof HTMLElement) {
|
|
431
|
+
if (domNode.offsetParent) {
|
|
432
|
+
const childNodes = Array.prototype.slice
|
|
433
|
+
.call(domNode.offsetParent.children, 0)
|
|
434
|
+
.filter((childNode) => childNode.tagName === domNode.tagName);
|
|
435
|
+
// // If there's more than one sibling, append the index
|
|
436
|
+
if (childNodes.length > 1) {
|
|
437
|
+
const index = childNodes.indexOf(domNode);
|
|
438
|
+
xpathe += `[${index + 1}]`;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
// // Make a recursive call to this nodes parents and prepend it to this xpath
|
|
443
|
+
return getAbsoluteXPath(domNode?.parentElement, docmt) + xpathe;
|
|
444
|
+
}
|
|
445
|
+
catch (error) {
|
|
446
|
+
// If there's an unexpected exception, abort and don't get an XPath
|
|
447
|
+
console.log("xpath", error);
|
|
448
|
+
return "";
|
|
449
|
+
}
|
|
450
|
+
};
|
|
451
|
+
const getRelativeXPath = (domNode, docmt, isIndex, isTarget = false, attributesArray) => {
|
|
452
|
+
try {
|
|
453
|
+
// Generate a cache key based on the node's identifier, index, and target flag
|
|
454
|
+
// Check if the result for this node is already cached
|
|
455
|
+
if (relativeXPathCache.has(domNode)) {
|
|
456
|
+
return relativeXPathCache.get(domNode);
|
|
457
|
+
}
|
|
458
|
+
// Initialize an array to hold parts of the XPath
|
|
459
|
+
const xpathParts = [];
|
|
460
|
+
let currentNode = domNode;
|
|
461
|
+
// Traverse up the DOM tree iteratively instead of using recursion
|
|
462
|
+
while (currentNode) {
|
|
463
|
+
let xpathe = "";
|
|
464
|
+
let hasUniqueAttr = false;
|
|
465
|
+
let attributes = domNode === currentNode ? attributesArray ?? currentNode.attributes : currentNode.attributes;
|
|
466
|
+
// Loop through attributes to check for unique identifiers
|
|
467
|
+
for (const attrName of Array.from(attributes)) {
|
|
468
|
+
if (checkBlockedAttributes(attrName, currentNode, isTarget)) {
|
|
469
|
+
let attrValue = attrName.nodeValue;
|
|
470
|
+
// Clean up attribute value
|
|
471
|
+
attrValue = attrValue?.replace("removePointers", "") ?? "";
|
|
472
|
+
const elementName = attrName.name;
|
|
473
|
+
// Construct an XPath based on attribute
|
|
474
|
+
xpathe = getXpathString(currentNode, elementName, attrValue);
|
|
475
|
+
let othersWithAttr = 0;
|
|
476
|
+
if (xpathe) {
|
|
477
|
+
othersWithAttr = getCountOfXPath(xpathe, currentNode, docmt);
|
|
478
|
+
if (othersWithAttr === 1) {
|
|
479
|
+
xpathParts.unshift(replaceTempAttributes(xpathe));
|
|
480
|
+
hasUniqueAttr = true;
|
|
481
|
+
break;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
if (othersWithAttr > 1 && isIndex) {
|
|
485
|
+
xpathe = findXpathWithIndex(xpathe, currentNode, docmt, othersWithAttr);
|
|
486
|
+
if (xpathe) {
|
|
487
|
+
xpathParts.unshift(replaceTempAttributes(xpathe));
|
|
488
|
+
hasUniqueAttr = true;
|
|
489
|
+
break;
|
|
490
|
+
}
|
|
491
|
+
// return replaceTempAttributes(xpathe);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
if (currentNode.textContent) {
|
|
496
|
+
if (!isTarget || (isTarget && !isNumberExist(currentNode.textContent))) {
|
|
497
|
+
let reWhiteSpace = new RegExp(/^[\S]+( [\S]+)*$/gi);
|
|
498
|
+
if (!reWhiteSpace.test(currentNode.textContent)) {
|
|
499
|
+
xpathe =
|
|
500
|
+
isSvg(currentNode)
|
|
501
|
+
? `//*[local-name()='${currentNode.tagName}' and normalize-space(.)=${escapeCharacters(getFilteredText(currentNode))}]`
|
|
502
|
+
: `//${currentNode.tagName || "*"}[normalize-space(.)=${escapeCharacters(getFilteredText(currentNode))}]`;
|
|
503
|
+
}
|
|
504
|
+
else {
|
|
505
|
+
xpathe =
|
|
506
|
+
isSvg(currentNode)
|
|
507
|
+
? `//*[local-name()='${currentNode.tagName}' and .=${escapeCharacters(getFilteredText(currentNode))}]`
|
|
508
|
+
: `//${currentNode.tagName || "*"}[.=${escapeCharacters(getFilteredText(currentNode))}]`;
|
|
509
|
+
}
|
|
510
|
+
let othersWithAttr = getCountOfXPath(xpathe, currentNode, docmt);
|
|
511
|
+
if (othersWithAttr === 1) {
|
|
512
|
+
return xpathe;
|
|
513
|
+
}
|
|
514
|
+
if (othersWithAttr > 1 && isIndex) {
|
|
515
|
+
xpathe = findXpathWithIndex(xpathe, currentNode, docmt, othersWithAttr);
|
|
516
|
+
return xpathe;
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
else {
|
|
520
|
+
let combinePattern = [];
|
|
521
|
+
const contentRes = [
|
|
522
|
+
...new Set(getFilteredText(currentNode).match(/([^0-9]+)/g)),
|
|
523
|
+
];
|
|
524
|
+
let reWhiteSpace = new RegExp(/^[\S]+( [\S]+)*$/gi);
|
|
525
|
+
if (contentRes?.length) {
|
|
526
|
+
for (let i = 0; i < contentRes?.length; i++) {
|
|
527
|
+
if (contentRes[i] &&
|
|
528
|
+
replaceWhiteSpaces(contentRes[i].trim())) {
|
|
529
|
+
if (!reWhiteSpace.test(contentRes[i])) {
|
|
530
|
+
combinePattern.push(`contains(.,${escapeCharacters(replaceWhiteSpaces(contentRes[i])).trim()})`);
|
|
531
|
+
}
|
|
532
|
+
else {
|
|
533
|
+
combinePattern.push(`contains(.,${escapeCharacters(contentRes[i].trim()).trim()})`);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
if (combinePattern?.length) {
|
|
539
|
+
xpathe =
|
|
540
|
+
isSvg(currentNode)
|
|
541
|
+
? `//*[local-name()='${currentNode.tagName}' and ${combinePattern.join(" and ")}]`
|
|
542
|
+
: `//${currentNode.tagName || "*"}[${combinePattern.join(" and ")}]`;
|
|
543
|
+
let othersWithAttr = getCountOfXPath(xpathe, currentNode, docmt);
|
|
544
|
+
if (othersWithAttr === 1) {
|
|
545
|
+
return xpathe;
|
|
546
|
+
}
|
|
547
|
+
if (othersWithAttr > 1 && isIndex) {
|
|
548
|
+
xpathe = findXpathWithIndex(xpathe, currentNode, docmt, othersWithAttr);
|
|
549
|
+
return xpathe;
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
// If no unique attribute was found, construct XPath by tag name
|
|
555
|
+
if (!hasUniqueAttr) {
|
|
556
|
+
let tagBasedXPath = isSvg(currentNode)
|
|
557
|
+
? `/*[local-name()='${currentNode.tagName}']`
|
|
558
|
+
: `/${currentNode.tagName}`;
|
|
559
|
+
// Handle sibling nodes
|
|
560
|
+
if (currentNode.parentElement) {
|
|
561
|
+
const siblings = Array.from(currentNode.parentElement.children).filter((childNode) => childNode.tagName === currentNode.tagName);
|
|
562
|
+
// Append index to distinguish between siblings
|
|
563
|
+
if (siblings.length > 1) {
|
|
564
|
+
const index = siblings.indexOf(currentNode);
|
|
565
|
+
tagBasedXPath += `[${index + 1}]`;
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
// Add the constructed tag-based XPath to the parts array
|
|
569
|
+
xpathParts.unshift(tagBasedXPath);
|
|
570
|
+
}
|
|
571
|
+
else {
|
|
572
|
+
break;
|
|
573
|
+
}
|
|
574
|
+
// Move up to the parent node for the next iteration
|
|
575
|
+
currentNode = currentNode.parentElement;
|
|
576
|
+
}
|
|
577
|
+
// Combine all parts into the final XPath
|
|
578
|
+
const finalXPath = `${xpathParts.join("")}`;
|
|
579
|
+
// Cache the final XPath for this node
|
|
580
|
+
relativeXPathCache.set(domNode, finalXPath);
|
|
581
|
+
return finalXPath;
|
|
582
|
+
}
|
|
583
|
+
catch (error) {
|
|
584
|
+
console.log(error);
|
|
585
|
+
return null;
|
|
586
|
+
}
|
|
587
|
+
};
|
|
588
|
+
const getCombinationXpath = (attribute, domNode) => {
|
|
589
|
+
const combinePattern = [];
|
|
590
|
+
let pattern = "";
|
|
591
|
+
if (attribute &&
|
|
592
|
+
!isNumberExist(attribute.value) &&
|
|
593
|
+
typeof attribute.nodeValue !== "function" // &&
|
|
594
|
+
// !modifiedElementAttributes?.find(
|
|
595
|
+
// (x) => x.element === domNode && x.attributeName === attribute.name
|
|
596
|
+
// )
|
|
597
|
+
) {
|
|
598
|
+
const contentRes = [...new Set(attribute.value.match(/([^0-9]+)/g))];
|
|
599
|
+
if (contentRes?.length) {
|
|
600
|
+
for (let i = 0; i < contentRes?.length; i++) {
|
|
601
|
+
if (contentRes[i] &&
|
|
602
|
+
replaceWhiteSpaces(contentRes[i].trim())?.length > 2) {
|
|
603
|
+
if (!reWhiteSpace$1.test(contentRes[i])) {
|
|
604
|
+
combinePattern.push(`contains(@${attribute.name},${escapeCharacters(replaceWhiteSpaces(contentRes[i])).trim()})`);
|
|
605
|
+
}
|
|
606
|
+
else {
|
|
607
|
+
combinePattern.push(`contains(@${attribute.name},${escapeCharacters(contentRes[i].trim())})`);
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
if (combinePattern?.length) {
|
|
613
|
+
pattern = isSvg(domNode)
|
|
614
|
+
? `//*[local-name()='${domNode.tagName}' and ${combinePattern.join(" and ")}]`
|
|
615
|
+
: `//${domNode.tagName}[${combinePattern.join(" and ")}]`;
|
|
616
|
+
return pattern;
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
};
|
|
620
|
+
const getAttributeCombinationXpath = (domNode, docmt, uniqueAttributes, isTarget) => {
|
|
621
|
+
try {
|
|
622
|
+
const xpathAttributes = [];
|
|
623
|
+
if (uniqueAttributes.length > 1) {
|
|
624
|
+
for (const attrName of uniqueAttributes) {
|
|
625
|
+
if (checkBlockedAttributes(attrName, domNode, isTarget)) {
|
|
626
|
+
const attrValue = attrName.nodeValue;
|
|
627
|
+
if (!reWhiteSpace$1.test(attrValue)) {
|
|
628
|
+
xpathAttributes.push(`normalize-space(@${attrName.nodeName})="${attrValue}"`);
|
|
629
|
+
}
|
|
630
|
+
else if (attrName.nodeName === "class") {
|
|
631
|
+
xpathAttributes.push(`contains(@${attrName.nodeName},"${attrValue}")`);
|
|
632
|
+
}
|
|
633
|
+
else {
|
|
634
|
+
xpathAttributes.push(`@${attrName.nodeName}="${attrValue}"`);
|
|
635
|
+
}
|
|
636
|
+
const xpathe = isSvg(domNode)
|
|
637
|
+
? `//*[local-name()='${domNode.tagName}' and ${xpathAttributes.join(" and ")}]`
|
|
638
|
+
: `//${domNode.tagName}[${xpathAttributes.join(" and ")}]`;
|
|
639
|
+
let othersWithAttr;
|
|
640
|
+
// If the XPath does not parse, move to the next unique attribute
|
|
641
|
+
try {
|
|
642
|
+
othersWithAttr = getCountOfXPath(xpathe, domNode, docmt);
|
|
643
|
+
}
|
|
644
|
+
catch (ign) {
|
|
645
|
+
continue;
|
|
646
|
+
}
|
|
647
|
+
if (othersWithAttr === 1 && !xpathe.includes("and")) {
|
|
648
|
+
return "";
|
|
649
|
+
}
|
|
650
|
+
// If the attribute isn't actually unique, get it's index too
|
|
651
|
+
if (othersWithAttr === 1 && xpathe.includes("and")) {
|
|
652
|
+
return xpathe;
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
catch (error) {
|
|
659
|
+
// If there's an unexpected exception, abort and don't get an XPath
|
|
660
|
+
console.log(`'${JSON.stringify(error, null, 2)}'`);
|
|
661
|
+
}
|
|
662
|
+
};
|
|
663
|
+
const intermediateXpathStep = (targetElemt, attr, isTarget) => {
|
|
664
|
+
let isSvgElement = isSvg(targetElemt);
|
|
665
|
+
let expression = "";
|
|
666
|
+
if (checkBlockedAttributes(attr, targetElemt, isTarget)) {
|
|
667
|
+
let attrValue = attr.value;
|
|
668
|
+
attrValue = attrValue.replace("removePointers", "");
|
|
669
|
+
const elementName = attr.name;
|
|
670
|
+
if (!reWhiteSpace$1.test(attrValue)) {
|
|
671
|
+
expression = isSvgElement
|
|
672
|
+
? `*[local-name()='${targetElemt.tagName}' and normalize-space(@${elementName})=${escapeCharacters(attrValue)}]`
|
|
673
|
+
: `${targetElemt.tagName || "*"}[normalize-space(@${elementName})=${escapeCharacters(attrValue)}]`;
|
|
674
|
+
}
|
|
675
|
+
else {
|
|
676
|
+
expression = isSvgElement
|
|
677
|
+
? `*[local-name()='${targetElemt.tagName}' and @${elementName}=${escapeCharacters(attrValue)}]`
|
|
678
|
+
: `${targetElemt.tagName || "*"}[@${elementName}=${escapeCharacters(attrValue)}]`;
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
return expression;
|
|
682
|
+
};
|
|
683
|
+
const getFilteredTextXPath = (node, docmt) => {
|
|
684
|
+
if (!node.textContent)
|
|
685
|
+
return "";
|
|
686
|
+
const filteredText = getFilteredText(node);
|
|
687
|
+
let xpathe;
|
|
688
|
+
if (!reWhiteSpace$1.test(filteredText)) {
|
|
689
|
+
xpathe = isSvg(node)
|
|
690
|
+
? `//*[local-name()='${node.tagName}' and normalize-space(.)=${escapeCharacters(filteredText)}]`
|
|
691
|
+
: `//${node.tagName || "*"}[normalize-space(.)=${escapeCharacters(filteredText)}]`;
|
|
692
|
+
}
|
|
693
|
+
else {
|
|
694
|
+
xpathe = isSvg(node)
|
|
695
|
+
? `//*[local-name()='${node.tagName}' and .=${escapeCharacters(filteredText)}]`
|
|
696
|
+
: `//${node.tagName || "*"}[.=${escapeCharacters(filteredText)}]`;
|
|
697
|
+
}
|
|
698
|
+
return xpathe;
|
|
699
|
+
};
|
|
700
|
+
const getTextXpathFunction = (domNode) => {
|
|
701
|
+
const trimmedText = getTextContent(domNode)?.trim();
|
|
702
|
+
const filteredText = trimmedText
|
|
703
|
+
? escapeCharacters(deleteGarbageFromInnerText(trimmedText))
|
|
704
|
+
: trimmedText;
|
|
705
|
+
if (filteredText) {
|
|
706
|
+
if (filteredText !== `'${trimmedText}'`) {
|
|
707
|
+
return `contains(.,${filteredText})`;
|
|
708
|
+
}
|
|
709
|
+
return `normalize-space(.)='${trimmedText}'`;
|
|
710
|
+
}
|
|
711
|
+
};
|
|
712
|
+
const getXpathString = (node, attrName, attrValue) => {
|
|
713
|
+
const reWhiteSpace = new RegExp(/^[\S]+( [\S]+)*$/gi);
|
|
714
|
+
let xpathe = "";
|
|
715
|
+
if (attrValue) {
|
|
716
|
+
if (!reWhiteSpace.test(attrValue)) {
|
|
717
|
+
xpathe = isSvg(node)
|
|
718
|
+
? `//*[local-name()='${node.tagName}' and contains(@${attrName},${escapeCharacters(attrValue)})]`
|
|
719
|
+
: `//${node.tagName || "*"}[contains(@${attrName},${escapeCharacters(attrValue)})]`;
|
|
720
|
+
}
|
|
721
|
+
else if (attrName === "class") {
|
|
722
|
+
xpathe = isSvg(node)
|
|
723
|
+
? `//*[local-name()='${node.tagName}' and contains(@${attrName},${escapeCharacters(attrValue)})]`
|
|
724
|
+
: `//${node.tagName || "*"}[contains(@${attrName},${escapeCharacters(attrValue)})]`;
|
|
725
|
+
}
|
|
726
|
+
else {
|
|
727
|
+
xpathe = isSvg(node)
|
|
728
|
+
? `//*[local-name()='${node.tagName}' and @${attrName}=${escapeCharacters(attrValue)}]`
|
|
729
|
+
: `//${node.tagName || "*"}[@${attrName}=${escapeCharacters(attrValue)}]`;
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
return xpathe;
|
|
733
|
+
};
|
|
734
|
+
const replaceActualAttributes = (str, element) => {
|
|
735
|
+
if (str) {
|
|
736
|
+
return str.replace(/\bdisabled\b/gi, "flndisabled");
|
|
737
|
+
}
|
|
738
|
+
return str;
|
|
739
|
+
};
|
|
740
|
+
const addAttributeSplitCombineXpaths = (attributes, targetElemt, docmt, isTarget) => {
|
|
741
|
+
const attributesArray = Array.prototype.slice.call(attributes);
|
|
742
|
+
const xpaths = [];
|
|
743
|
+
try {
|
|
744
|
+
attributesArray.map((element) => {
|
|
745
|
+
if (checkBlockedAttributes(element, targetElemt, isTarget)) {
|
|
746
|
+
const xpth = getCombinationXpath(element, targetElemt);
|
|
747
|
+
if (xpth) {
|
|
748
|
+
xpaths.push({
|
|
749
|
+
key: `split xpath by ${element.name}`,
|
|
750
|
+
value: xpth,
|
|
751
|
+
});
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
});
|
|
755
|
+
}
|
|
756
|
+
catch (error) {
|
|
757
|
+
console.log(error);
|
|
758
|
+
}
|
|
759
|
+
return xpaths;
|
|
760
|
+
};
|
|
761
|
+
const getReferenceElementsXpath = (domNode, docmt, isTarget) => {
|
|
762
|
+
let nodeXpath1;
|
|
763
|
+
const xpaths1 = [];
|
|
764
|
+
if (domNode.textContent &&
|
|
765
|
+
(!isTarget || (isTarget && !isNumberExist(domNode.textContent)))) {
|
|
766
|
+
if (!reWhiteSpace$1.test(domNode.textContent)) {
|
|
767
|
+
nodeXpath1 = isSvg(domNode)
|
|
768
|
+
? `*[local-name()='${domNode.tagName}' and ${getTextXpathFunction(domNode)})]`
|
|
769
|
+
: `${domNode.tagName}[${getTextXpathFunction(domNode)}]`;
|
|
770
|
+
if (nodeXpath1) {
|
|
771
|
+
xpaths1.push({ key: "getReferenceElementsXpath", value: nodeXpath1 });
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
else {
|
|
775
|
+
nodeXpath1 = isSvg(domNode)
|
|
776
|
+
? `*[local-name()='${domNode.tagName}' and .=${escapeCharacters(getTextContent(domNode))}]`
|
|
777
|
+
: `${domNode.tagName}[.=${escapeCharacters(getTextContent(domNode))}]`;
|
|
778
|
+
if (nodeXpath1) {
|
|
779
|
+
xpaths1.push({ key: "getReferenceElementsXpath", value: nodeXpath1 });
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
if (domNode.attributes) {
|
|
784
|
+
for (const attrName of Array.from(domNode.attributes)) {
|
|
785
|
+
if (checkBlockedAttributes(attrName, domNode, isTarget)) {
|
|
786
|
+
let attrValue = attrName.nodeValue;
|
|
787
|
+
if (attrValue) {
|
|
788
|
+
attrValue = attrValue.replace("removePointers", "");
|
|
789
|
+
const elementName = attrName.name;
|
|
790
|
+
if (elementName === "class") {
|
|
791
|
+
nodeXpath1 = isSvg(domNode)
|
|
792
|
+
? `*[local-name()='${domNode.tagName}' and contains(@${elementName},${escapeCharacters(attrValue)})]`
|
|
793
|
+
: `${domNode.tagName}[contains(@${elementName},${escapeCharacters(attrValue)})]`;
|
|
794
|
+
}
|
|
795
|
+
else {
|
|
796
|
+
nodeXpath1 = isSvg(domNode)
|
|
797
|
+
? `*[local-name()='${domNode.tagName}' and @${elementName}=${escapeCharacters(attrValue)}]`
|
|
798
|
+
: `${domNode.tagName}[@${elementName}=${escapeCharacters(attrValue)}]`;
|
|
799
|
+
}
|
|
800
|
+
if (nodeXpath1) {
|
|
801
|
+
xpaths1.push({
|
|
802
|
+
key: "getReferenceElementsXpath",
|
|
803
|
+
value: nodeXpath1,
|
|
804
|
+
});
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
if (!xpaths1?.length) {
|
|
811
|
+
const attributesArray = Array.prototype.slice.call(domNode.attributes);
|
|
812
|
+
if (attributesArray?.length > 1) {
|
|
813
|
+
const combinationXpath = getAttributeCombinationXpath(domNode, docmt, Array.prototype.slice.call(domNode.attributes), isTarget);
|
|
814
|
+
if (combinationXpath) {
|
|
815
|
+
xpaths1.push({
|
|
816
|
+
key: "getReferenceElementsXpath",
|
|
817
|
+
value: combinationXpath,
|
|
818
|
+
});
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
if (!xpaths1?.length) {
|
|
823
|
+
const combinePattern = [];
|
|
824
|
+
let pattern;
|
|
825
|
+
const tag = isSvg(domNode);
|
|
826
|
+
if (domNode.textContent && isTarget && isNumberExist(domNode.textContent)) {
|
|
827
|
+
const contentRes = [...new Set(domNode.textContent.match(/([^0-9]+)/g))];
|
|
828
|
+
if (contentRes?.length) {
|
|
829
|
+
for (let i = 0; i < contentRes?.length; i++) {
|
|
830
|
+
if (contentRes[i] &&
|
|
831
|
+
replaceWhiteSpaces(contentRes[i].trim())?.length > 1) {
|
|
832
|
+
if (!reWhiteSpace$1.test(contentRes[i])) {
|
|
833
|
+
combinePattern.push(`contains(.,${escapeCharacters(replaceWhiteSpaces(contentRes[i])).trim()})`);
|
|
834
|
+
}
|
|
835
|
+
else {
|
|
836
|
+
combinePattern.push(`contains(.,${escapeCharacters(contentRes[i]).trim()})`);
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
if (combinePattern?.length) {
|
|
842
|
+
if (tag) {
|
|
843
|
+
pattern = `//*[local-name()='${tag}' and ${combinePattern.join(" and ")}]`;
|
|
844
|
+
}
|
|
845
|
+
else {
|
|
846
|
+
pattern = `//${tag}[${combinePattern.join(" and ")}]`;
|
|
847
|
+
}
|
|
848
|
+
if (pattern)
|
|
849
|
+
xpaths1.push({ key: "getReferenceElementsXpath", value: pattern });
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
if (!xpaths1?.length) {
|
|
854
|
+
const xpaths = addAttributeSplitCombineXpaths(domNode.attributes, domNode, docmt, isTarget);
|
|
855
|
+
if (xpaths?.length) {
|
|
856
|
+
xpaths1.concat(xpaths);
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
return xpaths1;
|
|
860
|
+
};
|
|
861
|
+
function parseXml(xmlStr) {
|
|
862
|
+
if (window.DOMParser) {
|
|
863
|
+
return new window.DOMParser().parseFromString(xmlStr, "text/xml");
|
|
864
|
+
}
|
|
865
|
+
return null;
|
|
866
|
+
}
|
|
867
|
+
const normalizeXPath = (xpath) => {
|
|
868
|
+
// Replace text() = "value" or text()='value'
|
|
869
|
+
xpath = xpath.replace(/text\(\)\s*=\s*(['"])(.*?)\1/g, "normalize-space(.)=$1$2$1");
|
|
870
|
+
// Replace . = "value" or .='value'
|
|
871
|
+
xpath = xpath.replace(/\.\s*=\s*(['"])(.*?)\1/g, "normalize-space(.)=$1$2$1");
|
|
872
|
+
return xpath;
|
|
873
|
+
};
|
|
874
|
+
const findMatchingParenthesis = (text, openPos) => {
|
|
875
|
+
let closePos = openPos;
|
|
876
|
+
let counter = 1;
|
|
877
|
+
while (counter > 0) {
|
|
878
|
+
const c = text[++closePos];
|
|
879
|
+
if (c == "(") {
|
|
880
|
+
counter++;
|
|
881
|
+
}
|
|
882
|
+
else if (c == ")") {
|
|
883
|
+
counter--;
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
return closePos;
|
|
887
|
+
};
|
|
888
|
+
const xpathUtils = {
|
|
889
|
+
parseXml,
|
|
890
|
+
getReferenceElementsXpath,
|
|
891
|
+
getAbsoluteXPath,
|
|
892
|
+
getRelativeXPath,
|
|
893
|
+
getCombinationXpath,
|
|
894
|
+
getAttributeCombinationXpath,
|
|
895
|
+
getElementFromXpath,
|
|
896
|
+
isSvg,
|
|
897
|
+
findXpathWithIndex,
|
|
898
|
+
isNumberExist,
|
|
899
|
+
getTextContent,
|
|
900
|
+
getCountOfXPath,
|
|
901
|
+
normalizeXPath,
|
|
902
|
+
getShadowRoot,
|
|
903
|
+
escapeCharacters,
|
|
904
|
+
removeParenthesis,
|
|
905
|
+
checkBlockedAttributes,
|
|
906
|
+
getRelationship,
|
|
907
|
+
findMatchingParenthesis,
|
|
908
|
+
deleteGarbageFromInnerText,
|
|
909
|
+
replaceTempAttributes,
|
|
910
|
+
createObserver,
|
|
911
|
+
startObserver,
|
|
912
|
+
stopObserver,
|
|
913
|
+
modifiedElementAttributes: modifiedElementAttributes$1,
|
|
914
|
+
};
|
|
915
|
+
|
|
916
|
+
let xpathData$1 = [];
|
|
917
|
+
let xpathDataWithIndex$1 = [];
|
|
918
|
+
let referenceElementMode = false;
|
|
919
|
+
let xpathCache = new Map();
|
|
920
|
+
let cache = new Map();
|
|
921
|
+
const parentXpathCache = new Map(); // Cache for parent XPaths
|
|
922
|
+
const checkRelativeXpathRelation = (nodeXpath1, nodeXpath2, targetElemt, docmt, isIndex, relationType) => {
|
|
923
|
+
if (nodeXpath1 && !referenceElementMode) {
|
|
924
|
+
let xpaths;
|
|
925
|
+
if (relationType === "parent") {
|
|
926
|
+
xpaths = [
|
|
927
|
+
// `${nodeXpath1}/descendant::${nodeXpath2}`,
|
|
928
|
+
`${nodeXpath1}/descendant-or-self::${nodeXpath2}`,
|
|
929
|
+
`${nodeXpath1}/following::${nodeXpath2}`,
|
|
930
|
+
];
|
|
931
|
+
}
|
|
932
|
+
else {
|
|
933
|
+
xpaths = [
|
|
934
|
+
// `${nodeXpath1}/descendant::${nodeXpath2}`,
|
|
935
|
+
`${nodeXpath1}/ancestor-or-self::${nodeXpath2}`,
|
|
936
|
+
`${nodeXpath1}/preceding::${nodeXpath2}`,
|
|
937
|
+
];
|
|
938
|
+
}
|
|
939
|
+
// Iterate through XPath patterns
|
|
940
|
+
for (const xpath of xpaths) {
|
|
941
|
+
// Check if result is already cached to avoid recomputation
|
|
942
|
+
if (!xpathCache?.get(xpath)) {
|
|
943
|
+
// Compute and store result in cache
|
|
944
|
+
xpathCache.set(xpath, getCountOfXPath(xpath, targetElemt, docmt));
|
|
945
|
+
}
|
|
946
|
+
const count = xpathCache?.get(xpath);
|
|
947
|
+
// Short-circuit: Return the first valid XPath result found
|
|
948
|
+
if (count === 1) {
|
|
949
|
+
return xpath;
|
|
950
|
+
}
|
|
951
|
+
if (count > 1) {
|
|
952
|
+
if (xpathDataWithIndex$1.length) {
|
|
953
|
+
if (count < xpathDataWithIndex$1[0].count) {
|
|
954
|
+
xpathDataWithIndex$1.pop();
|
|
955
|
+
xpathDataWithIndex$1.push({
|
|
956
|
+
key: `relative xpath by unique parent ${isIndex ? "index" : ""}`,
|
|
957
|
+
value: xpath,
|
|
958
|
+
count,
|
|
959
|
+
});
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
else {
|
|
963
|
+
xpathDataWithIndex$1.push({
|
|
964
|
+
key: `relative xpath by unique parent ${isIndex ? "index" : ""}`,
|
|
965
|
+
value: xpath,
|
|
966
|
+
count,
|
|
967
|
+
});
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
if (count > 1 && isIndex && !xpathData$1.length) {
|
|
971
|
+
// Try finding XPath with index if count is greater than 1
|
|
972
|
+
const indexedXpath = findXpathWithIndex(xpath, targetElemt, docmt, count);
|
|
973
|
+
// Cache the indexed XPath result
|
|
974
|
+
if (indexedXpath &&
|
|
975
|
+
getCountOfXPath(indexedXpath, targetElemt, docmt) === 1) {
|
|
976
|
+
xpathData$1.push({
|
|
977
|
+
key: `relative xpath by unique parent ${isIndex ? "index" : ""}`,
|
|
978
|
+
value: indexedXpath,
|
|
979
|
+
});
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
return null;
|
|
985
|
+
};
|
|
986
|
+
const getUniqueParentXpath = (domNode, docmt, node, isTarget, nodeXpath, isIndex) => {
|
|
987
|
+
try {
|
|
988
|
+
if (parentXpathCache.has(domNode)) {
|
|
989
|
+
return parentXpathCache.get(domNode);
|
|
990
|
+
}
|
|
991
|
+
// Direct XPath construction without loops
|
|
992
|
+
const xpathParts = [];
|
|
993
|
+
let currentNode = domNode;
|
|
994
|
+
while (currentNode && currentNode.nodeType === 1) {
|
|
995
|
+
const hasUniqueAttr = false;
|
|
996
|
+
for (const attrName of Array.from(currentNode.attributes)) {
|
|
997
|
+
if (checkBlockedAttributes(attrName, currentNode, isTarget)) {
|
|
998
|
+
let attrValue = attrName.nodeValue;
|
|
999
|
+
attrValue = attrValue?.replace("removePointers", "") ?? "";
|
|
1000
|
+
const elementName = attrName.name;
|
|
1001
|
+
const xpathe = getXpathString(currentNode, elementName, attrValue);
|
|
1002
|
+
let othersWithAttr;
|
|
1003
|
+
// If the XPath does not parse, move to the next unique attribute
|
|
1004
|
+
try {
|
|
1005
|
+
othersWithAttr = checkRelativeXpathRelation(xpathe, nodeXpath, node, docmt, isIndex, "parent");
|
|
1006
|
+
}
|
|
1007
|
+
catch (ign) {
|
|
1008
|
+
continue;
|
|
1009
|
+
}
|
|
1010
|
+
// If the attribute isn't actually unique, get it's index too
|
|
1011
|
+
if (othersWithAttr) {
|
|
1012
|
+
return othersWithAttr;
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
if (currentNode.textContent && !currentNode.textContent) {
|
|
1017
|
+
if (!isTarget ||
|
|
1018
|
+
(isTarget && !isNumberExist(currentNode.textContent))) {
|
|
1019
|
+
let xpathe;
|
|
1020
|
+
if (!reWhiteSpace$1.test(currentNode.textContent)) {
|
|
1021
|
+
xpathe = isSvg(currentNode)
|
|
1022
|
+
? `//*[local-name()='${currentNode.tagName}' and normalize-space(.)=${escapeCharacters(getFilteredText(currentNode))}]`
|
|
1023
|
+
: `//${currentNode.tagName || "*"}[normalize-space(.)=${escapeCharacters(getFilteredText(currentNode))}]`;
|
|
1024
|
+
}
|
|
1025
|
+
else {
|
|
1026
|
+
xpathe = isSvg(currentNode)
|
|
1027
|
+
? `//*[local-name()='${currentNode.tagName}' and .=${escapeCharacters(getFilteredText(currentNode))}]`
|
|
1028
|
+
: `//${currentNode.tagName || "*"}[.=${escapeCharacters(getFilteredText(currentNode))}]`;
|
|
1029
|
+
}
|
|
1030
|
+
const othersWithAttr = checkRelativeXpathRelation(xpathe, nodeXpath, node, docmt, isIndex, "parent");
|
|
1031
|
+
if (othersWithAttr) {
|
|
1032
|
+
return othersWithAttr;
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
else {
|
|
1036
|
+
const combinePattern = [];
|
|
1037
|
+
const contentRes = [
|
|
1038
|
+
...new Set(getFilteredText(currentNode).match(/([^0-9]+)/g)),
|
|
1039
|
+
];
|
|
1040
|
+
const reWhiteSpace = new RegExp(/^[\S]+( [\S]+)*$/gi);
|
|
1041
|
+
if (contentRes?.length) {
|
|
1042
|
+
for (let i = 0; i < contentRes?.length; i++) {
|
|
1043
|
+
if (contentRes[i] && replaceWhiteSpaces(contentRes[i].trim())) {
|
|
1044
|
+
if (!reWhiteSpace.test(contentRes[i])) {
|
|
1045
|
+
combinePattern.push(`contains(.,${escapeCharacters(replaceWhiteSpaces(contentRes[i])).trim()})`);
|
|
1046
|
+
}
|
|
1047
|
+
else {
|
|
1048
|
+
combinePattern.push(`contains(.,${escapeCharacters(contentRes[i].trim()).trim()})`);
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
if (combinePattern?.length) {
|
|
1054
|
+
const xpathe = isSvg(currentNode)
|
|
1055
|
+
? `//*[local-name()='${currentNode.tagName}' and ${combinePattern.join(" and ")}]`
|
|
1056
|
+
: `//${currentNode.tagName || "*"}[${combinePattern.join(" and ")}]`;
|
|
1057
|
+
const othersWithAttr = checkRelativeXpathRelation(xpathe, nodeXpath, node, docmt, isIndex, "parent");
|
|
1058
|
+
if (othersWithAttr) {
|
|
1059
|
+
return othersWithAttr;
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
// Construct the XPath based on the tag name
|
|
1065
|
+
if (!hasUniqueAttr) {
|
|
1066
|
+
const xpathe = isSvg(currentNode)
|
|
1067
|
+
? `/*[local-name()='${currentNode.tagName}']`
|
|
1068
|
+
: `/${currentNode.tagName}`;
|
|
1069
|
+
xpathParts.unshift(xpathe);
|
|
1070
|
+
}
|
|
1071
|
+
// Move to the parent node for the next iteration
|
|
1072
|
+
currentNode = currentNode.parentElement;
|
|
1073
|
+
}
|
|
1074
|
+
// Final constructed XPath
|
|
1075
|
+
const finalXPath = xpathParts.join("");
|
|
1076
|
+
let count = getCountOfXPath(finalXPath, domNode, docmt);
|
|
1077
|
+
if (count === 1) {
|
|
1078
|
+
parentXpathCache.set(domNode, finalXPath); // Cache final result
|
|
1079
|
+
return finalXPath;
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
catch (error) {
|
|
1083
|
+
console.error(error);
|
|
1084
|
+
return null;
|
|
1085
|
+
}
|
|
1086
|
+
};
|
|
1087
|
+
const getParentRelativeXpath = (domNode, docmt, node, isTarget) => {
|
|
1088
|
+
const cache = new Map(); // Cache to store computed results
|
|
1089
|
+
if (cache.has(domNode)) {
|
|
1090
|
+
return cache.get(domNode); // Return cached result if available
|
|
1091
|
+
}
|
|
1092
|
+
const xpathParts = []; // Initialize an array to hold parts of the XPath
|
|
1093
|
+
let currentNode = domNode; // Start with the provided DOM node
|
|
1094
|
+
try {
|
|
1095
|
+
while (currentNode && currentNode.nodeType === 1) {
|
|
1096
|
+
// BASE CASE #1: If this isn't an element, we're above the root, return empty string
|
|
1097
|
+
if (!currentNode.tagName) {
|
|
1098
|
+
return "";
|
|
1099
|
+
}
|
|
1100
|
+
// BASE CASE #2: Check for unique attributes
|
|
1101
|
+
let uniqueAttrFound = false;
|
|
1102
|
+
for (const attr of Array.from(currentNode.attributes)) {
|
|
1103
|
+
if (checkBlockedAttributes(attr, currentNode, isTarget)) {
|
|
1104
|
+
let attrValue = attr.nodeValue;
|
|
1105
|
+
attrValue = attrValue?.replace("removePointers", "") ?? "";
|
|
1106
|
+
const elementName = attr.name;
|
|
1107
|
+
const xpathe = getXpathString(currentNode, elementName, attrValue);
|
|
1108
|
+
let othersWithAttr;
|
|
1109
|
+
// If the XPath does not parse, move to the next unique attribute
|
|
1110
|
+
try {
|
|
1111
|
+
othersWithAttr = getCountOfXPath(xpathe, currentNode, docmt);
|
|
1112
|
+
}
|
|
1113
|
+
catch (ign) {
|
|
1114
|
+
continue;
|
|
1115
|
+
}
|
|
1116
|
+
// If the attribute is unique, return its XPath
|
|
1117
|
+
if (othersWithAttr === 1) {
|
|
1118
|
+
xpathParts.unshift(xpathe);
|
|
1119
|
+
uniqueAttrFound = true; // Mark that we found at least one unique attribute
|
|
1120
|
+
break;
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
// If no unique attributes, check for text content
|
|
1125
|
+
if (!uniqueAttrFound && currentNode.textContent && !node.textContent) {
|
|
1126
|
+
const textXPath = getFilteredTextXPath(currentNode, docmt);
|
|
1127
|
+
if (textXPath) {
|
|
1128
|
+
const othersWithAttr = getCountOfXPath(textXPath, currentNode, docmt);
|
|
1129
|
+
if (othersWithAttr === 1) {
|
|
1130
|
+
uniqueAttrFound = true; // Mark that we found at least one unique attribute
|
|
1131
|
+
xpathParts.unshift(textXPath);
|
|
1132
|
+
break;
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
if (!uniqueAttrFound) {
|
|
1137
|
+
// Construct the XPath based on the tag name
|
|
1138
|
+
const xpathe = isSvg(currentNode)
|
|
1139
|
+
? `/*[local-name()='${currentNode.tagName}']`
|
|
1140
|
+
: `/${currentNode.tagName}`;
|
|
1141
|
+
// Prepend the current XPath part to the array
|
|
1142
|
+
xpathParts.unshift(xpathe);
|
|
1143
|
+
}
|
|
1144
|
+
else {
|
|
1145
|
+
break;
|
|
1146
|
+
}
|
|
1147
|
+
// Move to the parent node for the next iteration
|
|
1148
|
+
currentNode = currentNode.parentElement;
|
|
1149
|
+
}
|
|
1150
|
+
// Combine all parts into the final XPath
|
|
1151
|
+
const finalXpath = xpathParts.join("");
|
|
1152
|
+
cache.set(domNode, finalXpath); // Store result in cache
|
|
1153
|
+
return finalXpath;
|
|
1154
|
+
}
|
|
1155
|
+
catch (error) {
|
|
1156
|
+
console.log(error);
|
|
1157
|
+
return null;
|
|
1158
|
+
}
|
|
1159
|
+
};
|
|
1160
|
+
const getChildRelativeXpath = (domNode, docmt, node) => {
|
|
1161
|
+
const xpathParts = []; // Initialize an array to hold parts of the XPath.
|
|
1162
|
+
let currentNode;
|
|
1163
|
+
const st = [];
|
|
1164
|
+
if (domNode.firstElementChild != null &&
|
|
1165
|
+
domNode.firstElementChild.classList.contains("flntooltip")) {
|
|
1166
|
+
st.unshift(domNode.firstElementChild);
|
|
1167
|
+
}
|
|
1168
|
+
else if (domNode.nextElementSibling != null)
|
|
1169
|
+
st.unshift(domNode.nextElementSibling);
|
|
1170
|
+
for (let m = domNode.parentElement; m != null && m.nodeType === 1; m = m.parentElement) {
|
|
1171
|
+
if (m.nextElementSibling)
|
|
1172
|
+
st.unshift(m.nextElementSibling);
|
|
1173
|
+
}
|
|
1174
|
+
try {
|
|
1175
|
+
do {
|
|
1176
|
+
let uniqueAttrFound = false;
|
|
1177
|
+
for (currentNode = st.pop(); currentNode !== null;) {
|
|
1178
|
+
for (const attr of Array.from(currentNode.attributes)) {
|
|
1179
|
+
if (checkBlockedAttributes(attr, currentNode, true)) {
|
|
1180
|
+
let attrValue = attr.nodeValue;
|
|
1181
|
+
attrValue = attrValue?.replace("removePointers", "") ?? "";
|
|
1182
|
+
const elementName = attr.name;
|
|
1183
|
+
const xpathe = getXpathString(currentNode, elementName, attrValue);
|
|
1184
|
+
let othersWithAttr;
|
|
1185
|
+
// If the XPath does not parse, move to the next unique attribute
|
|
1186
|
+
try {
|
|
1187
|
+
othersWithAttr = getCountOfXPath(xpathe, currentNode, docmt);
|
|
1188
|
+
}
|
|
1189
|
+
catch (ign) {
|
|
1190
|
+
continue;
|
|
1191
|
+
}
|
|
1192
|
+
// If the attribute is unique, return its XPath
|
|
1193
|
+
if (othersWithAttr === 1) {
|
|
1194
|
+
uniqueAttrFound = true; // Mark that we found at least one unique attribute
|
|
1195
|
+
xpathParts.push(xpathe);
|
|
1196
|
+
break;
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
// If no unique attributes, check for text content
|
|
1201
|
+
if (!uniqueAttrFound && currentNode.textContent && !node.textContent) {
|
|
1202
|
+
const textXPath = getFilteredTextXPath(currentNode, docmt);
|
|
1203
|
+
if (textXPath) {
|
|
1204
|
+
const othersWithAttr = getCountOfXPath(textXPath, currentNode, docmt);
|
|
1205
|
+
if (othersWithAttr === 1) {
|
|
1206
|
+
uniqueAttrFound = true; // Mark that we found at least one unique attribute
|
|
1207
|
+
xpathParts.push(textXPath);
|
|
1208
|
+
break;
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
if (!uniqueAttrFound) {
|
|
1213
|
+
// Construct the XPath based on the tag name
|
|
1214
|
+
const xpathe = isSvg(currentNode)
|
|
1215
|
+
? `/*[local-name()='${currentNode.tagName}']`
|
|
1216
|
+
: `/${currentNode.tagName}`;
|
|
1217
|
+
// Prepend the current XPath part to the array
|
|
1218
|
+
xpathParts.push(xpathe);
|
|
1219
|
+
if (currentNode.firstElementChild != null) {
|
|
1220
|
+
st.push(currentNode.nextElementSibling);
|
|
1221
|
+
currentNode = currentNode.firstElementChild;
|
|
1222
|
+
}
|
|
1223
|
+
else {
|
|
1224
|
+
currentNode = currentNode.nextElementSibling;
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
else {
|
|
1228
|
+
break;
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
} while (st.length > 0);
|
|
1232
|
+
// Combine all parts into the final XPath
|
|
1233
|
+
const finalXpath = xpathParts.join("");
|
|
1234
|
+
cache.set(domNode, finalXpath); // Store result in cache
|
|
1235
|
+
return finalXpath;
|
|
1236
|
+
}
|
|
1237
|
+
catch (error) {
|
|
1238
|
+
console.log(error);
|
|
1239
|
+
return null;
|
|
1240
|
+
}
|
|
1241
|
+
};
|
|
1242
|
+
const getSiblingRelativeXPath = (domNode, docmt, isTarget, nodeXpath) => {
|
|
1243
|
+
try {
|
|
1244
|
+
const markedSpan = document.querySelector(".flntooltip");
|
|
1245
|
+
for (let m = domNode.nextElementSibling; m !== null && m !== markedSpan; m = m.nextElementSibling) {
|
|
1246
|
+
processSibling(m, domNode, docmt, nodeXpath, "preceding-sibling", isTarget);
|
|
1247
|
+
}
|
|
1248
|
+
for (let n = domNode.previousElementSibling; n !== null && n !== markedSpan; n = n.previousElementSibling) {
|
|
1249
|
+
processSibling(n, domNode, docmt, nodeXpath, "following-sibling", isTarget);
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
catch (error) {
|
|
1253
|
+
console.error("sibling error", error);
|
|
1254
|
+
return null;
|
|
1255
|
+
}
|
|
1256
|
+
};
|
|
1257
|
+
const processSibling = (sibling, domNode, docmt, nodeXpath, axis, isTarget) => {
|
|
1258
|
+
try {
|
|
1259
|
+
if (sibling.hasAttributes()) {
|
|
1260
|
+
for (const attr of Array.from(sibling.attributes)) {
|
|
1261
|
+
let xpathe = intermediateXpathStep(sibling, {
|
|
1262
|
+
name: attr.name,
|
|
1263
|
+
value: attr.value,
|
|
1264
|
+
}, isTarget);
|
|
1265
|
+
if (xpathe) {
|
|
1266
|
+
xpathe += `/${axis}::${nodeXpath}`;
|
|
1267
|
+
const count = getCountOfXPath(xpathe, sibling, docmt);
|
|
1268
|
+
if (count === 1) {
|
|
1269
|
+
xpathData$1.push({
|
|
1270
|
+
key: `xpath by ${axis}`,
|
|
1271
|
+
value: xpathe,
|
|
1272
|
+
});
|
|
1273
|
+
return;
|
|
1274
|
+
}
|
|
1275
|
+
else if (count > 1) {
|
|
1276
|
+
if (xpathDataWithIndex$1.length) {
|
|
1277
|
+
if (count < xpathDataWithIndex$1[0].count) {
|
|
1278
|
+
xpathDataWithIndex$1.pop();
|
|
1279
|
+
xpathDataWithIndex$1.push({
|
|
1280
|
+
key: `relative xpath by ${axis}`,
|
|
1281
|
+
value: xpathe,
|
|
1282
|
+
count,
|
|
1283
|
+
});
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
else {
|
|
1287
|
+
xpathDataWithIndex$1.push({
|
|
1288
|
+
key: `relative xpath by ${axis}`,
|
|
1289
|
+
value: xpathe,
|
|
1290
|
+
count,
|
|
1291
|
+
});
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
if (!isTarget) {
|
|
1298
|
+
let xpathe;
|
|
1299
|
+
xpathe = intermediateXpathStep(sibling, {
|
|
1300
|
+
name: "text",
|
|
1301
|
+
value: sibling.textContent,
|
|
1302
|
+
}, isTarget);
|
|
1303
|
+
if (xpathe) {
|
|
1304
|
+
const count = getCountOfXPath(xpathe, sibling, docmt);
|
|
1305
|
+
if (count === 1) {
|
|
1306
|
+
xpathData$1.push({
|
|
1307
|
+
key: `xpath by ${axis}`,
|
|
1308
|
+
value: xpathe,
|
|
1309
|
+
});
|
|
1310
|
+
return;
|
|
1311
|
+
}
|
|
1312
|
+
else if (count > 1) {
|
|
1313
|
+
if (xpathDataWithIndex$1.length) {
|
|
1314
|
+
if (count < xpathDataWithIndex$1[0].count) {
|
|
1315
|
+
xpathDataWithIndex$1.pop();
|
|
1316
|
+
xpathDataWithIndex$1.push({
|
|
1317
|
+
key: `relative xpath by ${axis}`,
|
|
1318
|
+
value: xpathe,
|
|
1319
|
+
count,
|
|
1320
|
+
});
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
else {
|
|
1324
|
+
xpathDataWithIndex$1.push({
|
|
1325
|
+
key: `relative xpath by ${axis}`,
|
|
1326
|
+
value: xpathe,
|
|
1327
|
+
count,
|
|
1328
|
+
});
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
catch (error) {
|
|
1335
|
+
console.log(`${axis} xpath-error`, error);
|
|
1336
|
+
}
|
|
1337
|
+
};
|
|
1338
|
+
function getXPathUsingAttributeAndText(attributes, targetElemt, docmt, isTarget) {
|
|
1339
|
+
const { tagName } = targetElemt;
|
|
1340
|
+
const textContent = targetElemt.textContent.trim();
|
|
1341
|
+
for (const attrName of attributes) {
|
|
1342
|
+
if (checkBlockedAttributes(attrName, targetElemt, isTarget)) {
|
|
1343
|
+
let attrValue = attrName.nodeValue;
|
|
1344
|
+
attrValue = attrValue?.replace("removePointers", "") ?? "";
|
|
1345
|
+
const elementName = attrName.name;
|
|
1346
|
+
const xpath = `//${tagName}[@${elementName}='${attrValue}' and text()='${textContent}']`;
|
|
1347
|
+
if (xpath) {
|
|
1348
|
+
const count = getCountOfXPath(xpath, targetElemt, docmt);
|
|
1349
|
+
if (count == 1) {
|
|
1350
|
+
return xpath;
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
const addRelativeXpaths = (targetElemt, docmt, isIndex, isTarget, attribute) => {
|
|
1357
|
+
try {
|
|
1358
|
+
let nodeXpath = [];
|
|
1359
|
+
let relativeXpath, relativeChildXpath;
|
|
1360
|
+
if (!xpathData$1.length && isIndex) {
|
|
1361
|
+
if (xpathDataWithIndex$1.length) {
|
|
1362
|
+
const xpathWithIndex = findXpathWithIndex(xpathDataWithIndex$1[0].value, targetElemt, docmt, xpathDataWithIndex$1[0].count);
|
|
1363
|
+
if (xpathWithIndex) {
|
|
1364
|
+
xpathData$1.push({
|
|
1365
|
+
key: xpathDataWithIndex$1[0].key,
|
|
1366
|
+
value: xpathWithIndex,
|
|
1367
|
+
});
|
|
1368
|
+
xpathDataWithIndex$1.pop();
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
1372
|
+
console.log(attribute);
|
|
1373
|
+
if (!xpathData$1.length) {
|
|
1374
|
+
if (targetElemt.attributes) {
|
|
1375
|
+
for (const attrName of attribute) {
|
|
1376
|
+
let expression = intermediateXpathStep(targetElemt, {
|
|
1377
|
+
name: attrName.name,
|
|
1378
|
+
value: attrName.value,
|
|
1379
|
+
}, isTarget);
|
|
1380
|
+
console.log(expression);
|
|
1381
|
+
if (expression) {
|
|
1382
|
+
nodeXpath.push(expression);
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
if (targetElemt.textContent) {
|
|
1387
|
+
let expression = intermediateXpathStep(targetElemt, {
|
|
1388
|
+
name: "text",
|
|
1389
|
+
value: targetElemt.textContent,
|
|
1390
|
+
}, isTarget);
|
|
1391
|
+
console.log(expression);
|
|
1392
|
+
if (expression) {
|
|
1393
|
+
nodeXpath.push(expression);
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
if (nodeXpath?.length) {
|
|
1397
|
+
for (let i = 0; i < nodeXpath.length; i++) {
|
|
1398
|
+
if (!xpathData$1.length) {
|
|
1399
|
+
getSiblingRelativeXPath(targetElemt, docmt, isTarget, nodeXpath[i]);
|
|
1400
|
+
if (!xpathData$1.length) {
|
|
1401
|
+
if (!relativeXpath) {
|
|
1402
|
+
relativeXpath = getParentRelativeXpath(targetElemt.parentElement, docmt, targetElemt, isTarget);
|
|
1403
|
+
}
|
|
1404
|
+
console.log(relativeXpath);
|
|
1405
|
+
if (relativeXpath &&
|
|
1406
|
+
(relativeXpath.includes("@") ||
|
|
1407
|
+
relativeXpath.includes("text()") ||
|
|
1408
|
+
relativeXpath.includes(".=")) &&
|
|
1409
|
+
relativeXpath.match(/\//g)?.length - 2 < 5) {
|
|
1410
|
+
const fullRelativeXpath = relativeXpath + `/${nodeXpath[i]}`;
|
|
1411
|
+
const count = getCountOfXPath(fullRelativeXpath, targetElemt, docmt);
|
|
1412
|
+
if (count === 1) {
|
|
1413
|
+
xpathData$1.push({
|
|
1414
|
+
key: "relative xpath by relative parent",
|
|
1415
|
+
value: fullRelativeXpath,
|
|
1416
|
+
});
|
|
1417
|
+
}
|
|
1418
|
+
else if (count > 1 && isIndex) {
|
|
1419
|
+
const relativeXpathIndex = findXpathWithIndex(fullRelativeXpath, targetElemt, docmt, count);
|
|
1420
|
+
if (relativeXpathIndex &&
|
|
1421
|
+
getCountOfXPath(relativeXpathIndex, targetElemt, docmt) ===
|
|
1422
|
+
1) {
|
|
1423
|
+
xpathData$1.push({
|
|
1424
|
+
key: `relative xpath by relative parent ${isIndex ? "index" : ""}`,
|
|
1425
|
+
value: relativeXpathIndex,
|
|
1426
|
+
});
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
else if (count > 1) {
|
|
1430
|
+
if (xpathDataWithIndex$1.length) {
|
|
1431
|
+
if (count < xpathDataWithIndex$1[0].count) {
|
|
1432
|
+
xpathDataWithIndex$1.pop();
|
|
1433
|
+
xpathDataWithIndex$1.push({
|
|
1434
|
+
key: `relative xpath by relative parent ${isIndex ? "index" : ""}`,
|
|
1435
|
+
value: fullRelativeXpath,
|
|
1436
|
+
count,
|
|
1437
|
+
});
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1440
|
+
else {
|
|
1441
|
+
xpathDataWithIndex$1.push({
|
|
1442
|
+
key: `relative xpath by relative parent ${isIndex ? "index" : ""}`,
|
|
1443
|
+
value: fullRelativeXpath,
|
|
1444
|
+
count,
|
|
1445
|
+
});
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1450
|
+
if (!xpathData$1.length) {
|
|
1451
|
+
if (!relativeChildXpath) {
|
|
1452
|
+
relativeChildXpath = getChildRelativeXpath(targetElemt, docmt, targetElemt);
|
|
1453
|
+
}
|
|
1454
|
+
if (relativeChildXpath &&
|
|
1455
|
+
(relativeChildXpath.includes("@") ||
|
|
1456
|
+
relativeChildXpath.includes("text()") ||
|
|
1457
|
+
relativeChildXpath.includes(".="))) {
|
|
1458
|
+
const fullRelativeXpath = `/${nodeXpath[i] + relativeChildXpath.substring(1)}`;
|
|
1459
|
+
const count = getCountOfXPath(fullRelativeXpath, targetElemt, docmt);
|
|
1460
|
+
if (count === 1) {
|
|
1461
|
+
xpathData$1.push({
|
|
1462
|
+
key: "relative xpath by relative child",
|
|
1463
|
+
value: fullRelativeXpath,
|
|
1464
|
+
});
|
|
1465
|
+
}
|
|
1466
|
+
else if (count > 1 && isIndex) {
|
|
1467
|
+
const relativeXpathIndex = findXpathWithIndex(fullRelativeXpath, targetElemt, docmt, count);
|
|
1468
|
+
if (relativeXpathIndex &&
|
|
1469
|
+
getCountOfXPath(relativeXpathIndex, targetElemt, docmt) ===
|
|
1470
|
+
1) {
|
|
1471
|
+
xpathData$1.push({
|
|
1472
|
+
key: `relative xpath by relative parent ${isIndex ? "index" : ""}`,
|
|
1473
|
+
value: relativeXpathIndex,
|
|
1474
|
+
});
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1477
|
+
else if (count > 1) {
|
|
1478
|
+
if (xpathDataWithIndex$1.length) {
|
|
1479
|
+
if (count < xpathDataWithIndex$1[0].count) {
|
|
1480
|
+
xpathDataWithIndex$1.pop();
|
|
1481
|
+
xpathDataWithIndex$1.push({
|
|
1482
|
+
key: `relative xpath by relative child ${isIndex ? "index" : ""}`,
|
|
1483
|
+
value: fullRelativeXpath,
|
|
1484
|
+
count,
|
|
1485
|
+
});
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1488
|
+
else {
|
|
1489
|
+
xpathDataWithIndex$1.push({
|
|
1490
|
+
key: `relative xpath by relative child ${isIndex ? "index" : ""}`,
|
|
1491
|
+
value: fullRelativeXpath,
|
|
1492
|
+
count,
|
|
1493
|
+
});
|
|
1494
|
+
}
|
|
1495
|
+
}
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1498
|
+
if (xpathData$1?.length === 1 &&
|
|
1499
|
+
xpathData$1?.[0]?.value?.match(/\[([0-9]+)\]/gm)?.length > 3 &&
|
|
1500
|
+
!referenceElementMode) {
|
|
1501
|
+
if (targetElemt.textContent) {
|
|
1502
|
+
const txtXpath = getTextXPath(targetElemt, docmt, isIndex, false);
|
|
1503
|
+
if (txtXpath) {
|
|
1504
|
+
xpathData$1.unshift({
|
|
1505
|
+
key: `xpath by text${isIndex ? "index" : ""}`,
|
|
1506
|
+
value: txtXpath,
|
|
1507
|
+
});
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
if (!xpathData$1.length) {
|
|
1512
|
+
let tempRelativeXpath = getUniqueParentXpath(targetElemt.parentElement, docmt, targetElemt, isTarget, nodeXpath[i], isIndex);
|
|
1513
|
+
if (tempRelativeXpath) {
|
|
1514
|
+
xpathData$1.push({
|
|
1515
|
+
key: "xpath by unique parent",
|
|
1516
|
+
value: tempRelativeXpath,
|
|
1517
|
+
});
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1524
|
+
return xpathData$1;
|
|
1525
|
+
}
|
|
1526
|
+
catch (error) {
|
|
1527
|
+
console.log(error);
|
|
1528
|
+
}
|
|
1529
|
+
};
|
|
1530
|
+
const attributesBasedXPath = (attr, targetElemt, docmt, isIndex, isTarget) => {
|
|
1531
|
+
let attrName;
|
|
1532
|
+
attrName = attr.name;
|
|
1533
|
+
let xpath = getPropertyXPath(targetElemt, docmt, `@${attrName}`, attr.value, isIndex, isTarget);
|
|
1534
|
+
return xpath;
|
|
1535
|
+
};
|
|
1536
|
+
const getUniqueClassName = (element, docmt, isIndex, isTarget) => {
|
|
1537
|
+
let value = element.className;
|
|
1538
|
+
if (typeof value !== "string") {
|
|
1539
|
+
value = "";
|
|
1540
|
+
}
|
|
1541
|
+
value = value?.replace("flndisabled", "disabled");
|
|
1542
|
+
value = value?.replace("removePointers", "");
|
|
1543
|
+
value = value?.trim();
|
|
1544
|
+
if (value) {
|
|
1545
|
+
return getPropertyXPath(element, docmt, `@class`, value, isIndex, isTarget);
|
|
1546
|
+
}
|
|
1547
|
+
};
|
|
1548
|
+
const getTextXPath = (element, docmt, isIndex, isTarget) => {
|
|
1549
|
+
if (element.textContent != "") {
|
|
1550
|
+
const text = getTextContent(element);
|
|
1551
|
+
if (text) {
|
|
1552
|
+
return getPropertyXPath(element, docmt, ".", text, isIndex, isTarget);
|
|
1553
|
+
}
|
|
1554
|
+
}
|
|
1555
|
+
};
|
|
1556
|
+
const addAllXPathAttributes = (attributes, targetElemt, docmt, isIndex, isTarget) => {
|
|
1557
|
+
const attributesArray = attributes;
|
|
1558
|
+
try {
|
|
1559
|
+
attributesArray.map((attr) => {
|
|
1560
|
+
if (!(attr.name === "className" || attr.name === "class")) {
|
|
1561
|
+
if (checkBlockedAttributes(attr, targetElemt, isTarget)) {
|
|
1562
|
+
const xpth = attributesBasedXPath(attr, targetElemt, docmt, isIndex, isTarget);
|
|
1563
|
+
if (xpth) {
|
|
1564
|
+
xpathData$1.push({
|
|
1565
|
+
key: `xpath by ${attr.name}${isIndex ? " index" : ""}`,
|
|
1566
|
+
value: xpth,
|
|
1567
|
+
});
|
|
1568
|
+
}
|
|
1569
|
+
}
|
|
1570
|
+
}
|
|
1571
|
+
});
|
|
1572
|
+
const txtXpath = getTextXPath(targetElemt, docmt, isIndex, isTarget);
|
|
1573
|
+
if (txtXpath) {
|
|
1574
|
+
xpathData$1.push({
|
|
1575
|
+
key: `xpath by text${isIndex ? " index" : ""}`,
|
|
1576
|
+
value: txtXpath,
|
|
1577
|
+
});
|
|
1578
|
+
}
|
|
1579
|
+
if (attributesArray.find((element) => element.name === "className") &&
|
|
1580
|
+
checkBlockedAttributes(attributesArray?.find((element) => element.name === "className"), targetElemt, isTarget)) {
|
|
1581
|
+
let xpath = getUniqueClassName(targetElemt, docmt, isIndex, isTarget);
|
|
1582
|
+
if (xpath) {
|
|
1583
|
+
xpathData$1.push({
|
|
1584
|
+
key: "xpath by class",
|
|
1585
|
+
value: xpath,
|
|
1586
|
+
});
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1589
|
+
if (!xpathData$1.length) {
|
|
1590
|
+
const textAttribute = getXPathUsingAttributeAndText(attributes, targetElemt, docmt, isTarget);
|
|
1591
|
+
if (textAttribute)
|
|
1592
|
+
xpathData$1.push({
|
|
1593
|
+
key: "xpath by textAttribute",
|
|
1594
|
+
value: textAttribute,
|
|
1595
|
+
});
|
|
1596
|
+
}
|
|
1597
|
+
if (!xpathData$1.length && attributesArray.length > 1) {
|
|
1598
|
+
const combinationXpath = getAttributeCombinationXpath(targetElemt, docmt, attributesArray, isTarget);
|
|
1599
|
+
if (combinationXpath)
|
|
1600
|
+
xpathData$1.push({
|
|
1601
|
+
key: "xpath by combination",
|
|
1602
|
+
value: combinationXpath,
|
|
1603
|
+
});
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1606
|
+
catch (error) {
|
|
1607
|
+
console.log(error);
|
|
1608
|
+
}
|
|
1609
|
+
};
|
|
1610
|
+
const parseDOM = (element, doc, isIndex, isTarget) => {
|
|
1611
|
+
xpathData$1 = [];
|
|
1612
|
+
console.log(element);
|
|
1613
|
+
const targetElemt = element;
|
|
1614
|
+
const docmt = targetElemt?.ownerDocument || doc;
|
|
1615
|
+
const tag = targetElemt.tagName;
|
|
1616
|
+
const { attributes } = targetElemt;
|
|
1617
|
+
addAllXPathAttributes(Array.from(attributes), targetElemt, docmt, isIndex, isTarget);
|
|
1618
|
+
{
|
|
1619
|
+
if (xpathData$1.length) {
|
|
1620
|
+
const len = xpathData$1.length;
|
|
1621
|
+
for (let i = 0; i < len; i++) {
|
|
1622
|
+
let xpth = xpathData$1[i].value;
|
|
1623
|
+
xpth = xpth.replace(tag, "*");
|
|
1624
|
+
const count = getCountOfXPath(xpth, element, docmt);
|
|
1625
|
+
if (count === 1) {
|
|
1626
|
+
xpathData$1.push({
|
|
1627
|
+
key: `${xpathData$1[i].key} regex`,
|
|
1628
|
+
value: xpth,
|
|
1629
|
+
});
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1632
|
+
}
|
|
1633
|
+
}
|
|
1634
|
+
console.log(xpathData$1);
|
|
1635
|
+
if (!xpathData$1.length) {
|
|
1636
|
+
addRelativeXpaths(targetElemt, docmt, isIndex, isTarget, Array.from(targetElemt.attributes));
|
|
1637
|
+
}
|
|
1638
|
+
return xpathData$1;
|
|
1639
|
+
};
|
|
1640
|
+
const xpath = {
|
|
1641
|
+
parseDOM,
|
|
1642
|
+
getTextXPath,
|
|
1643
|
+
getUniqueClassName,
|
|
1644
|
+
attributesBasedXPath,
|
|
1645
|
+
addAllXPathAttributes,
|
|
1646
|
+
addRelativeXpaths,
|
|
1647
|
+
getXPathUsingAttributeAndText,
|
|
1648
|
+
getSiblingRelativeXPath,
|
|
1649
|
+
getChildRelativeXpath,
|
|
1650
|
+
getParentRelativeXpath,
|
|
1651
|
+
getUniqueParentXpath,
|
|
1652
|
+
checkRelativeXpathRelation,
|
|
1653
|
+
};
|
|
1654
|
+
|
|
1655
|
+
let xpathDataWithIndex = [];
|
|
1656
|
+
let xpathData = [];
|
|
1657
|
+
const reWhiteSpace = /^[\S]+( [\S]+)*$/gi;
|
|
1658
|
+
const findRelativeXpath = (element1, element2, docmt, xpaths1, xpaths2, isIndex) => {
|
|
1659
|
+
// debugger;
|
|
1660
|
+
const par1 = element1.parentElement;
|
|
1661
|
+
const par2 = element2.parentElement;
|
|
1662
|
+
let rel_xpath = [];
|
|
1663
|
+
let tempElement;
|
|
1664
|
+
findRoot(element1);
|
|
1665
|
+
let finalXpaths = [];
|
|
1666
|
+
if (isIndex) {
|
|
1667
|
+
if (xpathDataWithIndex.length) {
|
|
1668
|
+
const xpathWithIndex = findXpathWithIndex(xpathDataWithIndex[0].value, element2, element2.ownerDocument, xpathDataWithIndex[0].count);
|
|
1669
|
+
if (xpathWithIndex) {
|
|
1670
|
+
finalXpaths = finalXpaths.concat({
|
|
1671
|
+
key: `${xpathDataWithIndex[0]?.key
|
|
1672
|
+
? xpathDataWithIndex[0]?.key
|
|
1673
|
+
: "xpath with "} index`,
|
|
1674
|
+
value: xpathWithIndex,
|
|
1675
|
+
});
|
|
1676
|
+
xpathDataWithIndex.pop();
|
|
1677
|
+
}
|
|
1678
|
+
}
|
|
1679
|
+
}
|
|
1680
|
+
if (!finalXpaths.length) {
|
|
1681
|
+
// both are same
|
|
1682
|
+
if (element1.isSameNode(element2)) {
|
|
1683
|
+
// rel_xpath = xpath1 + "/self::" + element1.tagName;
|
|
1684
|
+
rel_xpath = getXpathRelationExpression(element1, element2, "self", xpaths1, xpaths2, isIndex);
|
|
1685
|
+
if (rel_xpath)
|
|
1686
|
+
finalXpaths = finalXpaths.concat(rel_xpath);
|
|
1687
|
+
}
|
|
1688
|
+
// parent
|
|
1689
|
+
tempElement = element1.parentElement;
|
|
1690
|
+
if (tempElement === element2) {
|
|
1691
|
+
rel_xpath = getXpathRelationExpression(element1, element2, "parent", xpaths1, xpaths2, isIndex);
|
|
1692
|
+
if (rel_xpath)
|
|
1693
|
+
finalXpaths = finalXpaths.concat(rel_xpath);
|
|
1694
|
+
}
|
|
1695
|
+
// ancestor
|
|
1696
|
+
tempElement = element1.parentElement;
|
|
1697
|
+
while (tempElement !== null) {
|
|
1698
|
+
if (tempElement === element2) {
|
|
1699
|
+
rel_xpath = getXpathRelationExpression(element1, element2, "ancestor", xpaths1, xpaths2, isIndex);
|
|
1700
|
+
if (rel_xpath) {
|
|
1701
|
+
finalXpaths = finalXpaths.concat(rel_xpath);
|
|
1702
|
+
break;
|
|
1703
|
+
}
|
|
1704
|
+
}
|
|
1705
|
+
tempElement = tempElement.parentNode;
|
|
1706
|
+
}
|
|
1707
|
+
// ancestor-or-self
|
|
1708
|
+
tempElement = element1;
|
|
1709
|
+
do {
|
|
1710
|
+
if (tempElement === element2) {
|
|
1711
|
+
rel_xpath = getXpathRelationExpression(element1, element2, "ancestor-or-self", xpaths1, xpaths2, isIndex);
|
|
1712
|
+
if (rel_xpath) {
|
|
1713
|
+
finalXpaths = finalXpaths.concat(rel_xpath);
|
|
1714
|
+
break;
|
|
1715
|
+
}
|
|
1716
|
+
}
|
|
1717
|
+
tempElement = tempElement.parentNode;
|
|
1718
|
+
} while (tempElement !== null);
|
|
1719
|
+
// both has same parent
|
|
1720
|
+
if (par1?.isSameNode(par2)) {
|
|
1721
|
+
for (let m = element1.nextElementSibling; m != null; m = m.nextElementSibling) {
|
|
1722
|
+
if (m != null && m.isSameNode(element2)) {
|
|
1723
|
+
rel_xpath = getXpathRelationExpression(element1, element2, "following-sibling", xpaths1, xpaths2, isIndex);
|
|
1724
|
+
if (rel_xpath) {
|
|
1725
|
+
finalXpaths = finalXpaths.concat(rel_xpath);
|
|
1726
|
+
break;
|
|
1727
|
+
}
|
|
1728
|
+
}
|
|
1729
|
+
}
|
|
1730
|
+
for (let n = element1.previousElementSibling; n != null; n = n.previousElementSibling) {
|
|
1731
|
+
if (n != null && n.isSameNode(element2)) {
|
|
1732
|
+
rel_xpath = getXpathRelationExpression(element1, element2, "preceding-sibling", xpaths1, xpaths2, isIndex);
|
|
1733
|
+
if (rel_xpath) {
|
|
1734
|
+
finalXpaths = finalXpaths.concat(rel_xpath);
|
|
1735
|
+
break;
|
|
1736
|
+
}
|
|
1737
|
+
}
|
|
1738
|
+
}
|
|
1739
|
+
}
|
|
1740
|
+
// child
|
|
1741
|
+
if (element1?.children?.length) {
|
|
1742
|
+
for (let m = element1.children[0]; m !== null; m = m?.nextElementSibling) {
|
|
1743
|
+
if (m === element2) {
|
|
1744
|
+
rel_xpath = getXpathRelationExpression(element1, element2, "child", xpaths1, xpaths2, isIndex);
|
|
1745
|
+
if (rel_xpath) {
|
|
1746
|
+
finalXpaths = finalXpaths.concat(rel_xpath);
|
|
1747
|
+
break;
|
|
1748
|
+
}
|
|
1749
|
+
}
|
|
1750
|
+
}
|
|
1751
|
+
}
|
|
1752
|
+
// following
|
|
1753
|
+
const relation = element1.compareDocumentPosition(element2);
|
|
1754
|
+
if (relation === 2) {
|
|
1755
|
+
rel_xpath = getXpathRelationExpression(element1, element2, "preceding", xpaths1, xpaths2, isIndex);
|
|
1756
|
+
}
|
|
1757
|
+
if (relation === 4) {
|
|
1758
|
+
rel_xpath = getXpathRelationExpression(element1, element2, "following", xpaths1, xpaths2, isIndex);
|
|
1759
|
+
}
|
|
1760
|
+
if (rel_xpath) {
|
|
1761
|
+
finalXpaths = finalXpaths.concat(rel_xpath);
|
|
1762
|
+
}
|
|
1763
|
+
const descendantXpath = getDescendantXpath([element1, element2], docmt, xpaths1, xpaths2, "descendant", isIndex);
|
|
1764
|
+
if (descendantXpath)
|
|
1765
|
+
finalXpaths = finalXpaths.concat(descendantXpath);
|
|
1766
|
+
const descendantSelfXpath = getDescendantXpath([element1, element2], docmt, xpaths1, xpaths2, "descedant-or-self", isIndex);
|
|
1767
|
+
if (descendantSelfXpath) {
|
|
1768
|
+
finalXpaths = finalXpaths.concat(descendantSelfXpath);
|
|
1769
|
+
}
|
|
1770
|
+
}
|
|
1771
|
+
if (finalXpaths.length) {
|
|
1772
|
+
if (finalXpaths.length > 1) {
|
|
1773
|
+
finalXpaths.sort(function (a, b) {
|
|
1774
|
+
return a.value.length - b.value.length;
|
|
1775
|
+
});
|
|
1776
|
+
}
|
|
1777
|
+
if (finalXpaths.filter((x) => !x.key?.includes("index"))?.length) {
|
|
1778
|
+
return [finalXpaths.filter((x) => !x.key?.includes("index"))[0]];
|
|
1779
|
+
}
|
|
1780
|
+
return [finalXpaths[0]];
|
|
1781
|
+
}
|
|
1782
|
+
};
|
|
1783
|
+
const descendantExpression = (refExpectElement, xpath2, relation, docmt, isIndex, expCommonParentXpathElements, step4, refCommonParentXpathElementLength) => {
|
|
1784
|
+
let finalExpectedElementXpath = "";
|
|
1785
|
+
if (xpath2.split(/\/(?=(?:[^']*\'[^\']*\')*[^\']*$)/g)?.length >=
|
|
1786
|
+
expCommonParentXpathElements.length) {
|
|
1787
|
+
const xpaths2Els = [];
|
|
1788
|
+
const xpath2Elements = xpath2.split(/\/(?=(?:[^']*\'[^\']*\')*[^\']*$)/g);
|
|
1789
|
+
for (let x = 1; x <= expCommonParentXpathElements.length; x++) {
|
|
1790
|
+
xpaths2Els.unshift(x === expCommonParentXpathElements.length
|
|
1791
|
+
? expCommonParentXpathElements[expCommonParentXpathElements.length - x]
|
|
1792
|
+
: xpath2Elements[xpath2Elements.length - x]);
|
|
1793
|
+
}
|
|
1794
|
+
const traverseXpath = getTraverseXpathExpression(`${step4 +
|
|
1795
|
+
(refCommonParentXpathElementLength
|
|
1796
|
+
? "]".repeat(refCommonParentXpathElementLength)
|
|
1797
|
+
: "")}`, xpaths2Els, refExpectElement[refExpectElement.length - 1], refExpectElement, docmt, relation, isIndex);
|
|
1798
|
+
if (traverseXpath) {
|
|
1799
|
+
return traverseXpath;
|
|
1800
|
+
}
|
|
1801
|
+
}
|
|
1802
|
+
else {
|
|
1803
|
+
finalExpectedElementXpath = `//${step4 +
|
|
1804
|
+
(refCommonParentXpathElementLength
|
|
1805
|
+
? "]".repeat(refCommonParentXpathElementLength)
|
|
1806
|
+
: "")}/${relation}::${replaceActualAttributes(xpath2, refExpectElement[refExpectElement.length - 1])}`;
|
|
1807
|
+
let rel_count = getCountOfXPath(finalExpectedElementXpath, refExpectElement[refExpectElement.length - 1], docmt);
|
|
1808
|
+
if (rel_count === 1) {
|
|
1809
|
+
return [
|
|
1810
|
+
{
|
|
1811
|
+
key: `dynamic ${relation}`,
|
|
1812
|
+
value: replaceTempAttributes(finalExpectedElementXpath),
|
|
1813
|
+
},
|
|
1814
|
+
];
|
|
1815
|
+
}
|
|
1816
|
+
if (rel_count > 1) {
|
|
1817
|
+
if (isIndex) {
|
|
1818
|
+
finalExpectedElementXpath = findXpathWithIndex(finalExpectedElementXpath, refExpectElement[refExpectElement.length - 1], docmt, rel_count);
|
|
1819
|
+
if (finalExpectedElementXpath) {
|
|
1820
|
+
rel_count = getCountOfXPath(finalExpectedElementXpath, refExpectElement[refExpectElement.length - 1], docmt);
|
|
1821
|
+
if (rel_count === 1) {
|
|
1822
|
+
return [
|
|
1823
|
+
{
|
|
1824
|
+
key: `dynamic ${relation} ${isIndex ? " index" : ""}`,
|
|
1825
|
+
value: replaceTempAttributes(finalExpectedElementXpath),
|
|
1826
|
+
},
|
|
1827
|
+
];
|
|
1828
|
+
}
|
|
1829
|
+
}
|
|
1830
|
+
}
|
|
1831
|
+
}
|
|
1832
|
+
}
|
|
1833
|
+
};
|
|
1834
|
+
const getDescendantXpath = (refExpectElement, docmt, xpaths1, xpaths2, relation, isIndex) => {
|
|
1835
|
+
const refElement = refExpectElement[refExpectElement.length - 2];
|
|
1836
|
+
const expElement = refExpectElement[refExpectElement.length - 1];
|
|
1837
|
+
const expElementDocmnt = getShadowRoot(expElement) ?? expElement.ownerDocument;
|
|
1838
|
+
const refAbsoluteXpath = xpaths1.find((x) => x?.key?.includes("absolute"))?.value ||
|
|
1839
|
+
getAbsoluteXPath(refElement, refElement.ownerDocument);
|
|
1840
|
+
const refFullXpathElements = [];
|
|
1841
|
+
const refFullXpathElementsWithoutNumber = [];
|
|
1842
|
+
refAbsoluteXpath.split("/").map((x) => refFullXpathElements.push(x));
|
|
1843
|
+
refFullXpathElements.map((x) => refFullXpathElementsWithoutNumber.push(x.replace(/\[([0-9]+)\]/gm, "")));
|
|
1844
|
+
const expAbsoluteXpath = xpaths2.find((x) => x?.key?.includes("absolute"))?.value ||
|
|
1845
|
+
getAbsoluteXPath(expElement, expElement.ownerDocument);
|
|
1846
|
+
const expFullXpathElements = [];
|
|
1847
|
+
const expFullXpathElementsWithoutNumber = [];
|
|
1848
|
+
expAbsoluteXpath.split("/").map((x) => expFullXpathElements.push(x));
|
|
1849
|
+
expFullXpathElements.map((x) => expFullXpathElementsWithoutNumber.push(x.replace(/\[([0-9]+)\]/gm, "")));
|
|
1850
|
+
for (var parentElementNumber = 0; parentElementNumber < refFullXpathElements.length; parentElementNumber++) {
|
|
1851
|
+
if (refFullXpathElements[parentElementNumber] !=
|
|
1852
|
+
expFullXpathElements[parentElementNumber]) {
|
|
1853
|
+
break;
|
|
1854
|
+
}
|
|
1855
|
+
}
|
|
1856
|
+
const refCommonParentXpathElements = [];
|
|
1857
|
+
for (let i = parentElementNumber - 1; i < refFullXpathElements.length; i++) {
|
|
1858
|
+
if (refFullXpathElements[i]) {
|
|
1859
|
+
refCommonParentXpathElements.push(refFullXpathElementsWithoutNumber[i]);
|
|
1860
|
+
}
|
|
1861
|
+
}
|
|
1862
|
+
const expCommonParentXpathElements = [];
|
|
1863
|
+
for (let j = relation === "descendant" ? parentElementNumber : parentElementNumber - 1; j < expFullXpathElements.length; j++) {
|
|
1864
|
+
if (expFullXpathElements[j]) {
|
|
1865
|
+
if (expCommonParentXpathElements.length)
|
|
1866
|
+
expCommonParentXpathElements.push(expFullXpathElementsWithoutNumber[j]);
|
|
1867
|
+
else
|
|
1868
|
+
expCommonParentXpathElements.push(expFullXpathElementsWithoutNumber[j].replace(/\[([0-9]+)\]/gm, ""));
|
|
1869
|
+
}
|
|
1870
|
+
}
|
|
1871
|
+
xpathData = xpaths2;
|
|
1872
|
+
let nodeXpath2;
|
|
1873
|
+
if (refExpectElement[refExpectElement.length - 2].textContent) {
|
|
1874
|
+
if (!reWhiteSpace.test(refExpectElement[refExpectElement.length - 2].textContent)) {
|
|
1875
|
+
nodeXpath2 = isSvg(refExpectElement[refExpectElement.length - 2])
|
|
1876
|
+
? `*[local-name()='${refExpectElement[refExpectElement.length - 2].tagName}' and ${getTextXpathFunction(refExpectElement[refExpectElement.length - 2])})]`
|
|
1877
|
+
: `${refExpectElement[refExpectElement.length - 2].tagName}[${getTextXpathFunction(refExpectElement[refExpectElement.length - 2])}]`;
|
|
1878
|
+
}
|
|
1879
|
+
else {
|
|
1880
|
+
nodeXpath2 = isSvg(refExpectElement[refExpectElement.length - 2])
|
|
1881
|
+
? `*[local-name()='${refExpectElement[refExpectElement.length - 2].tagName}' and .=${escapeCharacters(getTextContent(refExpectElement[refExpectElement.length - 2]))}]`
|
|
1882
|
+
: `${refExpectElement[refExpectElement.length - 2].tagName}[.=${escapeCharacters(getTextContent(refExpectElement[refExpectElement.length - 2]))}]`;
|
|
1883
|
+
}
|
|
1884
|
+
refCommonParentXpathElements[refCommonParentXpathElements.length - 1] =
|
|
1885
|
+
nodeXpath2;
|
|
1886
|
+
const refCommonParentXpath = refCommonParentXpathElements.join("[");
|
|
1887
|
+
const refCommonParentXpathElementLength = refCommonParentXpathElements.length - 1;
|
|
1888
|
+
const step4 = refCommonParentXpath;
|
|
1889
|
+
for (let i = 0; i < xpathData.length; i++) {
|
|
1890
|
+
let xpath2;
|
|
1891
|
+
if (xpathData[i].value.startsWith("//")) {
|
|
1892
|
+
xpath2 = xpathData[i].value.substring(xpathData[i].value.indexOf("//") + 2);
|
|
1893
|
+
}
|
|
1894
|
+
else {
|
|
1895
|
+
xpath2 = xpathData[i].value; // No need to modify the value
|
|
1896
|
+
}
|
|
1897
|
+
if (xpath2) {
|
|
1898
|
+
return descendantExpression(refExpectElement, xpath2, relation, expElementDocmnt || docmt, isIndex, expCommonParentXpathElements, step4, refCommonParentXpathElementLength);
|
|
1899
|
+
}
|
|
1900
|
+
}
|
|
1901
|
+
}
|
|
1902
|
+
if (refExpectElement[refExpectElement.length - 2].attributes) {
|
|
1903
|
+
for (const attrName of Array.from(refExpectElement[refExpectElement.length - 2]
|
|
1904
|
+
.attributes)) {
|
|
1905
|
+
if (checkBlockedAttributes(attrName, refExpectElement[refExpectElement.length - 2], false)) {
|
|
1906
|
+
let attrValue = attrName.nodeValue;
|
|
1907
|
+
if (attrValue) {
|
|
1908
|
+
attrValue = attrValue.replace("removePointers", "");
|
|
1909
|
+
const elementName = attrName.name;
|
|
1910
|
+
nodeXpath2 = isSvg(refExpectElement[refExpectElement.length - 2])
|
|
1911
|
+
? `*[local-name()='${refExpectElement[refExpectElement.length - 2].tagName}' and @${elementName}=${escapeCharacters(attrValue)}]`
|
|
1912
|
+
: `${refExpectElement[refExpectElement.length - 2].tagName}[@${elementName}=${escapeCharacters(attrValue)}]`;
|
|
1913
|
+
refCommonParentXpathElements[refCommonParentXpathElements.length - 1] = nodeXpath2;
|
|
1914
|
+
const refCommonParentXpath = refCommonParentXpathElements.join("[");
|
|
1915
|
+
const refCommonParentXpathElementLength = refCommonParentXpathElements.length - 1;
|
|
1916
|
+
const step4 = refCommonParentXpath;
|
|
1917
|
+
for (let i = 0; i < xpathData.length; i++) {
|
|
1918
|
+
let xpath2;
|
|
1919
|
+
if (xpathData[i].value.startsWith("//")) {
|
|
1920
|
+
xpath2 = xpathData[i].value.substring(xpathData[i].value.indexOf("//") + 2);
|
|
1921
|
+
}
|
|
1922
|
+
else {
|
|
1923
|
+
xpath2 = xpathData[i].value; // No need to modify the value
|
|
1924
|
+
}
|
|
1925
|
+
return descendantExpression(refExpectElement, xpath2, relation, expElementDocmnt || docmt, isIndex, expCommonParentXpathElements, step4, refCommonParentXpathElementLength);
|
|
1926
|
+
}
|
|
1927
|
+
}
|
|
1928
|
+
}
|
|
1929
|
+
}
|
|
1930
|
+
for (const attrName of Array.from(refExpectElement[refExpectElement.length - 2]
|
|
1931
|
+
.attributes)) {
|
|
1932
|
+
if (checkBlockedAttributes(attrName, refExpectElement[refExpectElement.length - 2], false)) {
|
|
1933
|
+
let attrValue = attrName.nodeValue;
|
|
1934
|
+
if (attrValue) {
|
|
1935
|
+
attrValue = attrValue.replace("removePointers", "");
|
|
1936
|
+
const combinationXpath = getCombinationXpath(attrName, refExpectElement[refExpectElement.length - 2]);
|
|
1937
|
+
if (combinationXpath) {
|
|
1938
|
+
if (combinationXpath.startsWith("//")) {
|
|
1939
|
+
nodeXpath2 = combinationXpath.substring(combinationXpath.indexOf("//") + 2);
|
|
1940
|
+
}
|
|
1941
|
+
else {
|
|
1942
|
+
nodeXpath2 = combinationXpath; // No need to modify the value
|
|
1943
|
+
}
|
|
1944
|
+
refCommonParentXpathElements[refCommonParentXpathElements.length - 1] = nodeXpath2;
|
|
1945
|
+
const refCommonParentXpath = refCommonParentXpathElements.join("[");
|
|
1946
|
+
const refCommonParentXpathElementLength = refCommonParentXpathElements.length - 1;
|
|
1947
|
+
const step4 = refCommonParentXpath;
|
|
1948
|
+
for (let i = 0; i < xpathData.length; i++) {
|
|
1949
|
+
let xpath2;
|
|
1950
|
+
if (xpathData[i].value.startsWith("//")) {
|
|
1951
|
+
xpath2 = xpathData[i].value.substring(xpathData[i].value.indexOf("//") + 2);
|
|
1952
|
+
}
|
|
1953
|
+
else {
|
|
1954
|
+
xpath2 = xpathData[i].value; // No need to modify the value
|
|
1955
|
+
}
|
|
1956
|
+
return descendantExpression(refExpectElement, xpath2, relation, expElementDocmnt || docmt, isIndex, expCommonParentXpathElements, step4, refCommonParentXpathElementLength);
|
|
1957
|
+
}
|
|
1958
|
+
}
|
|
1959
|
+
}
|
|
1960
|
+
}
|
|
1961
|
+
}
|
|
1962
|
+
}
|
|
1963
|
+
const refCommonParentXpath = refCommonParentXpathElements.join("[");
|
|
1964
|
+
const refCommonParentXpathElementLength = refCommonParentXpathElements.length - 1;
|
|
1965
|
+
const step4 = refCommonParentXpath;
|
|
1966
|
+
const traverseXpath = getTraverseXpathExpression(`${step4 +
|
|
1967
|
+
(refCommonParentXpathElementLength
|
|
1968
|
+
? "]".repeat(refCommonParentXpathElementLength)
|
|
1969
|
+
: "")}`, expCommonParentXpathElements.reverse(), refExpectElement[refExpectElement.length - 1], refExpectElement, docmt, relation, isIndex);
|
|
1970
|
+
if (traverseXpath) {
|
|
1971
|
+
return traverseXpath;
|
|
1972
|
+
}
|
|
1973
|
+
};
|
|
1974
|
+
const getXpathRelationExpression = (element1, element2, relation, xpath1, xpath2, isIndex) => {
|
|
1975
|
+
let xpaths1;
|
|
1976
|
+
let xpaths2;
|
|
1977
|
+
console.log('getXpathRelationExpression', relation);
|
|
1978
|
+
const finalXpaths = [];
|
|
1979
|
+
try {
|
|
1980
|
+
xpaths1 = xpath1.filter((x) => !x?.key?.includes("absolute"));
|
|
1981
|
+
xpaths2 = xpath2.filter((x) => !x?.key?.includes("absolute"));
|
|
1982
|
+
for (let i = 0; i < xpaths1.length; i++) {
|
|
1983
|
+
for (let j = 0; j < xpaths2.length; j++) {
|
|
1984
|
+
let rel_xpath = `//${xpaths1[i].value.indexOf("//") !== 0
|
|
1985
|
+
? replaceActualAttributes(xpaths1[i].value, element1)
|
|
1986
|
+
: replaceActualAttributes(xpaths1[i].value.substring(xpaths1[i].value.indexOf("//") + 2), element1)}/${relation}::${xpaths2[j].value.indexOf("//") !== 0
|
|
1987
|
+
? replaceActualAttributes(xpaths2[j].value, element2)
|
|
1988
|
+
: replaceActualAttributes(xpaths2[j].value.substring(xpaths2[j].value.indexOf("//") + 2), element2)}`;
|
|
1989
|
+
console.log('getXpathRelationExpression', rel_xpath);
|
|
1990
|
+
const rel_count = getCountOfXPath(rel_xpath, element2, element2.ownerDocument);
|
|
1991
|
+
if (rel_count > 1) {
|
|
1992
|
+
if (isIndex) {
|
|
1993
|
+
rel_xpath = findXpathWithIndex(rel_xpath, element2, element2.ownerDocument, rel_count);
|
|
1994
|
+
if (rel_xpath) {
|
|
1995
|
+
finalXpaths.push({
|
|
1996
|
+
key: `dynamic ${relation}${isIndex ? " index" : ""}`,
|
|
1997
|
+
value: replaceTempAttributes(rel_xpath),
|
|
1998
|
+
});
|
|
1999
|
+
return finalXpaths;
|
|
2000
|
+
}
|
|
2001
|
+
}
|
|
2002
|
+
else if (rel_count > 1) {
|
|
2003
|
+
if (xpathDataWithIndex.length) {
|
|
2004
|
+
if (rel_count < xpathDataWithIndex[0].count) {
|
|
2005
|
+
xpathDataWithIndex.pop();
|
|
2006
|
+
xpathDataWithIndex.push({
|
|
2007
|
+
key: `relative xpath by relative child ${isIndex ? "index" : ""}`,
|
|
2008
|
+
value: rel_xpath,
|
|
2009
|
+
count: rel_count,
|
|
2010
|
+
});
|
|
2011
|
+
}
|
|
2012
|
+
}
|
|
2013
|
+
else {
|
|
2014
|
+
xpathDataWithIndex.push({
|
|
2015
|
+
key: `relative xpath by relative child ${isIndex ? "index" : ""}`,
|
|
2016
|
+
value: rel_xpath,
|
|
2017
|
+
count: rel_count,
|
|
2018
|
+
});
|
|
2019
|
+
}
|
|
2020
|
+
}
|
|
2021
|
+
}
|
|
2022
|
+
else if (rel_count === 1) {
|
|
2023
|
+
finalXpaths.push({
|
|
2024
|
+
key: `dynamic ${relation}`,
|
|
2025
|
+
value: replaceTempAttributes(rel_xpath),
|
|
2026
|
+
});
|
|
2027
|
+
return finalXpaths;
|
|
2028
|
+
}
|
|
2029
|
+
}
|
|
2030
|
+
}
|
|
2031
|
+
if (!finalXpaths.length) {
|
|
2032
|
+
for (let i = 0; i < xpaths1.length; i++) {
|
|
2033
|
+
for (let j = 0; j < xpaths2.length; j++) {
|
|
2034
|
+
const tempPath = `${xpaths2[j].value.indexOf("//") !== 0
|
|
2035
|
+
? xpaths2[j].value
|
|
2036
|
+
: xpaths2[j].value.substring(xpaths2[j].value.indexOf("//") + 2)}`;
|
|
2037
|
+
const xpath2Elements = tempPath.split(/\/(?=(?:[^']*\'[^\']*\')*[^\']*$)/g);
|
|
2038
|
+
if (xpath2Elements.length > 1) {
|
|
2039
|
+
const traverseXpath = getTraverseXpathExpression(`${xpaths1[i].value.indexOf("//") !== 0
|
|
2040
|
+
? replaceActualAttributes(xpaths1[i].value, element1)
|
|
2041
|
+
: replaceActualAttributes(xpaths1[i].value.substring(xpaths1[i].value.indexOf("//") + 2), element1)}`, xpath2Elements, element2, [element1, element2], element2.ownerDocument, relation, isIndex);
|
|
2042
|
+
console.log('getXpathRelationExpression traverseXpath', traverseXpath);
|
|
2043
|
+
if (traverseXpath) {
|
|
2044
|
+
finalXpaths.concat(traverseXpath);
|
|
2045
|
+
return finalXpaths;
|
|
2046
|
+
}
|
|
2047
|
+
}
|
|
2048
|
+
}
|
|
2049
|
+
}
|
|
2050
|
+
}
|
|
2051
|
+
}
|
|
2052
|
+
catch (error) {
|
|
2053
|
+
console.log(error);
|
|
2054
|
+
}
|
|
2055
|
+
return finalXpaths;
|
|
2056
|
+
};
|
|
2057
|
+
const getReferenceElementXpath = (element) => {
|
|
2058
|
+
let xpaths1 = [];
|
|
2059
|
+
xpaths1 = parseDOM(element, element.ownerDocument, false, false);
|
|
2060
|
+
if (!xpaths1?.length) {
|
|
2061
|
+
xpaths1 = parseDOM(element, element.ownerDocument, true, false);
|
|
2062
|
+
xpaths1 = xpaths1?.map((x) => x.value.charAt(0) == "(" &&
|
|
2063
|
+
findMatchingParenthesis(x.value, 0) + 1 === x.value.lastIndexOf("[")
|
|
2064
|
+
? { key: "", value: removeParenthesis(x.value) }
|
|
2065
|
+
: { key: "", value: x.value });
|
|
2066
|
+
}
|
|
2067
|
+
else {
|
|
2068
|
+
let xpaths = parseDOM(element, element.ownerDocument, true, false);
|
|
2069
|
+
if (xpaths?.length) {
|
|
2070
|
+
xpaths = xpaths?.map((x) => x.value.charAt(0) == "(" &&
|
|
2071
|
+
findMatchingParenthesis(x.value, 0) + 1 === x.value.lastIndexOf("[")
|
|
2072
|
+
? { key: "", value: removeParenthesis(x.value) }
|
|
2073
|
+
: { key: "", value: x.value });
|
|
2074
|
+
xpaths1 = xpaths1.concat(xpaths);
|
|
2075
|
+
}
|
|
2076
|
+
}
|
|
2077
|
+
if (!xpaths1?.length) {
|
|
2078
|
+
xpaths1 = [
|
|
2079
|
+
{
|
|
2080
|
+
key: "",
|
|
2081
|
+
value: getRelativeXPath(element, element.ownerDocument, false, false, Array.from(element.attributes)),
|
|
2082
|
+
},
|
|
2083
|
+
];
|
|
2084
|
+
}
|
|
2085
|
+
if (!xpaths1?.length) {
|
|
2086
|
+
xpaths1 = [
|
|
2087
|
+
{
|
|
2088
|
+
key: "",
|
|
2089
|
+
value: getRelativeXPath(element, element.ownerDocument, true, false, Array.from(element.attributes)),
|
|
2090
|
+
},
|
|
2091
|
+
];
|
|
2092
|
+
xpaths1 = xpaths1?.map((x) => x.value.charAt(0) == "(" &&
|
|
2093
|
+
findMatchingParenthesis(x.value, 0) + 1 === x.value.lastIndexOf("[")
|
|
2094
|
+
? { key: "", value: removeParenthesis(x.value) }
|
|
2095
|
+
: { key: "", value: x.value });
|
|
2096
|
+
}
|
|
2097
|
+
if (!xpaths1?.length) {
|
|
2098
|
+
xpaths1 = getReferenceElementsXpath(element, element.ownerDocument, false);
|
|
2099
|
+
}
|
|
2100
|
+
else {
|
|
2101
|
+
const xpaths = getReferenceElementsXpath(element, element.ownerDocument, false);
|
|
2102
|
+
if (xpaths?.length) {
|
|
2103
|
+
xpaths1 = xpaths1.concat(xpaths);
|
|
2104
|
+
}
|
|
2105
|
+
}
|
|
2106
|
+
const referenceXpathElement = getAbsoluteXPath(element, element.ownerDocument);
|
|
2107
|
+
xpaths1 = xpaths1.filter((x) => x.value !== referenceXpathElement);
|
|
2108
|
+
xpaths1.push({
|
|
2109
|
+
key: "absolute xpath",
|
|
2110
|
+
value: referenceXpathElement.slice(1),
|
|
2111
|
+
});
|
|
2112
|
+
return xpaths1;
|
|
2113
|
+
};
|
|
2114
|
+
const getTraverseXpathExpression = (xpathe1, absoluteXpathElements, element2, refExpectElement, docmt, relation, isIndex) => {
|
|
2115
|
+
let finalExpectedElementXpath;
|
|
2116
|
+
{
|
|
2117
|
+
for (let x = 1; x <= absoluteXpathElements.length; x++) {
|
|
2118
|
+
const xpath2 = absoluteXpathElements
|
|
2119
|
+
.slice(absoluteXpathElements.length - x, absoluteXpathElements.length)
|
|
2120
|
+
.join("/");
|
|
2121
|
+
finalExpectedElementXpath = `//${xpathe1}/${relation}::${replaceActualAttributes(xpath2)}`;
|
|
2122
|
+
const rel_count = getCountOfXPath(finalExpectedElementXpath, element2, docmt);
|
|
2123
|
+
if (rel_count === 1) {
|
|
2124
|
+
return [
|
|
2125
|
+
{
|
|
2126
|
+
key: `dynamic ${relation}`,
|
|
2127
|
+
value: replaceTempAttributes(finalExpectedElementXpath),
|
|
2128
|
+
},
|
|
2129
|
+
];
|
|
2130
|
+
}
|
|
2131
|
+
if (rel_count > 1) {
|
|
2132
|
+
if (isIndex) {
|
|
2133
|
+
finalExpectedElementXpath = findXpathWithIndex(finalExpectedElementXpath, refExpectElement[refExpectElement.length - 1], docmt, rel_count);
|
|
2134
|
+
if (finalExpectedElementXpath) {
|
|
2135
|
+
return [
|
|
2136
|
+
{
|
|
2137
|
+
key: `dynamic ${relation}${isIndex ? " index" : ""}`,
|
|
2138
|
+
value: replaceTempAttributes(finalExpectedElementXpath),
|
|
2139
|
+
},
|
|
2140
|
+
];
|
|
2141
|
+
}
|
|
2142
|
+
}
|
|
2143
|
+
}
|
|
2144
|
+
}
|
|
2145
|
+
}
|
|
2146
|
+
};
|
|
2147
|
+
const referenceXpath = {
|
|
2148
|
+
findRelativeXpath,
|
|
2149
|
+
getDescendantXpath,
|
|
2150
|
+
getXpathRelationExpression,
|
|
2151
|
+
getTraverseXpathExpression,
|
|
2152
|
+
getReferenceElementXpath,
|
|
2153
|
+
};
|
|
2154
|
+
|
|
2155
|
+
const getElementFromShadowRoot = (element, selector) => {
|
|
2156
|
+
const shadowRoot = element.shadowRoot;
|
|
2157
|
+
if (shadowRoot && !selector.includes("dynamic")) {
|
|
2158
|
+
return shadowRoot.querySelector(selector);
|
|
2159
|
+
}
|
|
2160
|
+
return null;
|
|
2161
|
+
};
|
|
2162
|
+
const getId = (element) => {
|
|
2163
|
+
return element?.id || null;
|
|
2164
|
+
};
|
|
2165
|
+
const getClassName = (element) => {
|
|
2166
|
+
return element.className || null;
|
|
2167
|
+
};
|
|
2168
|
+
const getVisibleText = (element) => {
|
|
2169
|
+
return element.textContent?.trim() || null;
|
|
2170
|
+
};
|
|
2171
|
+
const getName = (element) => {
|
|
2172
|
+
const elementEl = element;
|
|
2173
|
+
if (elementEl.hasAttribute("name")) {
|
|
2174
|
+
const attrValue = elementEl.getAttribute("name");
|
|
2175
|
+
const name = `${attrValue}`;
|
|
2176
|
+
return name || null;
|
|
2177
|
+
}
|
|
2178
|
+
return null;
|
|
2179
|
+
};
|
|
2180
|
+
const relations = [
|
|
2181
|
+
"/preceding-sibling",
|
|
2182
|
+
"/following-sibling",
|
|
2183
|
+
"/parent",
|
|
2184
|
+
"/descendant",
|
|
2185
|
+
"/ancestor",
|
|
2186
|
+
"/self",
|
|
2187
|
+
"/ancestor-or-self",
|
|
2188
|
+
"/child",
|
|
2189
|
+
"/preceding",
|
|
2190
|
+
"/following",
|
|
2191
|
+
];
|
|
2192
|
+
function getElementFromXPath(tempDiv, xpath) {
|
|
2193
|
+
let currentElement = tempDiv;
|
|
2194
|
+
const window = currentElement.ownerDocument.defaultView;
|
|
2195
|
+
if (!window)
|
|
2196
|
+
return null;
|
|
2197
|
+
const xpathEvaluator = new window.XPathEvaluator();
|
|
2198
|
+
const xpathResult = xpathEvaluator.evaluate(xpath, currentElement.ownerDocument, //here even tempDiv can be passed
|
|
2199
|
+
null, window.XPathResult.FIRST_ORDERED_NODE_TYPE, null);
|
|
2200
|
+
return xpathResult.singleNodeValue;
|
|
2201
|
+
}
|
|
2202
|
+
function checkReferenceElementIsValid(locator, relation, tempDiv) {
|
|
2203
|
+
if (locator.includes(relation)) {
|
|
2204
|
+
const locatotSplitArray = locator.split(relation);
|
|
2205
|
+
const sourceLoc = locatotSplitArray[0].trim();
|
|
2206
|
+
let currentElement = tempDiv;
|
|
2207
|
+
const window = currentElement.ownerDocument.defaultView;
|
|
2208
|
+
if (!window)
|
|
2209
|
+
return null;
|
|
2210
|
+
if (!locator.includes("dynamic")) {
|
|
2211
|
+
const xpathEvaluator = new window.XPathEvaluator();
|
|
2212
|
+
const xpathResult = xpathEvaluator.evaluate(sourceLoc, currentElement.ownerDocument, null, window.XPathResult.FIRST_ORDERED_NODE_TYPE, null);
|
|
2213
|
+
const sourceElement = xpathResult.singleNodeValue;
|
|
2214
|
+
if (sourceElement) {
|
|
2215
|
+
const xpathResultComplete = xpathEvaluator.evaluate(locator, currentElement.ownerDocument, null, window.XPathResult.FIRST_ORDERED_NODE_TYPE, null);
|
|
2216
|
+
const completeElement = xpathResultComplete.singleNodeValue;
|
|
2217
|
+
let relativeXpath;
|
|
2218
|
+
if (completeElement) {
|
|
2219
|
+
relativeXpath = locator;
|
|
2220
|
+
return relativeXpath;
|
|
2221
|
+
}
|
|
2222
|
+
else {
|
|
2223
|
+
console.error("Complete Locator is Invalid:", locator);
|
|
2224
|
+
relativeXpath = locator;
|
|
2225
|
+
return relativeXpath;
|
|
2226
|
+
}
|
|
2227
|
+
}
|
|
2228
|
+
else {
|
|
2229
|
+
console.error("Source Locator Not Found:", sourceLoc);
|
|
2230
|
+
}
|
|
2231
|
+
}
|
|
2232
|
+
}
|
|
2233
|
+
return null;
|
|
2234
|
+
}
|
|
2235
|
+
const getElementsFromHTML = (record, docmt) => {
|
|
2236
|
+
const document = docmt;
|
|
2237
|
+
// global.SVGElement = document.defaultView?.SVGElement!;
|
|
2238
|
+
const tempDiv = document.createElement("div");
|
|
2239
|
+
const elementsToRemove = document.querySelectorAll("script, style, link[rel='stylesheet'], meta, noscript, embed, object, param, source, svg");
|
|
2240
|
+
if (elementsToRemove) {
|
|
2241
|
+
elementsToRemove.forEach((tag) => {
|
|
2242
|
+
tag.remove();
|
|
2243
|
+
});
|
|
2244
|
+
}
|
|
2245
|
+
tempDiv.innerHTML = document.body.innerHTML;
|
|
2246
|
+
const finalLocatorsSet = new Set();
|
|
2247
|
+
let finalLocators = [];
|
|
2248
|
+
function createLocator(base, overrides = {}) {
|
|
2249
|
+
const newLocator = {
|
|
2250
|
+
name: overrides.name ?? base?.name,
|
|
2251
|
+
type: overrides.type ?? base?.type,
|
|
2252
|
+
value: overrides.value ?? base?.value,
|
|
2253
|
+
reference: overrides.reference ?? base?.reference,
|
|
2254
|
+
status: overrides.status ?? base?.status,
|
|
2255
|
+
isRecorded: overrides.isRecorded ?? base?.isRecorded,
|
|
2256
|
+
};
|
|
2257
|
+
if (overrides.hasOwnProperty("isSelfHealed")) {
|
|
2258
|
+
newLocator.isSelfHealed = overrides.isSelfHealed;
|
|
2259
|
+
}
|
|
2260
|
+
else if (base?.hasOwnProperty("isSelfHealed")) {
|
|
2261
|
+
newLocator.isSelfHealed = base.isSelfHealed;
|
|
2262
|
+
}
|
|
2263
|
+
pushUniqueLocator(newLocator);
|
|
2264
|
+
}
|
|
2265
|
+
function pushUniqueLocator(obj) {
|
|
2266
|
+
const key = `${obj.name}:${obj.value}`;
|
|
2267
|
+
if (!finalLocatorsSet.has(key)) {
|
|
2268
|
+
finalLocatorsSet.add(key);
|
|
2269
|
+
finalLocators.push(obj);
|
|
2270
|
+
}
|
|
2271
|
+
}
|
|
2272
|
+
/** Locator Value Cleaner (Handles Special Scenarios) **/
|
|
2273
|
+
const cleanLocatorValue = (val, type, isRecorded) => {
|
|
2274
|
+
if (!val)
|
|
2275
|
+
return null;
|
|
2276
|
+
let cleaned = val.trim();
|
|
2277
|
+
// Return null for empty or literal "null"
|
|
2278
|
+
if (!cleaned || cleaned.toLowerCase() === "null")
|
|
2279
|
+
return null;
|
|
2280
|
+
// Unescape any escaped quotes
|
|
2281
|
+
cleaned = cleaned.replace(/\\"/g, '"').replace(/\\'/g, "'");
|
|
2282
|
+
// Remove surrounding single or double quotes
|
|
2283
|
+
cleaned = cleaned.replace(/^['"](.+?)['"]$/, "$1");
|
|
2284
|
+
// Replace double single quotes with a single quote inside XPath
|
|
2285
|
+
cleaned = cleaned.replace(/''/g, "'");
|
|
2286
|
+
// Normalize double quotes in XPath attribute selectors [@id="" -> [@id='']
|
|
2287
|
+
cleaned = cleaned.replace(/\[@(id|name)=['"]{2}(.+?)['"]{2}\]/g, "[@$1='$2']");
|
|
2288
|
+
// For DOM selectors (id or name), remove ALL quotes
|
|
2289
|
+
if (type === "id" || type === "name") {
|
|
2290
|
+
cleaned = cleaned.replace(/['"]/g, "").trim();
|
|
2291
|
+
}
|
|
2292
|
+
if (type === "xpath" && isRecorded === "Y" && !val.startsWith("//"))
|
|
2293
|
+
return null;
|
|
2294
|
+
// Final check for empty strings
|
|
2295
|
+
if (!cleaned || /^['"]{2}$/.test(cleaned))
|
|
2296
|
+
return null;
|
|
2297
|
+
return cleaned;
|
|
2298
|
+
};
|
|
2299
|
+
locators: for (const locator of record.locators) {
|
|
2300
|
+
try {
|
|
2301
|
+
const isRecorded = String(locator.isRecorded || "");
|
|
2302
|
+
const recordedNLocators = record.locators.filter((l) => l.isRecorded === "N");
|
|
2303
|
+
if (recordedNLocators.length > 0) {
|
|
2304
|
+
for (const locator of recordedNLocators) {
|
|
2305
|
+
createLocator(locator);
|
|
2306
|
+
}
|
|
2307
|
+
}
|
|
2308
|
+
const isDynamic = String(locator.value || locator.type || "");
|
|
2309
|
+
if (isDynamic.includes("dynamic") ||
|
|
2310
|
+
isDynamic.match("dynamic") ||
|
|
2311
|
+
isDynamic.includes("{") ||
|
|
2312
|
+
isDynamic.includes("}")) {
|
|
2313
|
+
createLocator(locator);
|
|
2314
|
+
continue;
|
|
2315
|
+
}
|
|
2316
|
+
if (record.isShared.includes("Y")) {
|
|
2317
|
+
break locators;
|
|
2318
|
+
}
|
|
2319
|
+
for (const relation of relations) {
|
|
2320
|
+
try {
|
|
2321
|
+
let targetElement = null;
|
|
2322
|
+
if (locator.value.startsWith("iframe")) {
|
|
2323
|
+
const iframe = tempDiv.querySelector(locator.value);
|
|
2324
|
+
if (iframe) {
|
|
2325
|
+
const iframeDocument = iframe.contentDocument || iframe.contentWindow?.document;
|
|
2326
|
+
if (iframeDocument) {
|
|
2327
|
+
targetElement = iframeDocument.querySelector(locator.value.slice(6));
|
|
2328
|
+
}
|
|
2329
|
+
}
|
|
2330
|
+
}
|
|
2331
|
+
else {
|
|
2332
|
+
const selectors = locator.value.split(">>>"); // Custom delimiter for shadow DOM
|
|
2333
|
+
let currentElement = tempDiv;
|
|
2334
|
+
for (const selector of selectors) {
|
|
2335
|
+
if (currentElement) {
|
|
2336
|
+
const trimmedSelector = selector.trim();
|
|
2337
|
+
if (locator.name.includes("id") ||
|
|
2338
|
+
trimmedSelector.startsWith("#")) {
|
|
2339
|
+
targetElement = currentElement.querySelector("#" + trimmedSelector);
|
|
2340
|
+
}
|
|
2341
|
+
else if (locator.name.includes("className") ||
|
|
2342
|
+
trimmedSelector.startsWith(".")) {
|
|
2343
|
+
targetElement = currentElement.querySelector("." + trimmedSelector);
|
|
2344
|
+
}
|
|
2345
|
+
else if ((locator.name.includes("xpath") ||
|
|
2346
|
+
trimmedSelector.startsWith("//")) &&
|
|
2347
|
+
!locator.type.match("dynamic")) {
|
|
2348
|
+
if (tempDiv.innerHTML) {
|
|
2349
|
+
const normalizedXPath = normalizeXPath(trimmedSelector);
|
|
2350
|
+
targetElement = getElementFromXPath(tempDiv, normalizedXPath);
|
|
2351
|
+
if (targetElement) {
|
|
2352
|
+
createLocator(locator, {
|
|
2353
|
+
value: trimmedSelector,
|
|
2354
|
+
isRecorded: String(locator.isRecorded).includes("N")
|
|
2355
|
+
? "N"
|
|
2356
|
+
: "Y",
|
|
2357
|
+
});
|
|
2358
|
+
}
|
|
2359
|
+
}
|
|
2360
|
+
}
|
|
2361
|
+
else {
|
|
2362
|
+
targetElement = currentElement.querySelector(trimmedSelector);
|
|
2363
|
+
if (!targetElement) {
|
|
2364
|
+
targetElement = getElementFromShadowRoot(currentElement, trimmedSelector);
|
|
2365
|
+
}
|
|
2366
|
+
}
|
|
2367
|
+
if (!targetElement &&
|
|
2368
|
+
isSvg(currentElement) &&
|
|
2369
|
+
!locator.type.match("dynamic")) {
|
|
2370
|
+
targetElement = currentElement.querySelector(trimmedSelector);
|
|
2371
|
+
}
|
|
2372
|
+
currentElement = targetElement;
|
|
2373
|
+
}
|
|
2374
|
+
else {
|
|
2375
|
+
console.error("Element not found at:", selector);
|
|
2376
|
+
break;
|
|
2377
|
+
}
|
|
2378
|
+
}
|
|
2379
|
+
}
|
|
2380
|
+
const locatorExists = (name, value) => {
|
|
2381
|
+
const key = `${name}:${value}`;
|
|
2382
|
+
return finalLocatorsSet.has(key);
|
|
2383
|
+
};
|
|
2384
|
+
if (targetElement) {
|
|
2385
|
+
const idValue = getId(targetElement);
|
|
2386
|
+
if (idValue && !locatorExists("id", idValue)) {
|
|
2387
|
+
record.locators.forEach((loc) => {
|
|
2388
|
+
createLocator(loc, {
|
|
2389
|
+
name: "id",
|
|
2390
|
+
value: idValue,
|
|
2391
|
+
isRecorded: loc.value === idValue && loc.name === "id"
|
|
2392
|
+
? loc.isRecorded
|
|
2393
|
+
: "Y",
|
|
2394
|
+
isSelfHealed: loc.value === idValue && loc.name === "id"
|
|
2395
|
+
? loc.isSelfHealed
|
|
2396
|
+
: "Y",
|
|
2397
|
+
});
|
|
2398
|
+
});
|
|
2399
|
+
}
|
|
2400
|
+
const textValue = getVisibleText(targetElement);
|
|
2401
|
+
if (textValue) {
|
|
2402
|
+
record.locators.forEach((loc) => {
|
|
2403
|
+
createLocator(loc, {
|
|
2404
|
+
name: "linkText",
|
|
2405
|
+
type: "static",
|
|
2406
|
+
value: textValue,
|
|
2407
|
+
isRecorded: loc.value === textValue ? loc.isRecorded : "Y",
|
|
2408
|
+
isSelfHealed: loc.value === textValue ? loc.isSelfHealed : "Y",
|
|
2409
|
+
});
|
|
2410
|
+
});
|
|
2411
|
+
}
|
|
2412
|
+
const nameLocator = getName(targetElement);
|
|
2413
|
+
if (nameLocator && !locatorExists("name", nameLocator)) {
|
|
2414
|
+
record.locators.forEach((loc) => {
|
|
2415
|
+
createLocator(loc, {
|
|
2416
|
+
name: "name",
|
|
2417
|
+
type: "static",
|
|
2418
|
+
value: nameLocator,
|
|
2419
|
+
isRecorded: loc.value === nameLocator && loc.name === "name"
|
|
2420
|
+
? loc.isRecorded
|
|
2421
|
+
: "Y",
|
|
2422
|
+
isSelfHealed: loc.value === nameLocator && loc.name === "name"
|
|
2423
|
+
? loc.isSelfHealed
|
|
2424
|
+
: "Y",
|
|
2425
|
+
});
|
|
2426
|
+
});
|
|
2427
|
+
}
|
|
2428
|
+
const classValue = getClassName(targetElement);
|
|
2429
|
+
if (classValue &&
|
|
2430
|
+
classValue.trim() !== "" &&
|
|
2431
|
+
!locatorExists("className", classValue)) {
|
|
2432
|
+
record.locators.forEach((loc) => {
|
|
2433
|
+
createLocator(loc, {
|
|
2434
|
+
name: "className",
|
|
2435
|
+
value: classValue,
|
|
2436
|
+
isRecorded: loc.value === classValue && loc.name === "className"
|
|
2437
|
+
? loc.isRecorded
|
|
2438
|
+
: "Y",
|
|
2439
|
+
isSelfHealed: loc.value === classValue && loc.name === "className"
|
|
2440
|
+
? loc.isSelfHealed
|
|
2441
|
+
: "Y",
|
|
2442
|
+
});
|
|
2443
|
+
});
|
|
2444
|
+
}
|
|
2445
|
+
const fnValue = parseDOM(targetElement, document, false, true);
|
|
2446
|
+
record.locators.forEach((loc) => {
|
|
2447
|
+
createLocator(loc, {
|
|
2448
|
+
name: 'xpath',
|
|
2449
|
+
value: fnValue,
|
|
2450
|
+
isRecorded: fnValue?.find((x) => x.value === loc.value)
|
|
2451
|
+
? loc.isRecorded
|
|
2452
|
+
: "Y",
|
|
2453
|
+
isSelfHealed: fnValue?.find((x) => x.value === loc.value)
|
|
2454
|
+
? loc.isSelfHealed
|
|
2455
|
+
: "Y",
|
|
2456
|
+
});
|
|
2457
|
+
});
|
|
2458
|
+
for (const locator of record.locators) {
|
|
2459
|
+
try {
|
|
2460
|
+
for (const loc of record.locators) {
|
|
2461
|
+
if (!loc.value)
|
|
2462
|
+
continue;
|
|
2463
|
+
for (const relation of relations) {
|
|
2464
|
+
if (loc.value.includes(relation)) {
|
|
2465
|
+
const relativeXpath = checkReferenceElementIsValid(loc.value, relation, tempDiv);
|
|
2466
|
+
if (relativeXpath) {
|
|
2467
|
+
createLocator(loc, {
|
|
2468
|
+
name: "xpath",
|
|
2469
|
+
value: relativeXpath,
|
|
2470
|
+
isRecorded: locator.isRecorded !== "" &&
|
|
2471
|
+
locator.isRecorded !== null
|
|
2472
|
+
? locator.isRecorded
|
|
2473
|
+
: "Y",
|
|
2474
|
+
});
|
|
2475
|
+
break;
|
|
2476
|
+
}
|
|
2477
|
+
}
|
|
2478
|
+
}
|
|
2479
|
+
}
|
|
2480
|
+
}
|
|
2481
|
+
catch (error) {
|
|
2482
|
+
console.error("Error processing locator:", locator, error);
|
|
2483
|
+
}
|
|
2484
|
+
}
|
|
2485
|
+
const finalAutoHealedLocators = finalLocators.map((obj) => ({
|
|
2486
|
+
...obj,
|
|
2487
|
+
value: cleanLocatorValue(obj.value, obj.name, obj.isRecorded),
|
|
2488
|
+
}));
|
|
2489
|
+
const finalUniqueAutoHealedLocators = finalAutoHealedLocators.reduce((unique, locator) => {
|
|
2490
|
+
if (locator.value &&
|
|
2491
|
+
!unique.some((l) => l.value === locator.value)) {
|
|
2492
|
+
unique.push(locator);
|
|
2493
|
+
}
|
|
2494
|
+
return unique;
|
|
2495
|
+
}, []);
|
|
2496
|
+
const jsonResult = [
|
|
2497
|
+
{
|
|
2498
|
+
name: `${record.name}`,
|
|
2499
|
+
desc: `${record.desc}`,
|
|
2500
|
+
type: `${record.type}`,
|
|
2501
|
+
locators: finalUniqueAutoHealedLocators.filter((locator) => locator?.value != null && locator.value !== ""),
|
|
2502
|
+
isShared: `${record.isShared}`,
|
|
2503
|
+
projectId: `${record.projectId}`,
|
|
2504
|
+
projectType: `${record.projectType}`,
|
|
2505
|
+
isRecorded: `${record.isRecorded}`,
|
|
2506
|
+
folder: `${record.folder}`,
|
|
2507
|
+
parentId: `${record.parentId}`,
|
|
2508
|
+
parentName: `${record.parentName}`,
|
|
2509
|
+
platform: `${record.platform}`,
|
|
2510
|
+
licenseId: `${record.licenseId}`,
|
|
2511
|
+
licenseType: `${record.licenseType}`,
|
|
2512
|
+
userId: `${record.userId}`,
|
|
2513
|
+
},
|
|
2514
|
+
];
|
|
2515
|
+
return jsonResult;
|
|
2516
|
+
}
|
|
2517
|
+
}
|
|
2518
|
+
catch (error) {
|
|
2519
|
+
console.error("Error processing locator:", locator, error);
|
|
2520
|
+
continue;
|
|
2521
|
+
}
|
|
2522
|
+
}
|
|
2523
|
+
}
|
|
2524
|
+
catch (error) {
|
|
2525
|
+
console.error("Error processing locator:", locator, error);
|
|
2526
|
+
continue;
|
|
2527
|
+
}
|
|
2528
|
+
}
|
|
2529
|
+
return null;
|
|
2530
|
+
};
|
|
2531
|
+
|
|
2532
|
+
let modifiedElementAttributes = [];
|
|
2533
|
+
const parseCssSelectors = (el, docmt) => {
|
|
2534
|
+
let cssSelector = [];
|
|
2535
|
+
try {
|
|
2536
|
+
let idCssSelector = getIdCssPath(el, docmt);
|
|
2537
|
+
if (idCssSelector &&
|
|
2538
|
+
idCssSelector.includes('#') &&
|
|
2539
|
+
docmt.querySelectorAll(idCssSelector)?.length === 1 &&
|
|
2540
|
+
!(idCssSelector.includes('body') ||
|
|
2541
|
+
idCssSelector.includes('head') ||
|
|
2542
|
+
idCssSelector.includes('html'))) {
|
|
2543
|
+
cssSelector.push({ key: 'cssSelector by id', value: idCssSelector });
|
|
2544
|
+
}
|
|
2545
|
+
else {
|
|
2546
|
+
idCssSelector = '';
|
|
2547
|
+
}
|
|
2548
|
+
let nameCssSelector = getNameCssPath(el, docmt);
|
|
2549
|
+
if (nameCssSelector &&
|
|
2550
|
+
nameCssSelector !== idCssSelector &&
|
|
2551
|
+
nameCssSelector.includes('name') &&
|
|
2552
|
+
docmt.querySelectorAll(nameCssSelector)?.length === 1 &&
|
|
2553
|
+
!(nameCssSelector.includes('body') ||
|
|
2554
|
+
nameCssSelector.includes('head') ||
|
|
2555
|
+
nameCssSelector.includes('html'))) {
|
|
2556
|
+
cssSelector.push({ key: 'cssSelector by name', value: nameCssSelector });
|
|
2557
|
+
}
|
|
2558
|
+
else {
|
|
2559
|
+
nameCssSelector = '';
|
|
2560
|
+
}
|
|
2561
|
+
let classCssSelector = getClassCssPath(el, docmt);
|
|
2562
|
+
if (classCssSelector &&
|
|
2563
|
+
classCssSelector !== idCssSelector &&
|
|
2564
|
+
nameCssSelector !== classCssSelector &&
|
|
2565
|
+
classCssSelector.includes('.') &&
|
|
2566
|
+
docmt.querySelectorAll(classCssSelector)?.length === 1 &&
|
|
2567
|
+
!(classCssSelector.includes('body') ||
|
|
2568
|
+
classCssSelector.includes('head') ||
|
|
2569
|
+
classCssSelector.includes('html'))) {
|
|
2570
|
+
cssSelector.push({
|
|
2571
|
+
key: 'cssSelector by class',
|
|
2572
|
+
value: classCssSelector,
|
|
2573
|
+
});
|
|
2574
|
+
}
|
|
2575
|
+
else {
|
|
2576
|
+
classCssSelector = '';
|
|
2577
|
+
}
|
|
2578
|
+
}
|
|
2579
|
+
catch (error) {
|
|
2580
|
+
console.log(error);
|
|
2581
|
+
}
|
|
2582
|
+
if (!cssSelector.length) {
|
|
2583
|
+
let abSelector = getAbsoluteCssPath(el, docmt);
|
|
2584
|
+
if (abSelector &&
|
|
2585
|
+
docmt.querySelectorAll(abSelector)?.length === 1 &&
|
|
2586
|
+
!(abSelector.includes('body') ||
|
|
2587
|
+
abSelector.includes('head') ||
|
|
2588
|
+
abSelector.includes('html'))) {
|
|
2589
|
+
cssSelector.push({
|
|
2590
|
+
key: 'Absolute cssSelector',
|
|
2591
|
+
value: abSelector,
|
|
2592
|
+
});
|
|
2593
|
+
}
|
|
2594
|
+
else {
|
|
2595
|
+
abSelector = '';
|
|
2596
|
+
}
|
|
2597
|
+
}
|
|
2598
|
+
return cssSelector;
|
|
2599
|
+
};
|
|
2600
|
+
const getIdCssPath = (el, docmt) => {
|
|
2601
|
+
const tagName = el.tagName.toLowerCase();
|
|
2602
|
+
if (docmt?.defaultView?.Element &&
|
|
2603
|
+
!(el instanceof docmt?.defaultView?.Element))
|
|
2604
|
+
return;
|
|
2605
|
+
if (tagName.includes('style') || tagName.includes('script'))
|
|
2606
|
+
return;
|
|
2607
|
+
const path = [];
|
|
2608
|
+
while (el.nodeType === Node.ELEMENT_NODE) {
|
|
2609
|
+
let selector = el.nodeName?.toLowerCase();
|
|
2610
|
+
if (el.id && !isNumberExist(el.id)) {
|
|
2611
|
+
selector += `#${el.id}`;
|
|
2612
|
+
path.unshift(selector);
|
|
2613
|
+
break;
|
|
2614
|
+
}
|
|
2615
|
+
else {
|
|
2616
|
+
let sib = el;
|
|
2617
|
+
let nth = 1;
|
|
2618
|
+
if (sib.previousElementSibling) {
|
|
2619
|
+
while ((sib = sib.previousElementSibling)) {
|
|
2620
|
+
if (sib.nodeName?.toLowerCase() === selector)
|
|
2621
|
+
nth++;
|
|
2622
|
+
}
|
|
2623
|
+
}
|
|
2624
|
+
if (nth !== 1) {
|
|
2625
|
+
selector += `:nth-of-type(${nth})`;
|
|
2626
|
+
}
|
|
2627
|
+
if (nth === 1 && sib?.parentElement?.childElementCount > 1) {
|
|
2628
|
+
selector += `:nth-child(${nth})`;
|
|
2629
|
+
}
|
|
2630
|
+
}
|
|
2631
|
+
path.unshift(selector);
|
|
2632
|
+
el = el.parentElement;
|
|
2633
|
+
}
|
|
2634
|
+
return path.join(' > ');
|
|
2635
|
+
};
|
|
2636
|
+
const getNameCssPath = (el, docmt) => {
|
|
2637
|
+
const tagName = el.tagName.toLowerCase();
|
|
2638
|
+
if (docmt?.defaultView?.Element &&
|
|
2639
|
+
!(el instanceof docmt?.defaultView?.Element))
|
|
2640
|
+
return;
|
|
2641
|
+
if (tagName.includes('style') || tagName.includes('script'))
|
|
2642
|
+
return;
|
|
2643
|
+
const path = [];
|
|
2644
|
+
while (el.nodeType === Node.ELEMENT_NODE) {
|
|
2645
|
+
let selector = el.nodeName?.toLowerCase();
|
|
2646
|
+
let name = el.getAttribute('name');
|
|
2647
|
+
if (name && !isNumberExist(name)) {
|
|
2648
|
+
selector += `[name=${name}]`;
|
|
2649
|
+
path.unshift(selector);
|
|
2650
|
+
break;
|
|
2651
|
+
}
|
|
2652
|
+
else {
|
|
2653
|
+
let sib = el;
|
|
2654
|
+
let nth = 1;
|
|
2655
|
+
if (sib.previousElementSibling) {
|
|
2656
|
+
while ((sib = sib.previousElementSibling)) {
|
|
2657
|
+
if (sib.nodeName?.toLowerCase() === selector)
|
|
2658
|
+
nth++;
|
|
2659
|
+
}
|
|
2660
|
+
}
|
|
2661
|
+
if (nth !== 1) {
|
|
2662
|
+
selector += `:nth-of-type(${nth})`;
|
|
2663
|
+
}
|
|
2664
|
+
if (nth === 1 && sib?.parentElement?.childElementCount > 1) {
|
|
2665
|
+
selector += `:nth-child(${nth})`;
|
|
2666
|
+
}
|
|
2667
|
+
}
|
|
2668
|
+
path.unshift(selector);
|
|
2669
|
+
el = el.parentElement;
|
|
2670
|
+
}
|
|
2671
|
+
return path.join(' > ');
|
|
2672
|
+
};
|
|
2673
|
+
const getClassCssPath = (el, docmt) => {
|
|
2674
|
+
const tagName = el.tagName.toLowerCase();
|
|
2675
|
+
if (docmt?.defaultView?.Element &&
|
|
2676
|
+
!(el instanceof docmt?.defaultView?.Element))
|
|
2677
|
+
return;
|
|
2678
|
+
if (tagName.includes('style') || tagName.includes('script'))
|
|
2679
|
+
return;
|
|
2680
|
+
const path = [];
|
|
2681
|
+
while (el.nodeType === Node.ELEMENT_NODE) {
|
|
2682
|
+
let selector = el.nodeName?.toLowerCase();
|
|
2683
|
+
if (typeof el.className === 'string' &&
|
|
2684
|
+
el.className &&
|
|
2685
|
+
!isNumberExist(el.className) &&
|
|
2686
|
+
!modifiedElementAttributes?.find((x) => x.element === el && x.attributeName === 'class')) {
|
|
2687
|
+
el.classList.remove('marked-element-temp');
|
|
2688
|
+
el.classList.remove('removePointers');
|
|
2689
|
+
if (el.className) {
|
|
2690
|
+
selector += `.${el.className.trim().replace(/\s+/g, '.')}`;
|
|
2691
|
+
path.unshift(selector);
|
|
2692
|
+
break;
|
|
2693
|
+
}
|
|
2694
|
+
}
|
|
2695
|
+
else {
|
|
2696
|
+
let sib = el;
|
|
2697
|
+
let nth = 1;
|
|
2698
|
+
if (sib.previousElementSibling) {
|
|
2699
|
+
while ((sib = sib.previousElementSibling)) {
|
|
2700
|
+
if (sib.nodeName?.toLowerCase() === selector)
|
|
2701
|
+
nth++;
|
|
2702
|
+
}
|
|
2703
|
+
}
|
|
2704
|
+
if (nth !== 1) {
|
|
2705
|
+
selector += `:nth-of-type(${nth})`;
|
|
2706
|
+
}
|
|
2707
|
+
if (nth === 1 && sib?.parentElement?.childElementCount > 1) {
|
|
2708
|
+
selector += `:nth-child(${nth})`;
|
|
2709
|
+
}
|
|
2710
|
+
}
|
|
2711
|
+
path.unshift(selector);
|
|
2712
|
+
el = el.parentElement;
|
|
2713
|
+
}
|
|
2714
|
+
return path.join(' > ');
|
|
2715
|
+
};
|
|
2716
|
+
const getAbsoluteCssPath = (el, docmt) => {
|
|
2717
|
+
const tagName = el.tagName.toLowerCase();
|
|
2718
|
+
if (docmt?.defaultView?.Element &&
|
|
2719
|
+
!(el instanceof docmt?.defaultView?.Element))
|
|
2720
|
+
return;
|
|
2721
|
+
if (tagName.includes('style') || tagName.includes('script'))
|
|
2722
|
+
return;
|
|
2723
|
+
const path = [];
|
|
2724
|
+
while (el.nodeType === Node.ELEMENT_NODE) {
|
|
2725
|
+
let selector = el.nodeName?.toLowerCase();
|
|
2726
|
+
if (tagName.includes('body') ||
|
|
2727
|
+
tagName.includes('head') ||
|
|
2728
|
+
tagName.includes('html')) {
|
|
2729
|
+
selector += tagName;
|
|
2730
|
+
path.unshift(selector);
|
|
2731
|
+
break;
|
|
2732
|
+
}
|
|
2733
|
+
else {
|
|
2734
|
+
let sib = el;
|
|
2735
|
+
let nth = 1;
|
|
2736
|
+
if (sib.previousElementSibling) {
|
|
2737
|
+
while ((sib = sib.previousElementSibling)) {
|
|
2738
|
+
if (sib.nodeName?.toLowerCase() === selector)
|
|
2739
|
+
nth++;
|
|
2740
|
+
}
|
|
2741
|
+
}
|
|
2742
|
+
if (nth !== 1) {
|
|
2743
|
+
selector += `:nth-of-type(${nth})`;
|
|
2744
|
+
}
|
|
2745
|
+
if (nth === 1 && sib?.parentElement?.childElementCount > 1) {
|
|
2746
|
+
selector += `:nth-child(${nth})`;
|
|
2747
|
+
}
|
|
2748
|
+
}
|
|
2749
|
+
path.unshift(selector);
|
|
2750
|
+
el = el.parentElement;
|
|
2751
|
+
}
|
|
2752
|
+
return path.join(' > ');
|
|
2753
|
+
};
|
|
2754
|
+
const cssSelectors = {
|
|
2755
|
+
parseCssSelectors,
|
|
2756
|
+
getIdCssPath,
|
|
2757
|
+
getNameCssPath,
|
|
2758
|
+
getClassCssPath,
|
|
2759
|
+
getAbsoluteCssPath
|
|
2760
|
+
};
|
|
2761
|
+
|
|
2762
|
+
const createXPathAPI = () => ({
|
|
2763
|
+
xpath,
|
|
2764
|
+
referenceXpaths: referenceXpath,
|
|
2765
|
+
xpathUtils,
|
|
2766
|
+
getElementsFromHTML,
|
|
2767
|
+
cssSelectors
|
|
2768
|
+
});
|
|
2769
|
+
|
|
2770
|
+
exports.createXPathAPI = createXPathAPI;
|
|
2771
|
+
exports.default = createXPathAPI;
|
|
2772
|
+
|
|
2773
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
2774
|
+
|
|
2775
|
+
})(this.XpathLib = this.XpathLib || {});
|