ff-dom 1.0.16 → 1.0.18-beta.1
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 +63 -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 -685
- 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 -532
- package/src/index.ts +0 -3
|
@@ -0,0 +1,1299 @@
|
|
|
1
|
+
export const reWhiteSpace = /^[\S]+( [\S]+)*$/gi;
|
|
2
|
+
let cspEnabled: boolean = false;
|
|
3
|
+
const xpathCache: { [x: string]: number } = {};
|
|
4
|
+
let multiElementReferenceMode: boolean = false;
|
|
5
|
+
let relativeXPathCache = new Map();
|
|
6
|
+
export let modifiedElementAttributes: {
|
|
7
|
+
url: string | null;
|
|
8
|
+
attributeName: string | null;
|
|
9
|
+
element: HTMLElement | Element;
|
|
10
|
+
doc: Document;
|
|
11
|
+
}[] = [];
|
|
12
|
+
let mutationObserver: MutationObserver;
|
|
13
|
+
|
|
14
|
+
export const createObserver = (addedNodeCallBack: Function) => {
|
|
15
|
+
mutationObserver = new MutationObserver((mutations) => {
|
|
16
|
+
mutations.forEach((mutation) => {
|
|
17
|
+
if (mutation?.addedNodes?.length) {
|
|
18
|
+
addedNodeCallBack(mutation?.addedNodes);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (mutation.target instanceof HTMLElement) {
|
|
22
|
+
if (
|
|
23
|
+
mutation?.type === "attributes" &&
|
|
24
|
+
mutation.attributeName === "class" &&
|
|
25
|
+
((isSvg(mutation.target) &&
|
|
26
|
+
mutation.oldValue?.trim() ===
|
|
27
|
+
mutation.target.classList.value?.trim()) ||
|
|
28
|
+
mutation.target.classList?.value
|
|
29
|
+
?.replace(mutation.oldValue!, "")
|
|
30
|
+
.trim() === "marked-element-temp" ||
|
|
31
|
+
mutation?.oldValue
|
|
32
|
+
?.replace(mutation.target.classList?.value, "")
|
|
33
|
+
.trim() === "marked-element-temp")
|
|
34
|
+
) {
|
|
35
|
+
} else if (
|
|
36
|
+
mutation?.type === "attributes" &&
|
|
37
|
+
!["flndisabled"].includes(mutation.attributeName!)
|
|
38
|
+
) {
|
|
39
|
+
modifiedElementAttributes.push({
|
|
40
|
+
url: mutation.target.baseURI,
|
|
41
|
+
attributeName: mutation.attributeName,
|
|
42
|
+
element: mutation.target,
|
|
43
|
+
doc: mutation.target.ownerDocument,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
};
|
|
50
|
+
export const startObserver = (
|
|
51
|
+
target: Node,
|
|
52
|
+
options: MutationObserverInit | undefined
|
|
53
|
+
) => {
|
|
54
|
+
mutationObserver?.observe(target, options);
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export const stopObserver = () => {
|
|
58
|
+
mutationObserver?.disconnect();
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export const isNumberExist = (str: string): boolean => {
|
|
62
|
+
const hasNumber = /\d/;
|
|
63
|
+
return hasNumber.test(str);
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export const buildPattern = (
|
|
67
|
+
pattern: string,
|
|
68
|
+
isSvg: boolean,
|
|
69
|
+
tagName: string
|
|
70
|
+
) => {
|
|
71
|
+
return isSvg
|
|
72
|
+
? `//*[local-name()='${tagName}' and ${pattern}]`
|
|
73
|
+
: `//${tagName}[${pattern}]`;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export const getTextContent = (
|
|
77
|
+
targetElement: HTMLElement | Element
|
|
78
|
+
): string => {
|
|
79
|
+
const textContent = targetElement?.textContent;
|
|
80
|
+
if (cspEnabled) {
|
|
81
|
+
if (textContent) {
|
|
82
|
+
const tooltip = document.querySelector(".flntooltip") as HTMLElement;
|
|
83
|
+
if (tooltip) {
|
|
84
|
+
const lastIndex = textContent.lastIndexOf(tooltip.innerText);
|
|
85
|
+
if (
|
|
86
|
+
lastIndex &&
|
|
87
|
+
textContent.length === lastIndex + tooltip.innerText.length
|
|
88
|
+
) {
|
|
89
|
+
return textContent.substring(0, lastIndex);
|
|
90
|
+
}
|
|
91
|
+
} else {
|
|
92
|
+
return textContent;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
} else {
|
|
96
|
+
return textContent;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return "";
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
export const getFilteredText = (element: Node) => {
|
|
103
|
+
return element?.childNodes[0]?.nodeValue || "";
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
export const getCountOfXPath = (
|
|
107
|
+
xpath: string,
|
|
108
|
+
element: HTMLElement | Element,
|
|
109
|
+
docmt: Node
|
|
110
|
+
) => {
|
|
111
|
+
try {
|
|
112
|
+
let count;
|
|
113
|
+
// Check if result is cached
|
|
114
|
+
if (xpathCache[xpath] !== undefined) {
|
|
115
|
+
count = xpathCache[xpath];
|
|
116
|
+
} else {
|
|
117
|
+
const owner =
|
|
118
|
+
docmt.nodeType === 9 // DOCUMENT_NODE
|
|
119
|
+
? (docmt as Document)
|
|
120
|
+
: docmt.ownerDocument!;
|
|
121
|
+
|
|
122
|
+
count = owner.evaluate(
|
|
123
|
+
`count(${xpath})`,
|
|
124
|
+
docmt,
|
|
125
|
+
null,
|
|
126
|
+
XPathResult.NUMBER_TYPE,
|
|
127
|
+
null
|
|
128
|
+
).numberValue;
|
|
129
|
+
xpathCache[xpath] = count;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (multiElementReferenceMode && Array.isArray(element)) {
|
|
133
|
+
if (count > 1) {
|
|
134
|
+
const elementsFromXpath = getElementFromXpath(xpath, docmt, true);
|
|
135
|
+
let nodex,
|
|
136
|
+
matchedCount = 0;
|
|
137
|
+
if (elementsFromXpath instanceof XPathResult) {
|
|
138
|
+
while ((nodex = elementsFromXpath?.iterateNext())) {
|
|
139
|
+
if (element.includes(nodex)) {
|
|
140
|
+
matchedCount += 1;
|
|
141
|
+
|
|
142
|
+
if (matchedCount === 2) break;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (matchedCount !== 2) return 0;
|
|
148
|
+
}
|
|
149
|
+
} else {
|
|
150
|
+
// Short-circuit if we only need to match one element
|
|
151
|
+
if (count === 1) {
|
|
152
|
+
const elementFromXpath = getElementFromXpath(xpath, docmt);
|
|
153
|
+
return elementFromXpath === element ? count : 0; // Return 0 if no match
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return count; // Return the count
|
|
157
|
+
} catch (error) {
|
|
158
|
+
console.error(`Error evaluating XPath: ${xpath}`, error);
|
|
159
|
+
return 0; // Return 0 on error to avoid re-processing
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
export const escapeCharacters = (text: string) => {
|
|
164
|
+
if (text) {
|
|
165
|
+
if (!(text.indexOf('"') === -1)) {
|
|
166
|
+
return `'${text}'`;
|
|
167
|
+
}
|
|
168
|
+
if (!(text.indexOf("'") === -1)) {
|
|
169
|
+
return `"${text}"`;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return `'${text}'`;
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
export const removeParenthesis = (xpath: string) => {
|
|
176
|
+
const charArr = xpath.split("");
|
|
177
|
+
|
|
178
|
+
let count = charArr.length;
|
|
179
|
+
const indexArray = [];
|
|
180
|
+
|
|
181
|
+
while (charArr[count - 2] !== "[") {
|
|
182
|
+
indexArray.push(charArr[count - 2]);
|
|
183
|
+
count--;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
indexArray.reverse();
|
|
187
|
+
let finalStr = "";
|
|
188
|
+
for (let i = 0; i < indexArray.length; i++) {
|
|
189
|
+
finalStr += indexArray[i];
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const endBracketLength = finalStr.length + 2;
|
|
193
|
+
let firstpart = xpath.slice(0, xpath.length - endBracketLength);
|
|
194
|
+
|
|
195
|
+
if (firstpart.startsWith("(") && firstpart.endsWith(")")) {
|
|
196
|
+
firstpart = firstpart.slice(1, -1);
|
|
197
|
+
} else {
|
|
198
|
+
firstpart = xpath;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return firstpart;
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
export const findXpathWithIndex = (
|
|
205
|
+
val: string,
|
|
206
|
+
node: HTMLElement | Element,
|
|
207
|
+
docmt: Node,
|
|
208
|
+
count: number
|
|
209
|
+
) => {
|
|
210
|
+
try {
|
|
211
|
+
const owner =
|
|
212
|
+
docmt.nodeType === 9 // DOCUMENT_NODE
|
|
213
|
+
? (docmt as Document)
|
|
214
|
+
: docmt.ownerDocument!;
|
|
215
|
+
|
|
216
|
+
let index = 0;
|
|
217
|
+
if (count) {
|
|
218
|
+
if (getCountOfXPath(`${val}[${count}]`, node, docmt) === 1) {
|
|
219
|
+
return `${val}[${count}]`;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (getCountOfXPath(`(${val})[${count}]`, node, docmt) === 1) {
|
|
223
|
+
return `(${val})[${count}]`;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const nodes = owner.evaluate(val, docmt, null, XPathResult.ANY_TYPE, null);
|
|
228
|
+
let nodex;
|
|
229
|
+
while ((nodex = nodes.iterateNext())) {
|
|
230
|
+
index++;
|
|
231
|
+
if (nodex.isSameNode(node)) {
|
|
232
|
+
if (getCountOfXPath(`${val}[${index}]`, node, docmt) === 1) {
|
|
233
|
+
return `${val}[${index}]`;
|
|
234
|
+
}
|
|
235
|
+
if (getCountOfXPath(`(${val})[${index}]`, node, docmt) === 1) {
|
|
236
|
+
return `(${val})[${index}]`;
|
|
237
|
+
}
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
} catch (error) {
|
|
242
|
+
console.log(error);
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
const deleteLineGap = (a: string) => {
|
|
247
|
+
a &&= a.split("\n")[0].length > 0 ? a.split("\n")[0] : a.split("\n")[1];
|
|
248
|
+
return a;
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
const deleteGarbageFromInnerText = (a: string) => {
|
|
252
|
+
a = deleteLineGap(a);
|
|
253
|
+
a = a
|
|
254
|
+
.split(/[^\u0000-\u00ff]/)
|
|
255
|
+
.reduce((b, c) => {
|
|
256
|
+
return b.length > c.length ? b : c;
|
|
257
|
+
}, "")
|
|
258
|
+
.trim();
|
|
259
|
+
return (a = a.split("/")[0].trim());
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
export const replaceWhiteSpaces = (str: string) => {
|
|
263
|
+
if (str) {
|
|
264
|
+
return str.replace(/\s\s+/g, " ").trim();
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return str;
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
export const getShadowRoot = (a: HTMLElement | Element) => {
|
|
271
|
+
for (a = a && a.parentElement!; a; ) {
|
|
272
|
+
if (a.toString() === "[object ShadowRoot]") return a;
|
|
273
|
+
a = a.parentElement!;
|
|
274
|
+
}
|
|
275
|
+
return null;
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
export const checkBlockedAttributes = (
|
|
279
|
+
attribute: {
|
|
280
|
+
name: string;
|
|
281
|
+
value: string;
|
|
282
|
+
},
|
|
283
|
+
targetElement: HTMLElement | Element,
|
|
284
|
+
isTarget: boolean
|
|
285
|
+
) => {
|
|
286
|
+
if (
|
|
287
|
+
!attribute?.value ||
|
|
288
|
+
typeof attribute?.value === "boolean" ||
|
|
289
|
+
["true", "false", "on", "off", "flntooltip"].some(
|
|
290
|
+
(x) => x === attribute.value
|
|
291
|
+
) ||
|
|
292
|
+
["type", "style", "locator-data-tooltip", "value"].some(
|
|
293
|
+
(x) => x === attribute.name
|
|
294
|
+
) ||
|
|
295
|
+
modifiedElementAttributes?.find(
|
|
296
|
+
(x) =>
|
|
297
|
+
x.doc === targetElement.ownerDocument &&
|
|
298
|
+
x.element === targetElement &&
|
|
299
|
+
x.attributeName === attribute.name
|
|
300
|
+
) ||
|
|
301
|
+
(attribute?.name?.indexOf("on") === 0 && attribute?.name?.length > 3) ||
|
|
302
|
+
typeof attribute.value === "function" ||
|
|
303
|
+
(isTarget && isNumberExist(attribute.value))
|
|
304
|
+
) {
|
|
305
|
+
return false;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
return true;
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
export const getRelationship = (a: HTMLElement, b: HTMLElement) => {
|
|
312
|
+
let pos = a.compareDocumentPosition(b);
|
|
313
|
+
return pos === 2
|
|
314
|
+
? "preceding"
|
|
315
|
+
: pos === 4
|
|
316
|
+
? "following"
|
|
317
|
+
: pos === 8
|
|
318
|
+
? "ancestor"
|
|
319
|
+
: pos === 16
|
|
320
|
+
? "descendant"
|
|
321
|
+
: pos === 32
|
|
322
|
+
? "self"
|
|
323
|
+
: "";
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
export const findRoot = (node: Node) => {
|
|
327
|
+
while (node && node.parentNode) {
|
|
328
|
+
node = node.parentNode;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
return node;
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
export const isSvg = (element: HTMLElement | Element) => {
|
|
335
|
+
return element instanceof SVGElement;
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
export const replaceTempAttributes = (str: string): string => {
|
|
339
|
+
if (!str) return str;
|
|
340
|
+
|
|
341
|
+
return str.replace(/\b[a-zA-Z_]*disabled\b/gi, "disabled");
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
const getElementFromXpath = (xpath: string, docmt: Node, multi = false) => {
|
|
345
|
+
const owner =
|
|
346
|
+
docmt.nodeType === 9 // DOCUMENT_NODE
|
|
347
|
+
? (docmt as Document)
|
|
348
|
+
: docmt.ownerDocument!;
|
|
349
|
+
|
|
350
|
+
if (multi) {
|
|
351
|
+
return owner.evaluate(xpath, docmt, null, XPathResult.ANY_TYPE, null);
|
|
352
|
+
} else {
|
|
353
|
+
return owner.evaluate(
|
|
354
|
+
xpath,
|
|
355
|
+
docmt,
|
|
356
|
+
null,
|
|
357
|
+
XPathResult.FIRST_ORDERED_NODE_TYPE,
|
|
358
|
+
null
|
|
359
|
+
).singleNodeValue;
|
|
360
|
+
}
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
export const getPropertyXPath = (
|
|
364
|
+
element: HTMLElement | Element,
|
|
365
|
+
docmt: Document,
|
|
366
|
+
prop: string,
|
|
367
|
+
value: string,
|
|
368
|
+
isIndex: boolean,
|
|
369
|
+
isTarget: boolean
|
|
370
|
+
) => {
|
|
371
|
+
if (value) {
|
|
372
|
+
const { tagName } = element;
|
|
373
|
+
let count;
|
|
374
|
+
let combinePattern = "";
|
|
375
|
+
const mergePattern = [];
|
|
376
|
+
let pattern;
|
|
377
|
+
|
|
378
|
+
if (value && (!isTarget || !isNumberExist(value))) {
|
|
379
|
+
if (!reWhiteSpace.test(value)) {
|
|
380
|
+
pattern = buildPattern(
|
|
381
|
+
`normalize-space(${prop})=${escapeCharacters(
|
|
382
|
+
replaceWhiteSpaces(value)
|
|
383
|
+
).trim()}`,
|
|
384
|
+
isSvg(element),
|
|
385
|
+
element.tagName.toLowerCase()
|
|
386
|
+
);
|
|
387
|
+
} else {
|
|
388
|
+
pattern = `//${tagName}[${prop}=${escapeCharacters(value)}]`;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
count = getCountOfXPath(pattern, element, docmt);
|
|
392
|
+
|
|
393
|
+
if (count === 1 && !isIndex) {
|
|
394
|
+
return pattern;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
if (value && isTarget) {
|
|
399
|
+
const splitText = value.split(" ");
|
|
400
|
+
if (splitText?.length) {
|
|
401
|
+
if (splitText.length === 1) {
|
|
402
|
+
const contentRes = [...new Set(splitText[0].match(/([^0-9]+)/g))];
|
|
403
|
+
if (contentRes?.length >= 1) {
|
|
404
|
+
if (
|
|
405
|
+
contentRes[0] &&
|
|
406
|
+
replaceWhiteSpaces(contentRes[0].trim())?.length > 1
|
|
407
|
+
) {
|
|
408
|
+
if (value.startsWith(contentRes[0])) {
|
|
409
|
+
if (!reWhiteSpace.test(contentRes[0])) {
|
|
410
|
+
combinePattern = `starts-with(${prop},${escapeCharacters(
|
|
411
|
+
replaceWhiteSpaces(contentRes[0])
|
|
412
|
+
).trim()})`;
|
|
413
|
+
} else {
|
|
414
|
+
combinePattern = `starts-with(${prop},${escapeCharacters(
|
|
415
|
+
contentRes[0]
|
|
416
|
+
).trim()})`;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
if (contentRes?.length > 1) {
|
|
423
|
+
if (
|
|
424
|
+
contentRes[contentRes.length - 1] &&
|
|
425
|
+
replaceWhiteSpaces(contentRes[contentRes.length - 1].trim())
|
|
426
|
+
?.length > 1
|
|
427
|
+
) {
|
|
428
|
+
if (value.endsWith(contentRes[contentRes.length - 1])) {
|
|
429
|
+
if (!reWhiteSpace.test(contentRes[contentRes.length - 1])) {
|
|
430
|
+
combinePattern = `ends-with(${prop},${escapeCharacters(
|
|
431
|
+
replaceWhiteSpaces(contentRes[contentRes.length - 1])
|
|
432
|
+
).trim()})`;
|
|
433
|
+
} else {
|
|
434
|
+
combinePattern = `ends-with(${prop},${escapeCharacters(
|
|
435
|
+
contentRes[contentRes.length - 1]
|
|
436
|
+
).trim()})`;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
if (combinePattern?.length) {
|
|
443
|
+
if (isSvg(element)) {
|
|
444
|
+
pattern = `//*[local-name()='${tagName}' and ${combinePattern}]`;
|
|
445
|
+
} else {
|
|
446
|
+
pattern = `//${tagName}[${combinePattern}]`;
|
|
447
|
+
}
|
|
448
|
+
count = getCountOfXPath(pattern, element, docmt);
|
|
449
|
+
if (count === 1 && !isIndex) {
|
|
450
|
+
return pattern;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
} else {
|
|
454
|
+
const endIndex =
|
|
455
|
+
splitText.length % 2 === 0
|
|
456
|
+
? splitText.length / 2
|
|
457
|
+
: splitText.length % 2;
|
|
458
|
+
const startIndexString = splitText.slice(0, endIndex).join(" ");
|
|
459
|
+
let contentRes = [...new Set(startIndexString.match(/([^0-9]+)/g))];
|
|
460
|
+
if (contentRes?.length) {
|
|
461
|
+
if (
|
|
462
|
+
contentRes[0] &&
|
|
463
|
+
replaceWhiteSpaces(contentRes[0].trim())?.length
|
|
464
|
+
) {
|
|
465
|
+
if (value.startsWith(contentRes[0])) {
|
|
466
|
+
if (!reWhiteSpace.test(contentRes[0])) {
|
|
467
|
+
combinePattern = `starts-with(${prop},${escapeCharacters(
|
|
468
|
+
replaceWhiteSpaces(contentRes[0])
|
|
469
|
+
).trim()})`;
|
|
470
|
+
} else {
|
|
471
|
+
combinePattern = `starts-with(${prop},${escapeCharacters(
|
|
472
|
+
contentRes[0]
|
|
473
|
+
).trim()})`;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
if (combinePattern?.length) {
|
|
480
|
+
if (isSvg(element)) {
|
|
481
|
+
pattern = `//*[local-name()='${tagName}' and ${combinePattern}]`;
|
|
482
|
+
} else {
|
|
483
|
+
pattern = `//${tagName}[${combinePattern}]`;
|
|
484
|
+
}
|
|
485
|
+
count = getCountOfXPath(pattern, element, docmt);
|
|
486
|
+
if (count === 1 && !isIndex) {
|
|
487
|
+
return pattern;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
const endIndexString = splitText
|
|
492
|
+
.slice(endIndex, splitText.length - 1)
|
|
493
|
+
.join(" ");
|
|
494
|
+
contentRes = [...new Set(endIndexString.match(/([^0-9]+)/g))];
|
|
495
|
+
if (contentRes?.length) {
|
|
496
|
+
if (
|
|
497
|
+
contentRes[0] &&
|
|
498
|
+
replaceWhiteSpaces(contentRes[0].trim())?.length > 3
|
|
499
|
+
) {
|
|
500
|
+
if (value.endsWith(contentRes[0])) {
|
|
501
|
+
if (!reWhiteSpace.test(contentRes[0])) {
|
|
502
|
+
combinePattern = `ends-with(${prop},${escapeCharacters(
|
|
503
|
+
replaceWhiteSpaces(contentRes[0])
|
|
504
|
+
).trim()})`;
|
|
505
|
+
} else {
|
|
506
|
+
combinePattern = `ends-with(${prop},${escapeCharacters(
|
|
507
|
+
contentRes[0]
|
|
508
|
+
).trim()})`;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
if (combinePattern?.length) {
|
|
515
|
+
if (isSvg(element)) {
|
|
516
|
+
pattern = `//*[local-name()='${tagName}' and ${combinePattern}]`;
|
|
517
|
+
} else {
|
|
518
|
+
pattern = `//${tagName}[${combinePattern}]`;
|
|
519
|
+
}
|
|
520
|
+
count = getCountOfXPath(pattern, element, docmt);
|
|
521
|
+
if (count === 1 && !isIndex) {
|
|
522
|
+
return pattern;
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
if (value && isTarget && isNumberExist(value)) {
|
|
530
|
+
const contentRes = [...new Set(value.match(/([^0-9]+)/g))];
|
|
531
|
+
if (contentRes?.length) {
|
|
532
|
+
for (let i = 0; i < contentRes?.length; i++) {
|
|
533
|
+
if (
|
|
534
|
+
contentRes[i] &&
|
|
535
|
+
replaceWhiteSpaces(contentRes[i].trim())?.length > 1
|
|
536
|
+
) {
|
|
537
|
+
if (!reWhiteSpace.test(contentRes[i])) {
|
|
538
|
+
mergePattern.push(
|
|
539
|
+
`contains(${prop},${escapeCharacters(
|
|
540
|
+
replaceWhiteSpaces(contentRes[i])
|
|
541
|
+
).trim()})`
|
|
542
|
+
);
|
|
543
|
+
} else {
|
|
544
|
+
mergePattern.push(
|
|
545
|
+
`contains(${prop},${escapeCharacters(
|
|
546
|
+
contentRes[i].trim()
|
|
547
|
+
).trim()})`
|
|
548
|
+
);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
if (mergePattern?.length) {
|
|
555
|
+
if (isSvg(element)) {
|
|
556
|
+
pattern = `//*[local-name()='${tagName}' and ${mergePattern.join(
|
|
557
|
+
" and "
|
|
558
|
+
)}]`;
|
|
559
|
+
} else {
|
|
560
|
+
pattern = `//${tagName}[${mergePattern.join(" and ")}]`;
|
|
561
|
+
}
|
|
562
|
+
count = getCountOfXPath(pattern, element, docmt);
|
|
563
|
+
if (count === 1 && !isIndex) {
|
|
564
|
+
return pattern;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
if (isSvg(element)) {
|
|
570
|
+
pattern = `//*[local-name()='${tagName}' and text()]`;
|
|
571
|
+
} else {
|
|
572
|
+
pattern = `//${tagName}[text()]`;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
count = getCountOfXPath(pattern, element, docmt);
|
|
576
|
+
if (count === 1 && !isIndex) {
|
|
577
|
+
return pattern;
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
};
|
|
581
|
+
|
|
582
|
+
export const getAbsoluteXPath = (
|
|
583
|
+
domNode: HTMLElement | Element | null,
|
|
584
|
+
docmt: Document
|
|
585
|
+
): string => {
|
|
586
|
+
try {
|
|
587
|
+
if (!domNode) {
|
|
588
|
+
return "";
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
let xpathe = isSvg(domNode)
|
|
592
|
+
? `/*[local-name()='${domNode.tagName}']`
|
|
593
|
+
: `/${domNode.tagName}`;
|
|
594
|
+
|
|
595
|
+
// // If this node has siblings of the same tagName, get the index of this node
|
|
596
|
+
if (domNode.parentElement) {
|
|
597
|
+
// Get the siblings
|
|
598
|
+
const childNodes = Array.prototype.slice
|
|
599
|
+
.call(domNode.parentElement.children, 0)
|
|
600
|
+
.filter(
|
|
601
|
+
(childNode: HTMLElement) => childNode.tagName === domNode.tagName
|
|
602
|
+
);
|
|
603
|
+
|
|
604
|
+
// // If there's more than one sibling, append the index
|
|
605
|
+
if (childNodes.length > 1) {
|
|
606
|
+
const index = childNodes.indexOf(domNode);
|
|
607
|
+
xpathe += `[${index + 1}]`;
|
|
608
|
+
}
|
|
609
|
+
} else if (domNode instanceof HTMLElement) {
|
|
610
|
+
if (domNode.offsetParent) {
|
|
611
|
+
const childNodes = Array.prototype.slice
|
|
612
|
+
.call(domNode.offsetParent.children, 0)
|
|
613
|
+
.filter(
|
|
614
|
+
(childNode: HTMLElement) => childNode.tagName === domNode.tagName
|
|
615
|
+
);
|
|
616
|
+
|
|
617
|
+
// // If there's more than one sibling, append the index
|
|
618
|
+
if (childNodes.length > 1) {
|
|
619
|
+
const index = childNodes.indexOf(domNode);
|
|
620
|
+
xpathe += `[${index + 1}]`;
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
// // Make a recursive call to this nodes parents and prepend it to this xpath
|
|
626
|
+
return getAbsoluteXPath(domNode?.parentElement, docmt) + xpathe;
|
|
627
|
+
} catch (error) {
|
|
628
|
+
// If there's an unexpected exception, abort and don't get an XPath
|
|
629
|
+
console.log("xpath", error);
|
|
630
|
+
|
|
631
|
+
return "";
|
|
632
|
+
}
|
|
633
|
+
};
|
|
634
|
+
|
|
635
|
+
export const getRelativeXPath = (
|
|
636
|
+
domNode: HTMLElement | Element,
|
|
637
|
+
docmt: Document,
|
|
638
|
+
isIndex: boolean,
|
|
639
|
+
isTarget: boolean = false,
|
|
640
|
+
attributesArray: Attr[]
|
|
641
|
+
) => {
|
|
642
|
+
try {
|
|
643
|
+
// Generate a cache key based on the node's identifier, index, and target flag
|
|
644
|
+
// Check if the result for this node is already cached
|
|
645
|
+
if (relativeXPathCache.has(domNode)) {
|
|
646
|
+
return relativeXPathCache.get(domNode);
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
// Initialize an array to hold parts of the XPath
|
|
650
|
+
const xpathParts = [];
|
|
651
|
+
let currentNode = domNode;
|
|
652
|
+
|
|
653
|
+
// Traverse up the DOM tree iteratively instead of using recursion
|
|
654
|
+
while (currentNode) {
|
|
655
|
+
let xpathe: string | undefined = "";
|
|
656
|
+
let hasUniqueAttr = false;
|
|
657
|
+
let attributes = domNode === currentNode ? attributesArray : currentNode.attributes
|
|
658
|
+
|
|
659
|
+
// Loop through attributes to check for unique identifiers
|
|
660
|
+
for (const attrName of attributes) {
|
|
661
|
+
if (checkBlockedAttributes(attrName, currentNode, isTarget)) {
|
|
662
|
+
let attrValue = attrName.nodeValue;
|
|
663
|
+
|
|
664
|
+
// Clean up attribute value
|
|
665
|
+
attrValue = attrValue?.replace("removePointers", "") ?? "";
|
|
666
|
+
|
|
667
|
+
const elementName = attrName.name;
|
|
668
|
+
|
|
669
|
+
// Construct an XPath based on attribute
|
|
670
|
+
xpathe = getXpathString(currentNode, elementName, attrValue);
|
|
671
|
+
|
|
672
|
+
let othersWithAttr: number = 0;
|
|
673
|
+
if (xpathe) {
|
|
674
|
+
othersWithAttr = getCountOfXPath(xpathe, currentNode, docmt);
|
|
675
|
+
if (othersWithAttr === 1) {
|
|
676
|
+
xpathParts.unshift(replaceTempAttributes(xpathe));
|
|
677
|
+
hasUniqueAttr = true;
|
|
678
|
+
break;
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
if (othersWithAttr > 1 && isIndex) {
|
|
683
|
+
xpathe = findXpathWithIndex(
|
|
684
|
+
xpathe,
|
|
685
|
+
currentNode,
|
|
686
|
+
docmt,
|
|
687
|
+
othersWithAttr
|
|
688
|
+
);
|
|
689
|
+
if (xpathe) {
|
|
690
|
+
xpathParts.unshift(replaceTempAttributes(xpathe));
|
|
691
|
+
hasUniqueAttr = true;
|
|
692
|
+
break;
|
|
693
|
+
}
|
|
694
|
+
// return replaceTempAttributes(xpathe);
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
if (currentNode.textContent) {
|
|
700
|
+
if (!isTarget || (isTarget && !isNumberExist(currentNode.textContent))) {
|
|
701
|
+
let reWhiteSpace = new RegExp(/^[\S]+( [\S]+)*$/gi);
|
|
702
|
+
|
|
703
|
+
if (!reWhiteSpace.test(currentNode.textContent)) {
|
|
704
|
+
xpathe =
|
|
705
|
+
isSvg(currentNode)
|
|
706
|
+
? `//*[local-name()='${
|
|
707
|
+
currentNode.tagName
|
|
708
|
+
}' and normalize-space(.)=${escapeCharacters(
|
|
709
|
+
getFilteredText(currentNode)
|
|
710
|
+
)}]`
|
|
711
|
+
: `//${
|
|
712
|
+
currentNode.tagName || "*"
|
|
713
|
+
}[normalize-space(.)=${escapeCharacters(
|
|
714
|
+
getFilteredText(currentNode)
|
|
715
|
+
)}]`;
|
|
716
|
+
} else {
|
|
717
|
+
xpathe =
|
|
718
|
+
isSvg(currentNode)
|
|
719
|
+
? `//*[local-name()='${
|
|
720
|
+
currentNode.tagName
|
|
721
|
+
}' and .=${escapeCharacters(getFilteredText(currentNode))}]`
|
|
722
|
+
: `//${currentNode.tagName || "*"}[.=${escapeCharacters(
|
|
723
|
+
getFilteredText(currentNode)
|
|
724
|
+
)}]`;
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
let othersWithAttr = getCountOfXPath(xpathe, currentNode, docmt);
|
|
728
|
+
if (othersWithAttr === 1) {
|
|
729
|
+
return xpathe;
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
if (othersWithAttr > 1 && isIndex) {
|
|
733
|
+
xpathe = findXpathWithIndex(xpathe, currentNode, docmt, othersWithAttr);
|
|
734
|
+
return xpathe;
|
|
735
|
+
}
|
|
736
|
+
} else {
|
|
737
|
+
let combinePattern: string[] = [];
|
|
738
|
+
const contentRes = [
|
|
739
|
+
...new Set(getFilteredText(currentNode).match(/([^0-9]+)/g)),
|
|
740
|
+
];
|
|
741
|
+
let reWhiteSpace = new RegExp(/^[\S]+( [\S]+)*$/gi);
|
|
742
|
+
if (contentRes?.length) {
|
|
743
|
+
for (let i = 0; i < contentRes?.length; i++) {
|
|
744
|
+
if (
|
|
745
|
+
contentRes[i] &&
|
|
746
|
+
replaceWhiteSpaces((contentRes[i] as string).trim())
|
|
747
|
+
) {
|
|
748
|
+
if (!reWhiteSpace.test(contentRes[i] as string)) {
|
|
749
|
+
combinePattern.push(
|
|
750
|
+
`contains(.,${escapeCharacters(
|
|
751
|
+
replaceWhiteSpaces(contentRes[i])
|
|
752
|
+
).trim()})`
|
|
753
|
+
);
|
|
754
|
+
} else {
|
|
755
|
+
combinePattern.push(
|
|
756
|
+
`contains(.,${escapeCharacters(
|
|
757
|
+
(contentRes[i] as string).trim()
|
|
758
|
+
).trim()})`
|
|
759
|
+
);
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
if (combinePattern?.length) {
|
|
766
|
+
xpathe =
|
|
767
|
+
isSvg(currentNode)
|
|
768
|
+
? `//*[local-name()='${
|
|
769
|
+
currentNode.tagName
|
|
770
|
+
}' and ${combinePattern.join(" and ")}]`
|
|
771
|
+
: `//${currentNode.tagName || "*"}[${combinePattern.join(
|
|
772
|
+
" and "
|
|
773
|
+
)}]`;
|
|
774
|
+
let othersWithAttr = getCountOfXPath(xpathe, currentNode, docmt);
|
|
775
|
+
if (othersWithAttr === 1) {
|
|
776
|
+
return xpathe;
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
if (othersWithAttr > 1 && isIndex) {
|
|
780
|
+
xpathe = findXpathWithIndex(xpathe, currentNode, docmt, othersWithAttr);
|
|
781
|
+
return xpathe;
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
// If no unique attribute was found, construct XPath by tag name
|
|
788
|
+
if (!hasUniqueAttr) {
|
|
789
|
+
let tagBasedXPath = isSvg(currentNode)
|
|
790
|
+
? `/*[local-name()='${currentNode.tagName}']`
|
|
791
|
+
: `/${currentNode.tagName}`;
|
|
792
|
+
|
|
793
|
+
// Handle sibling nodes
|
|
794
|
+
if (currentNode.parentElement) {
|
|
795
|
+
const siblings = Array.from(
|
|
796
|
+
currentNode.parentElement.children
|
|
797
|
+
).filter((childNode) => childNode.tagName === currentNode.tagName);
|
|
798
|
+
|
|
799
|
+
// Append index to distinguish between siblings
|
|
800
|
+
if (siblings.length > 1) {
|
|
801
|
+
const index = siblings.indexOf(currentNode);
|
|
802
|
+
tagBasedXPath += `[${index + 1}]`;
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
// Add the constructed tag-based XPath to the parts array
|
|
807
|
+
xpathParts.unshift(tagBasedXPath);
|
|
808
|
+
} else {
|
|
809
|
+
break;
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
// Move up to the parent node for the next iteration
|
|
813
|
+
currentNode = currentNode.parentElement!;
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
// Combine all parts into the final XPath
|
|
817
|
+
const finalXPath = `${xpathParts.join("")}`;
|
|
818
|
+
|
|
819
|
+
// Cache the final XPath for this node
|
|
820
|
+
relativeXPathCache.set(domNode, finalXPath);
|
|
821
|
+
return finalXPath;
|
|
822
|
+
} catch (error) {
|
|
823
|
+
console.log(error);
|
|
824
|
+
return null;
|
|
825
|
+
}
|
|
826
|
+
};
|
|
827
|
+
|
|
828
|
+
export const getCombinationXpath = (
|
|
829
|
+
attribute: Attr,
|
|
830
|
+
domNode: HTMLElement | Element
|
|
831
|
+
) => {
|
|
832
|
+
const combinePattern = [];
|
|
833
|
+
let pattern: string = "";
|
|
834
|
+
|
|
835
|
+
if (
|
|
836
|
+
attribute &&
|
|
837
|
+
!isNumberExist(attribute.value) &&
|
|
838
|
+
typeof attribute.nodeValue !== "function" // &&
|
|
839
|
+
// !modifiedElementAttributes?.find(
|
|
840
|
+
// (x) => x.element === domNode && x.attributeName === attribute.name
|
|
841
|
+
// )
|
|
842
|
+
) {
|
|
843
|
+
const contentRes = [...new Set(attribute.value.match(/([^0-9]+)/g))];
|
|
844
|
+
if (contentRes?.length) {
|
|
845
|
+
for (let i = 0; i < contentRes?.length; i++) {
|
|
846
|
+
if (
|
|
847
|
+
contentRes[i] &&
|
|
848
|
+
replaceWhiteSpaces(contentRes[i].trim())?.length > 2
|
|
849
|
+
) {
|
|
850
|
+
if (!reWhiteSpace.test(contentRes[i])) {
|
|
851
|
+
combinePattern.push(
|
|
852
|
+
`contains(@${attribute.name},${escapeCharacters(
|
|
853
|
+
replaceWhiteSpaces(contentRes[i])
|
|
854
|
+
).trim()})`
|
|
855
|
+
);
|
|
856
|
+
} else {
|
|
857
|
+
combinePattern.push(
|
|
858
|
+
`contains(@${attribute.name},${escapeCharacters(
|
|
859
|
+
contentRes[i].trim()
|
|
860
|
+
)})`
|
|
861
|
+
);
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
if (combinePattern?.length) {
|
|
868
|
+
pattern = isSvg(domNode)
|
|
869
|
+
? `//*[local-name()='${domNode.tagName}' and ${combinePattern.join(
|
|
870
|
+
" and "
|
|
871
|
+
)}]`
|
|
872
|
+
: `//${domNode.tagName}[${combinePattern.join(" and ")}]`;
|
|
873
|
+
return pattern;
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
};
|
|
877
|
+
|
|
878
|
+
export const getAttributeCombinationXpath = (
|
|
879
|
+
domNode: HTMLElement | Element,
|
|
880
|
+
docmt: Document,
|
|
881
|
+
uniqueAttributes: Attr[],
|
|
882
|
+
isTarget: boolean
|
|
883
|
+
) => {
|
|
884
|
+
try {
|
|
885
|
+
const xpathAttributes = [];
|
|
886
|
+
|
|
887
|
+
if (uniqueAttributes.length > 1) {
|
|
888
|
+
for (const attrName of uniqueAttributes) {
|
|
889
|
+
if (checkBlockedAttributes(attrName, domNode, isTarget)) {
|
|
890
|
+
const attrValue = attrName.nodeValue;
|
|
891
|
+
|
|
892
|
+
if (!reWhiteSpace.test(attrValue!)) {
|
|
893
|
+
xpathAttributes.push(
|
|
894
|
+
`normalize-space(@${attrName.nodeName})="${attrValue}"`
|
|
895
|
+
);
|
|
896
|
+
} else if (attrName.nodeName === "class") {
|
|
897
|
+
xpathAttributes.push(
|
|
898
|
+
`contains(@${attrName.nodeName},"${attrValue}")`
|
|
899
|
+
);
|
|
900
|
+
} else {
|
|
901
|
+
xpathAttributes.push(`@${attrName.nodeName}="${attrValue}"`);
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
const xpathe = isSvg(domNode)
|
|
905
|
+
? `//*[local-name()='${domNode.tagName}' and ${xpathAttributes.join(
|
|
906
|
+
" and "
|
|
907
|
+
)}]`
|
|
908
|
+
: `//${domNode.tagName}[${xpathAttributes.join(" and ")}]`;
|
|
909
|
+
let othersWithAttr;
|
|
910
|
+
|
|
911
|
+
// If the XPath does not parse, move to the next unique attribute
|
|
912
|
+
try {
|
|
913
|
+
othersWithAttr = getCountOfXPath(xpathe, domNode, docmt);
|
|
914
|
+
} catch (ign) {
|
|
915
|
+
continue;
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
if (othersWithAttr === 1 && !xpathe.includes("and")) {
|
|
919
|
+
return "";
|
|
920
|
+
}
|
|
921
|
+
// If the attribute isn't actually unique, get it's index too
|
|
922
|
+
if (othersWithAttr === 1 && xpathe.includes("and")) {
|
|
923
|
+
return xpathe;
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
} catch (error) {
|
|
929
|
+
// If there's an unexpected exception, abort and don't get an XPath
|
|
930
|
+
console.log(`'${JSON.stringify(error, null, 2)}'`);
|
|
931
|
+
}
|
|
932
|
+
};
|
|
933
|
+
|
|
934
|
+
export const intermediateXpathStep = (
|
|
935
|
+
targetElemt: HTMLElement | Element,
|
|
936
|
+
attr: { name: string; value: string },
|
|
937
|
+
isTarget: boolean
|
|
938
|
+
): string => {
|
|
939
|
+
let isSvgElement = isSvg(targetElemt);
|
|
940
|
+
let expression: string = "";
|
|
941
|
+
|
|
942
|
+
if (checkBlockedAttributes(attr, targetElemt, isTarget)) {
|
|
943
|
+
let attrValue = attr.value;
|
|
944
|
+
attrValue = attrValue.replace("removePointers", "");
|
|
945
|
+
const elementName = attr.name;
|
|
946
|
+
|
|
947
|
+
if (!reWhiteSpace.test(attrValue)) {
|
|
948
|
+
expression = isSvgElement
|
|
949
|
+
? `*[local-name()='${
|
|
950
|
+
targetElemt.tagName
|
|
951
|
+
}' and normalize-space(@${elementName})=${escapeCharacters(
|
|
952
|
+
attrValue
|
|
953
|
+
)}]`
|
|
954
|
+
: `${
|
|
955
|
+
targetElemt.tagName || "*"
|
|
956
|
+
}[normalize-space(@${elementName})=${escapeCharacters(attrValue)}]`;
|
|
957
|
+
} else {
|
|
958
|
+
expression = isSvgElement
|
|
959
|
+
? `*[local-name()='${
|
|
960
|
+
targetElemt.tagName
|
|
961
|
+
}' and @${elementName}=${escapeCharacters(attrValue)}]`
|
|
962
|
+
: `${targetElemt.tagName || "*"}[@${elementName}=${escapeCharacters(
|
|
963
|
+
attrValue
|
|
964
|
+
)}]`;
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
return expression;
|
|
969
|
+
};
|
|
970
|
+
|
|
971
|
+
export const getFilteredTextXPath = (
|
|
972
|
+
node: HTMLElement | Element,
|
|
973
|
+
docmt: Document
|
|
974
|
+
) => {
|
|
975
|
+
if (!node.textContent) return "";
|
|
976
|
+
|
|
977
|
+
const filteredText = getFilteredText(node);
|
|
978
|
+
|
|
979
|
+
let xpathe;
|
|
980
|
+
|
|
981
|
+
if (!reWhiteSpace.test(filteredText)) {
|
|
982
|
+
xpathe = isSvg(node)
|
|
983
|
+
? `//*[local-name()='${
|
|
984
|
+
node.tagName
|
|
985
|
+
}' and normalize-space(.)=${escapeCharacters(filteredText)}]`
|
|
986
|
+
: `//${node.tagName || "*"}[normalize-space(.)=${escapeCharacters(
|
|
987
|
+
filteredText
|
|
988
|
+
)}]`;
|
|
989
|
+
} else {
|
|
990
|
+
xpathe = isSvg(node)
|
|
991
|
+
? `//*[local-name()='${node.tagName}' and .=${escapeCharacters(
|
|
992
|
+
filteredText
|
|
993
|
+
)}]`
|
|
994
|
+
: `//${node.tagName || "*"}[.=${escapeCharacters(filteredText)}]`;
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
return xpathe;
|
|
998
|
+
};
|
|
999
|
+
|
|
1000
|
+
export const getTextXpathFunction = (domNode: HTMLElement | Element) => {
|
|
1001
|
+
const trimmedText = getTextContent(domNode)?.trim();
|
|
1002
|
+
const filteredText = trimmedText
|
|
1003
|
+
? escapeCharacters(deleteGarbageFromInnerText(trimmedText))
|
|
1004
|
+
: trimmedText;
|
|
1005
|
+
if (filteredText) {
|
|
1006
|
+
if (filteredText !== `'${trimmedText}'`) {
|
|
1007
|
+
return `contains(.,${filteredText})`;
|
|
1008
|
+
}
|
|
1009
|
+
return `normalize-space(.)='${trimmedText}'`;
|
|
1010
|
+
}
|
|
1011
|
+
};
|
|
1012
|
+
|
|
1013
|
+
export const getXpathString = (
|
|
1014
|
+
node: HTMLElement | Element,
|
|
1015
|
+
attrName: string,
|
|
1016
|
+
attrValue: string
|
|
1017
|
+
) => {
|
|
1018
|
+
const reWhiteSpace = new RegExp(/^[\S]+( [\S]+)*$/gi);
|
|
1019
|
+
let xpathe: string = "";
|
|
1020
|
+
|
|
1021
|
+
if (attrValue) {
|
|
1022
|
+
if (!reWhiteSpace.test(attrValue)) {
|
|
1023
|
+
xpathe = isSvg(node)
|
|
1024
|
+
? `//*[local-name()='${
|
|
1025
|
+
node.tagName
|
|
1026
|
+
}' and contains(@${attrName},${escapeCharacters(attrValue)})]`
|
|
1027
|
+
: `//${node.tagName || "*"}[contains(@${attrName},${escapeCharacters(
|
|
1028
|
+
attrValue
|
|
1029
|
+
)})]`;
|
|
1030
|
+
} else if (attrName === "class") {
|
|
1031
|
+
xpathe = isSvg(node)
|
|
1032
|
+
? `//*[local-name()='${
|
|
1033
|
+
node.tagName
|
|
1034
|
+
}' and contains(@${attrName},${escapeCharacters(attrValue)})]`
|
|
1035
|
+
: `//${node.tagName || "*"}[contains(@${attrName},${escapeCharacters(
|
|
1036
|
+
attrValue
|
|
1037
|
+
)})]`;
|
|
1038
|
+
} else {
|
|
1039
|
+
xpathe = isSvg(node)
|
|
1040
|
+
? `//*[local-name()='${
|
|
1041
|
+
node.tagName
|
|
1042
|
+
}' and @${attrName}=${escapeCharacters(attrValue)}]`
|
|
1043
|
+
: `//${node.tagName || "*"}[@${attrName}=${escapeCharacters(
|
|
1044
|
+
attrValue
|
|
1045
|
+
)}]`;
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
return xpathe;
|
|
1050
|
+
};
|
|
1051
|
+
|
|
1052
|
+
export const replaceActualAttributes = (
|
|
1053
|
+
str: string,
|
|
1054
|
+
element: { attributes: any }
|
|
1055
|
+
) => {
|
|
1056
|
+
if (str) {
|
|
1057
|
+
return str.replace(/\bdisabled\b/gi, "flndisabled");
|
|
1058
|
+
}
|
|
1059
|
+
return str;
|
|
1060
|
+
};
|
|
1061
|
+
|
|
1062
|
+
const addAttributeSplitCombineXpaths = (
|
|
1063
|
+
attributes: NamedNodeMap,
|
|
1064
|
+
targetElemt: HTMLElement | Element,
|
|
1065
|
+
docmt: Document,
|
|
1066
|
+
isTarget: boolean
|
|
1067
|
+
) => {
|
|
1068
|
+
const attributesArray = Array.prototype.slice.call(attributes);
|
|
1069
|
+
const xpaths: { key: string; value: string }[] = [];
|
|
1070
|
+
try {
|
|
1071
|
+
attributesArray.map((element) => {
|
|
1072
|
+
if (checkBlockedAttributes(element, targetElemt, isTarget)) {
|
|
1073
|
+
const xpth = getCombinationXpath(element, targetElemt);
|
|
1074
|
+
if (xpth) {
|
|
1075
|
+
xpaths.push({
|
|
1076
|
+
key: `split xpath by ${element.name}`,
|
|
1077
|
+
value: xpth,
|
|
1078
|
+
});
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
});
|
|
1082
|
+
} catch (error) {
|
|
1083
|
+
console.log(error);
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
return xpaths;
|
|
1087
|
+
};
|
|
1088
|
+
|
|
1089
|
+
export const getReferenceElementsXpath = (
|
|
1090
|
+
domNode: HTMLElement | Element,
|
|
1091
|
+
docmt: Document,
|
|
1092
|
+
isTarget: boolean
|
|
1093
|
+
) => {
|
|
1094
|
+
let nodeXpath1;
|
|
1095
|
+
const xpaths1 = [];
|
|
1096
|
+
if (
|
|
1097
|
+
domNode.textContent &&
|
|
1098
|
+
(!isTarget || (isTarget && !isNumberExist(domNode.textContent)))
|
|
1099
|
+
) {
|
|
1100
|
+
if (!reWhiteSpace.test(domNode.textContent)) {
|
|
1101
|
+
nodeXpath1 = isSvg(domNode)
|
|
1102
|
+
? `*[local-name()='${domNode.tagName}' and ${getTextXpathFunction(
|
|
1103
|
+
domNode
|
|
1104
|
+
)})]`
|
|
1105
|
+
: `${domNode.tagName}[${getTextXpathFunction(domNode)}]`;
|
|
1106
|
+
if (nodeXpath1) {
|
|
1107
|
+
xpaths1.push({ key: "getReferenceElementsXpath", value: nodeXpath1 });
|
|
1108
|
+
}
|
|
1109
|
+
} else {
|
|
1110
|
+
nodeXpath1 = isSvg(domNode)
|
|
1111
|
+
? `*[local-name()='${domNode.tagName}' and .=${escapeCharacters(
|
|
1112
|
+
getTextContent(domNode)
|
|
1113
|
+
)}]`
|
|
1114
|
+
: `${domNode.tagName}[.=${escapeCharacters(getTextContent(domNode))}]`;
|
|
1115
|
+
if (nodeXpath1) {
|
|
1116
|
+
xpaths1.push({ key: "getReferenceElementsXpath", value: nodeXpath1 });
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
if (domNode.attributes) {
|
|
1122
|
+
for (const attrName of domNode.attributes) {
|
|
1123
|
+
if (checkBlockedAttributes(attrName, domNode, isTarget)) {
|
|
1124
|
+
let attrValue = attrName.nodeValue;
|
|
1125
|
+
if (attrValue) {
|
|
1126
|
+
attrValue = attrValue.replace("removePointers", "");
|
|
1127
|
+
const elementName = attrName.name;
|
|
1128
|
+
|
|
1129
|
+
if (elementName === "class") {
|
|
1130
|
+
nodeXpath1 = isSvg(domNode)
|
|
1131
|
+
? `*[local-name()='${
|
|
1132
|
+
domNode.tagName
|
|
1133
|
+
}' and contains(@${elementName},${escapeCharacters(
|
|
1134
|
+
attrValue
|
|
1135
|
+
)})]`
|
|
1136
|
+
: `${domNode.tagName}[contains(@${elementName},${escapeCharacters(
|
|
1137
|
+
attrValue
|
|
1138
|
+
)})]`;
|
|
1139
|
+
} else {
|
|
1140
|
+
nodeXpath1 = isSvg(domNode)
|
|
1141
|
+
? `*[local-name()='${
|
|
1142
|
+
domNode.tagName
|
|
1143
|
+
}' and @${elementName}=${escapeCharacters(attrValue)}]`
|
|
1144
|
+
: `${domNode.tagName}[@${elementName}=${escapeCharacters(
|
|
1145
|
+
attrValue
|
|
1146
|
+
)}]`;
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
if (nodeXpath1) {
|
|
1150
|
+
xpaths1.push({
|
|
1151
|
+
key: "getReferenceElementsXpath",
|
|
1152
|
+
value: nodeXpath1,
|
|
1153
|
+
});
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
if (!xpaths1?.length) {
|
|
1161
|
+
const attributesArray = Array.prototype.slice.call(domNode.attributes);
|
|
1162
|
+
if (attributesArray?.length > 1) {
|
|
1163
|
+
const combinationXpath = getAttributeCombinationXpath(
|
|
1164
|
+
domNode,
|
|
1165
|
+
docmt,
|
|
1166
|
+
Array.prototype.slice.call(domNode.attributes),
|
|
1167
|
+
isTarget
|
|
1168
|
+
);
|
|
1169
|
+
if (combinationXpath) {
|
|
1170
|
+
xpaths1.push({
|
|
1171
|
+
key: "getReferenceElementsXpath",
|
|
1172
|
+
value: combinationXpath,
|
|
1173
|
+
});
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
if (!xpaths1?.length) {
|
|
1179
|
+
const combinePattern = [];
|
|
1180
|
+
let pattern;
|
|
1181
|
+
const tag = isSvg(domNode);
|
|
1182
|
+
if (domNode.textContent && isTarget && isNumberExist(domNode.textContent)) {
|
|
1183
|
+
const contentRes = [...new Set(domNode.textContent.match(/([^0-9]+)/g))];
|
|
1184
|
+
if (contentRes?.length) {
|
|
1185
|
+
for (let i = 0; i < contentRes?.length; i++) {
|
|
1186
|
+
if (
|
|
1187
|
+
contentRes[i] &&
|
|
1188
|
+
replaceWhiteSpaces(contentRes[i].trim())?.length > 1
|
|
1189
|
+
) {
|
|
1190
|
+
if (!reWhiteSpace.test(contentRes[i])) {
|
|
1191
|
+
combinePattern.push(
|
|
1192
|
+
`contains(.,${escapeCharacters(
|
|
1193
|
+
replaceWhiteSpaces(contentRes[i])
|
|
1194
|
+
).trim()})`
|
|
1195
|
+
);
|
|
1196
|
+
} else {
|
|
1197
|
+
combinePattern.push(
|
|
1198
|
+
`contains(.,${escapeCharacters(contentRes[i]).trim()})`
|
|
1199
|
+
);
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
if (combinePattern?.length) {
|
|
1206
|
+
if (tag) {
|
|
1207
|
+
pattern = `//*[local-name()='${tag}' and ${combinePattern.join(
|
|
1208
|
+
" and "
|
|
1209
|
+
)}]`;
|
|
1210
|
+
} else {
|
|
1211
|
+
pattern = `//${tag}[${combinePattern.join(" and ")}]`;
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
if (pattern)
|
|
1215
|
+
xpaths1.push({ key: "getReferenceElementsXpath", value: pattern });
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
if (!xpaths1?.length) {
|
|
1221
|
+
const xpaths = addAttributeSplitCombineXpaths(
|
|
1222
|
+
domNode.attributes,
|
|
1223
|
+
domNode,
|
|
1224
|
+
docmt,
|
|
1225
|
+
isTarget
|
|
1226
|
+
);
|
|
1227
|
+
if (xpaths?.length) {
|
|
1228
|
+
xpaths1.concat(xpaths);
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
return xpaths1;
|
|
1233
|
+
};
|
|
1234
|
+
|
|
1235
|
+
export function parseXml(xmlStr: string) {
|
|
1236
|
+
if (window.DOMParser) {
|
|
1237
|
+
return new window.DOMParser().parseFromString(xmlStr, "text/xml");
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
return null;
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
export const normalizeXPath = (xpath: string): string => {
|
|
1244
|
+
// Replace text() = "value" or text()='value'
|
|
1245
|
+
xpath = xpath.replace(
|
|
1246
|
+
/text\(\)\s*=\s*(['"])(.*?)\1/g,
|
|
1247
|
+
"normalize-space(.)=$1$2$1"
|
|
1248
|
+
);
|
|
1249
|
+
|
|
1250
|
+
// Replace . = "value" or .='value'
|
|
1251
|
+
xpath = xpath.replace(/\.\s*=\s*(['"])(.*?)\1/g, "normalize-space(.)=$1$2$1");
|
|
1252
|
+
|
|
1253
|
+
return xpath;
|
|
1254
|
+
};
|
|
1255
|
+
|
|
1256
|
+
export const findMatchingParenthesis = (
|
|
1257
|
+
text: string,
|
|
1258
|
+
openPos: number
|
|
1259
|
+
): number => {
|
|
1260
|
+
let closePos = openPos;
|
|
1261
|
+
let counter = 1;
|
|
1262
|
+
while (counter > 0) {
|
|
1263
|
+
const c = text[++closePos];
|
|
1264
|
+
if (c == "(") {
|
|
1265
|
+
counter++;
|
|
1266
|
+
} else if (c == ")") {
|
|
1267
|
+
counter--;
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
return closePos;
|
|
1271
|
+
};
|
|
1272
|
+
|
|
1273
|
+
export const xpathUtils = {
|
|
1274
|
+
parseXml,
|
|
1275
|
+
getReferenceElementsXpath,
|
|
1276
|
+
getAbsoluteXPath,
|
|
1277
|
+
getRelativeXPath,
|
|
1278
|
+
getCombinationXpath,
|
|
1279
|
+
getAttributeCombinationXpath,
|
|
1280
|
+
getElementFromXpath,
|
|
1281
|
+
isSvg,
|
|
1282
|
+
findXpathWithIndex,
|
|
1283
|
+
isNumberExist,
|
|
1284
|
+
getTextContent,
|
|
1285
|
+
getCountOfXPath,
|
|
1286
|
+
normalizeXPath,
|
|
1287
|
+
getShadowRoot,
|
|
1288
|
+
escapeCharacters,
|
|
1289
|
+
removeParenthesis,
|
|
1290
|
+
checkBlockedAttributes,
|
|
1291
|
+
getRelationship,
|
|
1292
|
+
findMatchingParenthesis,
|
|
1293
|
+
deleteGarbageFromInnerText,
|
|
1294
|
+
replaceTempAttributes,
|
|
1295
|
+
createObserver,
|
|
1296
|
+
startObserver,
|
|
1297
|
+
stopObserver,
|
|
1298
|
+
modifiedElementAttributes,
|
|
1299
|
+
};
|