ff-dom 1.0.17 → 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 -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,1105 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isNumberExist,
|
|
3
|
+
getCountOfXPath,
|
|
4
|
+
escapeCharacters,
|
|
5
|
+
checkBlockedAttributes,
|
|
6
|
+
replaceWhiteSpaces,
|
|
7
|
+
getTextContent,
|
|
8
|
+
getPropertyXPath,
|
|
9
|
+
findXpathWithIndex,
|
|
10
|
+
getFilteredText,
|
|
11
|
+
intermediateXpathStep,
|
|
12
|
+
getAttributeCombinationXpath,
|
|
13
|
+
getFilteredTextXPath,
|
|
14
|
+
isSvg,
|
|
15
|
+
getXpathString,
|
|
16
|
+
reWhiteSpace,
|
|
17
|
+
modifiedElementAttributes,
|
|
18
|
+
} from "./xpathHelpers.ts";
|
|
19
|
+
|
|
20
|
+
let xpathData: { key: string; value: string }[] = [];
|
|
21
|
+
let xpathDataWithIndex: { key: string; value: string; count: number }[] = [];
|
|
22
|
+
let referenceElementMode: boolean = false;
|
|
23
|
+
let xpathCache = new Map();
|
|
24
|
+
let cache = new Map();
|
|
25
|
+
|
|
26
|
+
const parentXpathCache = new Map(); // Cache for parent XPaths
|
|
27
|
+
|
|
28
|
+
const checkRelativeXpathRelation = (
|
|
29
|
+
nodeXpath1: string,
|
|
30
|
+
nodeXpath2: string,
|
|
31
|
+
targetElemt: HTMLElement | Element,
|
|
32
|
+
docmt: Document,
|
|
33
|
+
isIndex: boolean,
|
|
34
|
+
relationType: string
|
|
35
|
+
) => {
|
|
36
|
+
if (nodeXpath1 && !referenceElementMode) {
|
|
37
|
+
let xpaths;
|
|
38
|
+
|
|
39
|
+
if (relationType === "parent") {
|
|
40
|
+
xpaths = [
|
|
41
|
+
// `${nodeXpath1}/descendant::${nodeXpath2}`,
|
|
42
|
+
`${nodeXpath1}/descendant-or-self::${nodeXpath2}`,
|
|
43
|
+
`${nodeXpath1}/following::${nodeXpath2}`,
|
|
44
|
+
];
|
|
45
|
+
} else {
|
|
46
|
+
xpaths = [
|
|
47
|
+
// `${nodeXpath1}/descendant::${nodeXpath2}`,
|
|
48
|
+
`${nodeXpath1}/ancestor-or-self::${nodeXpath2}`,
|
|
49
|
+
`${nodeXpath1}/preceding::${nodeXpath2}`,
|
|
50
|
+
];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Iterate through XPath patterns
|
|
54
|
+
for (const xpath of xpaths) {
|
|
55
|
+
// Check if result is already cached to avoid recomputation
|
|
56
|
+
if (!xpathCache?.get(xpath)) {
|
|
57
|
+
// Compute and store result in cache
|
|
58
|
+
xpathCache.set(xpath, getCountOfXPath(xpath, targetElemt, docmt));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const count = xpathCache?.get(xpath);
|
|
62
|
+
|
|
63
|
+
// Short-circuit: Return the first valid XPath result found
|
|
64
|
+
if (count === 1) {
|
|
65
|
+
return xpath;
|
|
66
|
+
}
|
|
67
|
+
if (count > 1) {
|
|
68
|
+
if (xpathDataWithIndex.length) {
|
|
69
|
+
if (count < xpathDataWithIndex[0].count) {
|
|
70
|
+
xpathDataWithIndex.pop();
|
|
71
|
+
xpathDataWithIndex.push({
|
|
72
|
+
key: `relative xpath by unique parent ${isIndex ? "index" : ""}`,
|
|
73
|
+
value: xpath,
|
|
74
|
+
count,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
} else {
|
|
78
|
+
xpathDataWithIndex.push({
|
|
79
|
+
key: `relative xpath by unique parent ${isIndex ? "index" : ""}`,
|
|
80
|
+
value: xpath,
|
|
81
|
+
count,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (count > 1 && isIndex && !xpathData.length) {
|
|
87
|
+
// Try finding XPath with index if count is greater than 1
|
|
88
|
+
const indexedXpath = findXpathWithIndex(
|
|
89
|
+
xpath,
|
|
90
|
+
targetElemt,
|
|
91
|
+
docmt,
|
|
92
|
+
count
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
// Cache the indexed XPath result
|
|
96
|
+
if (
|
|
97
|
+
indexedXpath &&
|
|
98
|
+
getCountOfXPath(indexedXpath, targetElemt, docmt) === 1
|
|
99
|
+
) {
|
|
100
|
+
xpathData.push({
|
|
101
|
+
key: `relative xpath by unique parent ${isIndex ? "index" : ""}`,
|
|
102
|
+
value: indexedXpath,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return null;
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const getUniqueParentXpath = (
|
|
112
|
+
domNode: HTMLElement,
|
|
113
|
+
docmt: Document,
|
|
114
|
+
node: HTMLElement | Element,
|
|
115
|
+
isTarget: boolean,
|
|
116
|
+
nodeXpath: string,
|
|
117
|
+
isIndex: boolean
|
|
118
|
+
) => {
|
|
119
|
+
try {
|
|
120
|
+
if (parentXpathCache.has(domNode)) {
|
|
121
|
+
return parentXpathCache.get(domNode);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Direct XPath construction without loops
|
|
125
|
+
const xpathParts = [];
|
|
126
|
+
let currentNode = domNode;
|
|
127
|
+
|
|
128
|
+
while (currentNode && currentNode.nodeType === 1) {
|
|
129
|
+
const hasUniqueAttr = false;
|
|
130
|
+
for (const attrName of currentNode.attributes) {
|
|
131
|
+
if (checkBlockedAttributes(attrName, currentNode, isTarget)) {
|
|
132
|
+
let attrValue = attrName.nodeValue;
|
|
133
|
+
|
|
134
|
+
attrValue = attrValue?.replace("removePointers", "") ?? "";
|
|
135
|
+
const elementName = attrName.name;
|
|
136
|
+
const xpathe = getXpathString(currentNode, elementName, attrValue);
|
|
137
|
+
|
|
138
|
+
let othersWithAttr;
|
|
139
|
+
|
|
140
|
+
// If the XPath does not parse, move to the next unique attribute
|
|
141
|
+
try {
|
|
142
|
+
othersWithAttr = checkRelativeXpathRelation(
|
|
143
|
+
xpathe,
|
|
144
|
+
nodeXpath,
|
|
145
|
+
node,
|
|
146
|
+
docmt,
|
|
147
|
+
isIndex,
|
|
148
|
+
"parent"
|
|
149
|
+
);
|
|
150
|
+
} catch (ign) {
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// If the attribute isn't actually unique, get it's index too
|
|
155
|
+
if (othersWithAttr) {
|
|
156
|
+
return othersWithAttr;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (currentNode.textContent && !currentNode.textContent) {
|
|
162
|
+
if (
|
|
163
|
+
!isTarget ||
|
|
164
|
+
(isTarget && !isNumberExist(currentNode.textContent))
|
|
165
|
+
) {
|
|
166
|
+
let xpathe;
|
|
167
|
+
|
|
168
|
+
if (!reWhiteSpace.test(currentNode.textContent)) {
|
|
169
|
+
xpathe = isSvg(currentNode)
|
|
170
|
+
? `//*[local-name()='${
|
|
171
|
+
currentNode.tagName
|
|
172
|
+
}' and normalize-space(.)=${escapeCharacters(
|
|
173
|
+
getFilteredText(currentNode)
|
|
174
|
+
)}]`
|
|
175
|
+
: `//${
|
|
176
|
+
currentNode.tagName || "*"
|
|
177
|
+
}[normalize-space(.)=${escapeCharacters(
|
|
178
|
+
getFilteredText(currentNode)
|
|
179
|
+
)}]`;
|
|
180
|
+
} else {
|
|
181
|
+
xpathe = isSvg(currentNode)
|
|
182
|
+
? `//*[local-name()='${
|
|
183
|
+
currentNode.tagName
|
|
184
|
+
}' and .=${escapeCharacters(getFilteredText(currentNode))}]`
|
|
185
|
+
: `//${currentNode.tagName || "*"}[.=${escapeCharacters(
|
|
186
|
+
getFilteredText(currentNode)
|
|
187
|
+
)}]`;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const othersWithAttr = checkRelativeXpathRelation(
|
|
191
|
+
xpathe,
|
|
192
|
+
nodeXpath,
|
|
193
|
+
node,
|
|
194
|
+
docmt,
|
|
195
|
+
isIndex,
|
|
196
|
+
"parent"
|
|
197
|
+
);
|
|
198
|
+
if (othersWithAttr) {
|
|
199
|
+
return othersWithAttr;
|
|
200
|
+
}
|
|
201
|
+
} else {
|
|
202
|
+
const combinePattern = [];
|
|
203
|
+
const contentRes = [
|
|
204
|
+
...new Set(getFilteredText(currentNode).match(/([^0-9]+)/g)),
|
|
205
|
+
];
|
|
206
|
+
const reWhiteSpace = new RegExp(/^[\S]+( [\S]+)*$/gi);
|
|
207
|
+
if (contentRes?.length) {
|
|
208
|
+
for (let i = 0; i < contentRes?.length; i++) {
|
|
209
|
+
if (contentRes[i] && replaceWhiteSpaces(contentRes[i].trim())) {
|
|
210
|
+
if (!reWhiteSpace.test(contentRes[i])) {
|
|
211
|
+
combinePattern.push(
|
|
212
|
+
`contains(.,${escapeCharacters(
|
|
213
|
+
replaceWhiteSpaces(contentRes[i])
|
|
214
|
+
).trim()})`
|
|
215
|
+
);
|
|
216
|
+
} else {
|
|
217
|
+
combinePattern.push(
|
|
218
|
+
`contains(.,${escapeCharacters(
|
|
219
|
+
contentRes[i].trim()
|
|
220
|
+
).trim()})`
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (combinePattern?.length) {
|
|
228
|
+
const xpathe = isSvg(currentNode)
|
|
229
|
+
? `//*[local-name()='${
|
|
230
|
+
currentNode.tagName
|
|
231
|
+
}' and ${combinePattern.join(" and ")}]`
|
|
232
|
+
: `//${currentNode.tagName || "*"}[${combinePattern.join(
|
|
233
|
+
" and "
|
|
234
|
+
)}]`;
|
|
235
|
+
const othersWithAttr = checkRelativeXpathRelation(
|
|
236
|
+
xpathe,
|
|
237
|
+
nodeXpath,
|
|
238
|
+
node,
|
|
239
|
+
docmt,
|
|
240
|
+
isIndex,
|
|
241
|
+
"parent"
|
|
242
|
+
);
|
|
243
|
+
if (othersWithAttr) {
|
|
244
|
+
return othersWithAttr;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Construct the XPath based on the tag name
|
|
251
|
+
if (!hasUniqueAttr) {
|
|
252
|
+
const xpathe = isSvg(currentNode)
|
|
253
|
+
? `/*[local-name()='${currentNode.tagName}']`
|
|
254
|
+
: `/${currentNode.tagName}`;
|
|
255
|
+
|
|
256
|
+
xpathParts.unshift(xpathe);
|
|
257
|
+
} else {
|
|
258
|
+
break;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Move to the parent node for the next iteration
|
|
262
|
+
currentNode = currentNode.parentElement!;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Final constructed XPath
|
|
266
|
+
const finalXPath = xpathParts.join("");
|
|
267
|
+
let count = getCountOfXPath(finalXPath, domNode, docmt);
|
|
268
|
+
if (count === 1) {
|
|
269
|
+
parentXpathCache.set(domNode, finalXPath); // Cache final result
|
|
270
|
+
return finalXPath;
|
|
271
|
+
}
|
|
272
|
+
} catch (error) {
|
|
273
|
+
console.error(error);
|
|
274
|
+
return null;
|
|
275
|
+
}
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
const getParentRelativeXpath = (
|
|
279
|
+
domNode: HTMLElement,
|
|
280
|
+
docmt: Document,
|
|
281
|
+
node: HTMLElement | Element,
|
|
282
|
+
isTarget: boolean
|
|
283
|
+
) => {
|
|
284
|
+
const cache = new Map(); // Cache to store computed results
|
|
285
|
+
|
|
286
|
+
if (cache.has(domNode)) {
|
|
287
|
+
return cache.get(domNode); // Return cached result if available
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const xpathParts = []; // Initialize an array to hold parts of the XPath
|
|
291
|
+
let currentNode = domNode; // Start with the provided DOM node
|
|
292
|
+
|
|
293
|
+
try {
|
|
294
|
+
while (currentNode && currentNode.nodeType === 1) {
|
|
295
|
+
// BASE CASE #1: If this isn't an element, we're above the root, return empty string
|
|
296
|
+
if (!currentNode.tagName) {
|
|
297
|
+
return "";
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// BASE CASE #2: Check for unique attributes
|
|
301
|
+
let uniqueAttrFound = false;
|
|
302
|
+
for (const attr of currentNode.attributes) {
|
|
303
|
+
if (checkBlockedAttributes(attr, currentNode, isTarget)) {
|
|
304
|
+
let attrValue = attr.nodeValue;
|
|
305
|
+
|
|
306
|
+
attrValue = attrValue?.replace("removePointers", "") ?? "";
|
|
307
|
+
const elementName = attr.name;
|
|
308
|
+
|
|
309
|
+
const xpathe = getXpathString(currentNode, elementName, attrValue);
|
|
310
|
+
|
|
311
|
+
let othersWithAttr;
|
|
312
|
+
|
|
313
|
+
// If the XPath does not parse, move to the next unique attribute
|
|
314
|
+
try {
|
|
315
|
+
othersWithAttr = getCountOfXPath(xpathe, currentNode, docmt);
|
|
316
|
+
} catch (ign) {
|
|
317
|
+
continue;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// If the attribute is unique, return its XPath
|
|
321
|
+
if (othersWithAttr === 1) {
|
|
322
|
+
xpathParts.unshift(xpathe);
|
|
323
|
+
uniqueAttrFound = true; // Mark that we found at least one unique attribute
|
|
324
|
+
break;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// If no unique attributes, check for text content
|
|
330
|
+
if (!uniqueAttrFound && currentNode.textContent && !node.textContent) {
|
|
331
|
+
const textXPath = getFilteredTextXPath(currentNode, docmt);
|
|
332
|
+
if (textXPath) {
|
|
333
|
+
const othersWithAttr = getCountOfXPath(textXPath, currentNode, docmt);
|
|
334
|
+
if (othersWithAttr === 1) {
|
|
335
|
+
uniqueAttrFound = true; // Mark that we found at least one unique attribute
|
|
336
|
+
xpathParts.unshift(textXPath);
|
|
337
|
+
break;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
if (!uniqueAttrFound) {
|
|
343
|
+
// Construct the XPath based on the tag name
|
|
344
|
+
const xpathe = isSvg(currentNode)
|
|
345
|
+
? `/*[local-name()='${currentNode.tagName}']`
|
|
346
|
+
: `/${currentNode.tagName}`;
|
|
347
|
+
|
|
348
|
+
// Prepend the current XPath part to the array
|
|
349
|
+
xpathParts.unshift(xpathe);
|
|
350
|
+
} else {
|
|
351
|
+
break;
|
|
352
|
+
}
|
|
353
|
+
// Move to the parent node for the next iteration
|
|
354
|
+
currentNode = currentNode.parentElement!;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Combine all parts into the final XPath
|
|
358
|
+
const finalXpath = xpathParts.join("");
|
|
359
|
+
cache.set(domNode, finalXpath); // Store result in cache
|
|
360
|
+
return finalXpath;
|
|
361
|
+
} catch (error) {
|
|
362
|
+
console.log(error);
|
|
363
|
+
return null;
|
|
364
|
+
}
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
const getChildRelativeXpath = (
|
|
368
|
+
domNode: HTMLElement | Element,
|
|
369
|
+
docmt: Document,
|
|
370
|
+
node: HTMLElement | Element
|
|
371
|
+
) => {
|
|
372
|
+
const xpathParts = []; // Initialize an array to hold parts of the XPath.
|
|
373
|
+
let currentNode: HTMLElement | Element | null;
|
|
374
|
+
|
|
375
|
+
const st = [];
|
|
376
|
+
if (
|
|
377
|
+
domNode.firstElementChild != null &&
|
|
378
|
+
domNode.firstElementChild.classList.contains("flntooltip")
|
|
379
|
+
) {
|
|
380
|
+
st.unshift(domNode.firstElementChild);
|
|
381
|
+
} else if (domNode.nextElementSibling != null)
|
|
382
|
+
st.unshift(domNode.nextElementSibling);
|
|
383
|
+
|
|
384
|
+
for (
|
|
385
|
+
let m = domNode.parentElement;
|
|
386
|
+
m != null && m.nodeType === 1;
|
|
387
|
+
m = m.parentElement
|
|
388
|
+
) {
|
|
389
|
+
if (m.nextElementSibling) st.unshift(m.nextElementSibling);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
try {
|
|
393
|
+
do {
|
|
394
|
+
let uniqueAttrFound = false;
|
|
395
|
+
for (currentNode = st.pop()!; currentNode !== null; ) {
|
|
396
|
+
for (const attr of currentNode.attributes) {
|
|
397
|
+
if (checkBlockedAttributes(attr, currentNode, true)) {
|
|
398
|
+
let attrValue = attr.nodeValue;
|
|
399
|
+
|
|
400
|
+
attrValue = attrValue?.replace("removePointers", "") ?? "";
|
|
401
|
+
const elementName = attr.name;
|
|
402
|
+
|
|
403
|
+
const xpathe = getXpathString(currentNode, elementName, attrValue);
|
|
404
|
+
|
|
405
|
+
let othersWithAttr;
|
|
406
|
+
|
|
407
|
+
// If the XPath does not parse, move to the next unique attribute
|
|
408
|
+
try {
|
|
409
|
+
othersWithAttr = getCountOfXPath(xpathe, currentNode, docmt);
|
|
410
|
+
} catch (ign) {
|
|
411
|
+
continue;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// If the attribute is unique, return its XPath
|
|
415
|
+
if (othersWithAttr === 1) {
|
|
416
|
+
uniqueAttrFound = true; // Mark that we found at least one unique attribute
|
|
417
|
+
xpathParts.push(xpathe);
|
|
418
|
+
break;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// If no unique attributes, check for text content
|
|
424
|
+
if (!uniqueAttrFound && currentNode.textContent && !node.textContent) {
|
|
425
|
+
const textXPath = getFilteredTextXPath(currentNode, docmt);
|
|
426
|
+
if (textXPath) {
|
|
427
|
+
const othersWithAttr = getCountOfXPath(
|
|
428
|
+
textXPath,
|
|
429
|
+
currentNode,
|
|
430
|
+
docmt
|
|
431
|
+
);
|
|
432
|
+
if (othersWithAttr === 1) {
|
|
433
|
+
uniqueAttrFound = true; // Mark that we found at least one unique attribute
|
|
434
|
+
xpathParts.push(textXPath);
|
|
435
|
+
break;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
if (!uniqueAttrFound) {
|
|
441
|
+
// Construct the XPath based on the tag name
|
|
442
|
+
const xpathe = isSvg(currentNode)
|
|
443
|
+
? `/*[local-name()='${currentNode.tagName}']`
|
|
444
|
+
: `/${currentNode.tagName}`;
|
|
445
|
+
|
|
446
|
+
// Prepend the current XPath part to the array
|
|
447
|
+
xpathParts.push(xpathe);
|
|
448
|
+
|
|
449
|
+
if (currentNode.firstElementChild != null) {
|
|
450
|
+
st.push(currentNode.nextElementSibling);
|
|
451
|
+
currentNode = currentNode.firstElementChild;
|
|
452
|
+
} else {
|
|
453
|
+
currentNode = currentNode.nextElementSibling;
|
|
454
|
+
}
|
|
455
|
+
} else {
|
|
456
|
+
break;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
} while (st.length > 0);
|
|
460
|
+
|
|
461
|
+
// Combine all parts into the final XPath
|
|
462
|
+
const finalXpath = xpathParts.join("");
|
|
463
|
+
cache.set(domNode, finalXpath); // Store result in cache
|
|
464
|
+
return finalXpath;
|
|
465
|
+
} catch (error) {
|
|
466
|
+
console.log(error);
|
|
467
|
+
return null;
|
|
468
|
+
}
|
|
469
|
+
};
|
|
470
|
+
|
|
471
|
+
const getSiblingRelativeXPath = (
|
|
472
|
+
domNode: HTMLElement | Element,
|
|
473
|
+
docmt: Document,
|
|
474
|
+
isTarget: boolean,
|
|
475
|
+
nodeXpath: string
|
|
476
|
+
) => {
|
|
477
|
+
try {
|
|
478
|
+
const markedSpan = document.querySelector(".flntooltip");
|
|
479
|
+
|
|
480
|
+
for (
|
|
481
|
+
let m = domNode.nextElementSibling;
|
|
482
|
+
m !== null && m !== markedSpan;
|
|
483
|
+
m = m.nextElementSibling
|
|
484
|
+
) {
|
|
485
|
+
processSibling(
|
|
486
|
+
m,
|
|
487
|
+
domNode,
|
|
488
|
+
docmt,
|
|
489
|
+
nodeXpath,
|
|
490
|
+
"preceding-sibling",
|
|
491
|
+
isTarget
|
|
492
|
+
);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
for (
|
|
496
|
+
let n = domNode.previousElementSibling;
|
|
497
|
+
n !== null && n !== markedSpan;
|
|
498
|
+
n = n.previousElementSibling
|
|
499
|
+
) {
|
|
500
|
+
processSibling(
|
|
501
|
+
n,
|
|
502
|
+
domNode,
|
|
503
|
+
docmt,
|
|
504
|
+
nodeXpath,
|
|
505
|
+
"following-sibling",
|
|
506
|
+
isTarget
|
|
507
|
+
);
|
|
508
|
+
}
|
|
509
|
+
} catch (error) {
|
|
510
|
+
console.error("sibling error", error);
|
|
511
|
+
return null;
|
|
512
|
+
}
|
|
513
|
+
};
|
|
514
|
+
|
|
515
|
+
const processSibling = (
|
|
516
|
+
sibling: Element,
|
|
517
|
+
domNode: HTMLElement | Element,
|
|
518
|
+
docmt: Document,
|
|
519
|
+
nodeXpath: string,
|
|
520
|
+
axis: string,
|
|
521
|
+
isTarget: boolean
|
|
522
|
+
) => {
|
|
523
|
+
try {
|
|
524
|
+
if (sibling.hasAttributes()) {
|
|
525
|
+
for (const attr of sibling.attributes) {
|
|
526
|
+
let xpathe = intermediateXpathStep(
|
|
527
|
+
sibling,
|
|
528
|
+
{
|
|
529
|
+
name: attr.name,
|
|
530
|
+
value: attr.value,
|
|
531
|
+
},
|
|
532
|
+
isTarget
|
|
533
|
+
);
|
|
534
|
+
if (xpathe) {
|
|
535
|
+
xpathe += `/${axis}::${nodeXpath}`;
|
|
536
|
+
|
|
537
|
+
const count = getCountOfXPath(xpathe, sibling, docmt);
|
|
538
|
+
|
|
539
|
+
if (count === 1) {
|
|
540
|
+
xpathData.push({
|
|
541
|
+
key: `xpath by ${axis}`,
|
|
542
|
+
value: xpathe,
|
|
543
|
+
});
|
|
544
|
+
return;
|
|
545
|
+
} else if (count > 1) {
|
|
546
|
+
if (xpathDataWithIndex.length) {
|
|
547
|
+
if (count < xpathDataWithIndex[0].count) {
|
|
548
|
+
xpathDataWithIndex.pop();
|
|
549
|
+
xpathDataWithIndex.push({
|
|
550
|
+
key: `relative xpath by ${axis}`,
|
|
551
|
+
value: xpathe,
|
|
552
|
+
count,
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
} else {
|
|
556
|
+
xpathDataWithIndex.push({
|
|
557
|
+
key: `relative xpath by ${axis}`,
|
|
558
|
+
value: xpathe,
|
|
559
|
+
count,
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
if (!isTarget) {
|
|
568
|
+
let xpathe;
|
|
569
|
+
xpathe = intermediateXpathStep(
|
|
570
|
+
sibling,
|
|
571
|
+
{
|
|
572
|
+
name: "text",
|
|
573
|
+
value: sibling.textContent,
|
|
574
|
+
},
|
|
575
|
+
isTarget
|
|
576
|
+
);
|
|
577
|
+
|
|
578
|
+
if (xpathe) {
|
|
579
|
+
const count = getCountOfXPath(xpathe, sibling, docmt);
|
|
580
|
+
|
|
581
|
+
if (count === 1) {
|
|
582
|
+
xpathData.push({
|
|
583
|
+
key: `xpath by ${axis}`,
|
|
584
|
+
value: xpathe,
|
|
585
|
+
});
|
|
586
|
+
return;
|
|
587
|
+
} else if (count > 1) {
|
|
588
|
+
if (xpathDataWithIndex.length) {
|
|
589
|
+
if (count < xpathDataWithIndex[0].count) {
|
|
590
|
+
xpathDataWithIndex.pop();
|
|
591
|
+
xpathDataWithIndex.push({
|
|
592
|
+
key: `relative xpath by ${axis}`,
|
|
593
|
+
value: xpathe,
|
|
594
|
+
count,
|
|
595
|
+
});
|
|
596
|
+
}
|
|
597
|
+
} else {
|
|
598
|
+
xpathDataWithIndex.push({
|
|
599
|
+
key: `relative xpath by ${axis}`,
|
|
600
|
+
value: xpathe,
|
|
601
|
+
count,
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
} catch (error) {
|
|
608
|
+
console.log(`${axis} xpath-error`, error);
|
|
609
|
+
}
|
|
610
|
+
};
|
|
611
|
+
|
|
612
|
+
function getXPathUsingAttributeAndText(
|
|
613
|
+
attributes: Attr[],
|
|
614
|
+
targetElemt: HTMLElement | Element,
|
|
615
|
+
docmt: Document,
|
|
616
|
+
isTarget: boolean
|
|
617
|
+
) {
|
|
618
|
+
const { tagName } = targetElemt;
|
|
619
|
+
const textContent = targetElemt.textContent.trim();
|
|
620
|
+
for (const attrName of attributes) {
|
|
621
|
+
if (checkBlockedAttributes(attrName, targetElemt, isTarget)) {
|
|
622
|
+
let attrValue = attrName.nodeValue;
|
|
623
|
+
|
|
624
|
+
attrValue = attrValue?.replace("removePointers", "") ?? "";
|
|
625
|
+
const elementName = attrName.name;
|
|
626
|
+
const xpath = `//${tagName}[@${elementName}='${attrValue}' and text()='${textContent}']`;
|
|
627
|
+
if (xpath) {
|
|
628
|
+
const count = getCountOfXPath(xpath, targetElemt, docmt);
|
|
629
|
+
if (count == 1) {
|
|
630
|
+
return xpath;
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
const addRelativeXpaths = (
|
|
638
|
+
targetElemt: HTMLElement | Element,
|
|
639
|
+
docmt: Document,
|
|
640
|
+
isIndex: boolean,
|
|
641
|
+
isTarget: boolean,
|
|
642
|
+
attribute: Attr[]
|
|
643
|
+
) => {
|
|
644
|
+
try {
|
|
645
|
+
let nodeXpath: string[] = [];
|
|
646
|
+
let relativeXpath, relativeChildXpath;
|
|
647
|
+
|
|
648
|
+
if (!xpathData.length && isIndex) {
|
|
649
|
+
if (xpathDataWithIndex.length) {
|
|
650
|
+
const xpathWithIndex = findXpathWithIndex(
|
|
651
|
+
xpathDataWithIndex[0].value,
|
|
652
|
+
targetElemt,
|
|
653
|
+
docmt,
|
|
654
|
+
xpathDataWithIndex[0].count
|
|
655
|
+
);
|
|
656
|
+
if (xpathWithIndex) {
|
|
657
|
+
xpathData.push({
|
|
658
|
+
key: xpathDataWithIndex[0].key,
|
|
659
|
+
value: xpathWithIndex,
|
|
660
|
+
});
|
|
661
|
+
xpathDataWithIndex.pop();
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
console.log(attribute);
|
|
667
|
+
if (!xpathData.length) {
|
|
668
|
+
if (targetElemt.attributes) {
|
|
669
|
+
for (const attrName of attribute) {
|
|
670
|
+
let expression = intermediateXpathStep(
|
|
671
|
+
targetElemt,
|
|
672
|
+
{
|
|
673
|
+
name: attrName.name,
|
|
674
|
+
value: attrName.value,
|
|
675
|
+
},
|
|
676
|
+
isTarget
|
|
677
|
+
);
|
|
678
|
+
|
|
679
|
+
console.log(expression);
|
|
680
|
+
if (expression) {
|
|
681
|
+
nodeXpath.push(expression);
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
if (targetElemt.textContent) {
|
|
687
|
+
let expression = intermediateXpathStep(
|
|
688
|
+
targetElemt,
|
|
689
|
+
{
|
|
690
|
+
name: "text",
|
|
691
|
+
value: targetElemt.textContent,
|
|
692
|
+
},
|
|
693
|
+
isTarget
|
|
694
|
+
);
|
|
695
|
+
|
|
696
|
+
console.log(expression);
|
|
697
|
+
if (expression) {
|
|
698
|
+
nodeXpath.push(expression);
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
if (nodeXpath?.length) {
|
|
703
|
+
for (let i = 0; i < nodeXpath.length; i++) {
|
|
704
|
+
if (!xpathData.length) {
|
|
705
|
+
getSiblingRelativeXPath(targetElemt, docmt, isTarget, nodeXpath[i]);
|
|
706
|
+
|
|
707
|
+
if (!xpathData.length) {
|
|
708
|
+
if (!relativeXpath) {
|
|
709
|
+
relativeXpath = getParentRelativeXpath(
|
|
710
|
+
targetElemt.parentElement!,
|
|
711
|
+
docmt,
|
|
712
|
+
targetElemt,
|
|
713
|
+
isTarget
|
|
714
|
+
);
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
console.log(relativeXpath);
|
|
718
|
+
|
|
719
|
+
if (
|
|
720
|
+
relativeXpath &&
|
|
721
|
+
(relativeXpath.includes("@") ||
|
|
722
|
+
relativeXpath.includes("text()") ||
|
|
723
|
+
relativeXpath.includes(".=")) &&
|
|
724
|
+
relativeXpath.match(/\//g)?.length - 2 < 5
|
|
725
|
+
) {
|
|
726
|
+
const fullRelativeXpath = relativeXpath + `/${nodeXpath[i]}`;
|
|
727
|
+
const count = getCountOfXPath(
|
|
728
|
+
fullRelativeXpath,
|
|
729
|
+
targetElemt,
|
|
730
|
+
docmt
|
|
731
|
+
);
|
|
732
|
+
|
|
733
|
+
if (count === 1) {
|
|
734
|
+
xpathData.push({
|
|
735
|
+
key: "relative xpath by relative parent",
|
|
736
|
+
value: fullRelativeXpath,
|
|
737
|
+
});
|
|
738
|
+
} else if (count > 1 && isIndex) {
|
|
739
|
+
const relativeXpathIndex = findXpathWithIndex(
|
|
740
|
+
fullRelativeXpath,
|
|
741
|
+
targetElemt,
|
|
742
|
+
docmt,
|
|
743
|
+
count
|
|
744
|
+
);
|
|
745
|
+
if (
|
|
746
|
+
relativeXpathIndex &&
|
|
747
|
+
getCountOfXPath(relativeXpathIndex, targetElemt, docmt) ===
|
|
748
|
+
1
|
|
749
|
+
) {
|
|
750
|
+
xpathData.push({
|
|
751
|
+
key: `relative xpath by relative parent ${
|
|
752
|
+
isIndex ? "index" : ""
|
|
753
|
+
}`,
|
|
754
|
+
value: relativeXpathIndex,
|
|
755
|
+
});
|
|
756
|
+
}
|
|
757
|
+
} else if (count > 1) {
|
|
758
|
+
if (xpathDataWithIndex.length) {
|
|
759
|
+
if (count < xpathDataWithIndex[0].count) {
|
|
760
|
+
xpathDataWithIndex.pop();
|
|
761
|
+
xpathDataWithIndex.push({
|
|
762
|
+
key: `relative xpath by relative parent ${
|
|
763
|
+
isIndex ? "index" : ""
|
|
764
|
+
}`,
|
|
765
|
+
value: fullRelativeXpath,
|
|
766
|
+
count,
|
|
767
|
+
});
|
|
768
|
+
}
|
|
769
|
+
} else {
|
|
770
|
+
xpathDataWithIndex.push({
|
|
771
|
+
key: `relative xpath by relative parent ${
|
|
772
|
+
isIndex ? "index" : ""
|
|
773
|
+
}`,
|
|
774
|
+
value: fullRelativeXpath,
|
|
775
|
+
count,
|
|
776
|
+
});
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
if (!xpathData.length) {
|
|
783
|
+
if (!relativeChildXpath) {
|
|
784
|
+
relativeChildXpath = getChildRelativeXpath(
|
|
785
|
+
targetElemt,
|
|
786
|
+
docmt,
|
|
787
|
+
targetElemt
|
|
788
|
+
);
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
if (
|
|
792
|
+
relativeChildXpath &&
|
|
793
|
+
(relativeChildXpath.includes("@") ||
|
|
794
|
+
relativeChildXpath.includes("text()") ||
|
|
795
|
+
relativeChildXpath.includes(".="))
|
|
796
|
+
) {
|
|
797
|
+
const fullRelativeXpath = `/${
|
|
798
|
+
nodeXpath[i] + relativeChildXpath.substring(1)
|
|
799
|
+
}`;
|
|
800
|
+
const count = getCountOfXPath(
|
|
801
|
+
fullRelativeXpath,
|
|
802
|
+
targetElemt,
|
|
803
|
+
docmt
|
|
804
|
+
);
|
|
805
|
+
|
|
806
|
+
if (count === 1) {
|
|
807
|
+
xpathData.push({
|
|
808
|
+
key: "relative xpath by relative child",
|
|
809
|
+
value: fullRelativeXpath,
|
|
810
|
+
});
|
|
811
|
+
} else if (count > 1 && isIndex) {
|
|
812
|
+
const relativeXpathIndex = findXpathWithIndex(
|
|
813
|
+
fullRelativeXpath,
|
|
814
|
+
targetElemt,
|
|
815
|
+
docmt,
|
|
816
|
+
count
|
|
817
|
+
);
|
|
818
|
+
if (
|
|
819
|
+
relativeXpathIndex &&
|
|
820
|
+
getCountOfXPath(relativeXpathIndex, targetElemt, docmt) ===
|
|
821
|
+
1
|
|
822
|
+
) {
|
|
823
|
+
xpathData.push({
|
|
824
|
+
key: `relative xpath by relative parent ${
|
|
825
|
+
isIndex ? "index" : ""
|
|
826
|
+
}`,
|
|
827
|
+
value: relativeXpathIndex,
|
|
828
|
+
});
|
|
829
|
+
}
|
|
830
|
+
} else if (count > 1) {
|
|
831
|
+
if (xpathDataWithIndex.length) {
|
|
832
|
+
if (count < xpathDataWithIndex[0].count) {
|
|
833
|
+
xpathDataWithIndex.pop();
|
|
834
|
+
xpathDataWithIndex.push({
|
|
835
|
+
key: `relative xpath by relative child ${
|
|
836
|
+
isIndex ? "index" : ""
|
|
837
|
+
}`,
|
|
838
|
+
value: fullRelativeXpath,
|
|
839
|
+
count,
|
|
840
|
+
});
|
|
841
|
+
}
|
|
842
|
+
} else {
|
|
843
|
+
xpathDataWithIndex.push({
|
|
844
|
+
key: `relative xpath by relative child ${
|
|
845
|
+
isIndex ? "index" : ""
|
|
846
|
+
}`,
|
|
847
|
+
value: fullRelativeXpath,
|
|
848
|
+
count,
|
|
849
|
+
});
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
if (
|
|
856
|
+
xpathData?.length === 1 &&
|
|
857
|
+
xpathData?.[0]?.value?.match(/\[([0-9]+)\]/gm)?.length! > 3 &&
|
|
858
|
+
!referenceElementMode
|
|
859
|
+
) {
|
|
860
|
+
if (targetElemt.textContent) {
|
|
861
|
+
const txtXpath = getTextXPath(
|
|
862
|
+
targetElemt,
|
|
863
|
+
docmt,
|
|
864
|
+
isIndex,
|
|
865
|
+
false
|
|
866
|
+
);
|
|
867
|
+
if (txtXpath) {
|
|
868
|
+
xpathData.unshift({
|
|
869
|
+
key: `xpath by text${isIndex ? "index" : ""}`,
|
|
870
|
+
value: txtXpath,
|
|
871
|
+
});
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
if (!xpathData.length) {
|
|
877
|
+
let tempRelativeXpath = getUniqueParentXpath(
|
|
878
|
+
targetElemt.parentElement!,
|
|
879
|
+
docmt,
|
|
880
|
+
targetElemt,
|
|
881
|
+
isTarget,
|
|
882
|
+
nodeXpath[i],
|
|
883
|
+
isIndex
|
|
884
|
+
);
|
|
885
|
+
|
|
886
|
+
if (tempRelativeXpath) {
|
|
887
|
+
xpathData.push({
|
|
888
|
+
key: "xpath by unique parent",
|
|
889
|
+
value: tempRelativeXpath,
|
|
890
|
+
});
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
return xpathData;
|
|
899
|
+
} catch (error) {
|
|
900
|
+
console.log(error);
|
|
901
|
+
}
|
|
902
|
+
};
|
|
903
|
+
|
|
904
|
+
export const attributesBasedXPath = (
|
|
905
|
+
attr: Attr,
|
|
906
|
+
targetElemt: HTMLElement | Element,
|
|
907
|
+
docmt: Document,
|
|
908
|
+
isIndex: boolean,
|
|
909
|
+
isTarget: boolean
|
|
910
|
+
) => {
|
|
911
|
+
let attrName;
|
|
912
|
+
|
|
913
|
+
attrName = attr.name;
|
|
914
|
+
let xpath = getPropertyXPath(
|
|
915
|
+
targetElemt,
|
|
916
|
+
docmt,
|
|
917
|
+
`@${attrName}`,
|
|
918
|
+
attr.value,
|
|
919
|
+
isIndex,
|
|
920
|
+
isTarget
|
|
921
|
+
);
|
|
922
|
+
|
|
923
|
+
return xpath;
|
|
924
|
+
};
|
|
925
|
+
|
|
926
|
+
export const getUniqueClassName = (
|
|
927
|
+
element: HTMLElement | Element,
|
|
928
|
+
docmt: Document,
|
|
929
|
+
isIndex: boolean,
|
|
930
|
+
isTarget: boolean
|
|
931
|
+
) => {
|
|
932
|
+
let value = element.className;
|
|
933
|
+
if (typeof value !== "string") {
|
|
934
|
+
value = "";
|
|
935
|
+
}
|
|
936
|
+
value = value?.replace("flndisabled", "disabled");
|
|
937
|
+
value = value?.replace("removePointers", "");
|
|
938
|
+
value = value?.trim();
|
|
939
|
+
|
|
940
|
+
if (value) {
|
|
941
|
+
return getPropertyXPath(element, docmt, `@class`, value, isIndex, isTarget);
|
|
942
|
+
}
|
|
943
|
+
};
|
|
944
|
+
|
|
945
|
+
export const getTextXPath = (
|
|
946
|
+
element: HTMLElement | Element,
|
|
947
|
+
docmt: Document,
|
|
948
|
+
isIndex: boolean,
|
|
949
|
+
isTarget: boolean
|
|
950
|
+
) => {
|
|
951
|
+
if (element.textContent != "") {
|
|
952
|
+
const text = getTextContent(element);
|
|
953
|
+
|
|
954
|
+
if (text) {
|
|
955
|
+
return getPropertyXPath(element, docmt, ".", text, isIndex, isTarget);
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
};
|
|
959
|
+
|
|
960
|
+
const addAllXPathAttributes = (
|
|
961
|
+
attributes: Attr[],
|
|
962
|
+
targetElemt: HTMLElement | Element,
|
|
963
|
+
docmt: Document,
|
|
964
|
+
isIndex: boolean,
|
|
965
|
+
isTarget: boolean
|
|
966
|
+
) => {
|
|
967
|
+
const attributesArray = attributes;
|
|
968
|
+
try {
|
|
969
|
+
attributesArray.map((attr) => {
|
|
970
|
+
if (!(attr.name === "className" || attr.name === "class")) {
|
|
971
|
+
if (checkBlockedAttributes(attr, targetElemt, isTarget)) {
|
|
972
|
+
const xpth = attributesBasedXPath(
|
|
973
|
+
attr,
|
|
974
|
+
targetElemt,
|
|
975
|
+
docmt,
|
|
976
|
+
isIndex,
|
|
977
|
+
isTarget
|
|
978
|
+
);
|
|
979
|
+
if (xpth) {
|
|
980
|
+
xpathData.push({
|
|
981
|
+
key: `xpath by ${attr.name}${isIndex ? " index" : ""}`,
|
|
982
|
+
value: xpth,
|
|
983
|
+
});
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
});
|
|
988
|
+
|
|
989
|
+
const txtXpath = getTextXPath(targetElemt, docmt, isIndex, isTarget);
|
|
990
|
+
if (txtXpath) {
|
|
991
|
+
xpathData.push({
|
|
992
|
+
key: `xpath by text${isIndex ? " index" : ""}`,
|
|
993
|
+
value: txtXpath,
|
|
994
|
+
});
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
if (
|
|
998
|
+
attributesArray.find((element) => element.name === "className") &&
|
|
999
|
+
checkBlockedAttributes(
|
|
1000
|
+
attributesArray?.find((element) => element.name === "className")!,
|
|
1001
|
+
targetElemt,
|
|
1002
|
+
isTarget
|
|
1003
|
+
)
|
|
1004
|
+
) {
|
|
1005
|
+
let xpath = getUniqueClassName(targetElemt, docmt, isIndex, isTarget);
|
|
1006
|
+
if (xpath) {
|
|
1007
|
+
xpathData.push({
|
|
1008
|
+
key: "xpath by class",
|
|
1009
|
+
value: xpath,
|
|
1010
|
+
});
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
if (!xpathData.length) {
|
|
1015
|
+
const textAttribute = getXPathUsingAttributeAndText(
|
|
1016
|
+
attributes,
|
|
1017
|
+
targetElemt,
|
|
1018
|
+
docmt,
|
|
1019
|
+
isTarget
|
|
1020
|
+
);
|
|
1021
|
+
if (textAttribute)
|
|
1022
|
+
xpathData.push({
|
|
1023
|
+
key: "xpath by textAttribute",
|
|
1024
|
+
value: textAttribute,
|
|
1025
|
+
});
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
if (!xpathData.length && attributesArray.length > 1) {
|
|
1029
|
+
const combinationXpath = getAttributeCombinationXpath(
|
|
1030
|
+
targetElemt,
|
|
1031
|
+
docmt,
|
|
1032
|
+
attributesArray,
|
|
1033
|
+
isTarget
|
|
1034
|
+
);
|
|
1035
|
+
if (combinationXpath)
|
|
1036
|
+
xpathData.push({
|
|
1037
|
+
key: "xpath by combination",
|
|
1038
|
+
value: combinationXpath,
|
|
1039
|
+
});
|
|
1040
|
+
}
|
|
1041
|
+
} catch (error) {
|
|
1042
|
+
console.log(error);
|
|
1043
|
+
}
|
|
1044
|
+
};
|
|
1045
|
+
|
|
1046
|
+
export const parseDOM = (
|
|
1047
|
+
element: HTMLElement | Element,
|
|
1048
|
+
doc: Document,
|
|
1049
|
+
isIndex: boolean,
|
|
1050
|
+
isTarget: boolean
|
|
1051
|
+
) => {
|
|
1052
|
+
xpathData = [];
|
|
1053
|
+
console.log(element);
|
|
1054
|
+
const targetElemt = element;
|
|
1055
|
+
const docmt = targetElemt?.ownerDocument || doc;
|
|
1056
|
+
const tag = targetElemt.tagName;
|
|
1057
|
+
const { attributes } = targetElemt;
|
|
1058
|
+
addAllXPathAttributes([...attributes], targetElemt, docmt, isIndex, isTarget);
|
|
1059
|
+
if (!referenceElementMode) {
|
|
1060
|
+
if (xpathData.length) {
|
|
1061
|
+
const len = xpathData.length;
|
|
1062
|
+
for (let i = 0; i < len; i++) {
|
|
1063
|
+
let xpth = xpathData[i].value;
|
|
1064
|
+
xpth = xpth.replace(tag, "*");
|
|
1065
|
+
const count = getCountOfXPath(xpth, element, docmt);
|
|
1066
|
+
if (count === 1) {
|
|
1067
|
+
xpathData.push({
|
|
1068
|
+
key: `${xpathData[i].key} regex`,
|
|
1069
|
+
value: xpth,
|
|
1070
|
+
});
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
console.log(xpathData);
|
|
1077
|
+
if (!xpathData.length) {
|
|
1078
|
+
addRelativeXpaths(
|
|
1079
|
+
targetElemt,
|
|
1080
|
+
docmt,
|
|
1081
|
+
isIndex,
|
|
1082
|
+
isTarget,
|
|
1083
|
+
[...targetElemt.attributes]
|
|
1084
|
+
);
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
return xpathData;
|
|
1088
|
+
};
|
|
1089
|
+
|
|
1090
|
+
const xpath = {
|
|
1091
|
+
parseDOM,
|
|
1092
|
+
getTextXPath,
|
|
1093
|
+
getUniqueClassName,
|
|
1094
|
+
attributesBasedXPath,
|
|
1095
|
+
addAllXPathAttributes,
|
|
1096
|
+
addRelativeXpaths,
|
|
1097
|
+
getXPathUsingAttributeAndText,
|
|
1098
|
+
getSiblingRelativeXPath,
|
|
1099
|
+
getChildRelativeXpath,
|
|
1100
|
+
getParentRelativeXpath,
|
|
1101
|
+
getUniqueParentXpath,
|
|
1102
|
+
checkRelativeXpathRelation,
|
|
1103
|
+
};
|
|
1104
|
+
|
|
1105
|
+
export default xpath;
|