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
|
@@ -1,593 +1,513 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
const
|
|
73
|
-
if (
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
null
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
if (
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
isRecorded: loc.value === fnValue ? loc.isRecorded : 'Y',
|
|
515
|
-
isSelfHealed: loc.value === fnValue ? loc.isSelfHealed : 'Y',
|
|
516
|
-
});
|
|
517
|
-
});
|
|
518
|
-
});
|
|
519
|
-
|
|
520
|
-
for (const locator of locators) {
|
|
521
|
-
try {
|
|
522
|
-
for (const loc of locators) {
|
|
523
|
-
if (!loc.value) continue;
|
|
524
|
-
|
|
525
|
-
for (const relation of relations) {
|
|
526
|
-
if (loc.value.includes(relation)) {
|
|
527
|
-
const relativeXpath = checkReferenceElementIsValid(loc.value, relation, tempDiv);
|
|
528
|
-
if (relativeXpath) {
|
|
529
|
-
createLocator(loc, {
|
|
530
|
-
name: 'xpath',
|
|
531
|
-
value: relativeXpath,
|
|
532
|
-
isRecorded: locator.isRecorded !== '' && locator.isRecorded !== null ? locator.isRecorded : 'Y',
|
|
533
|
-
});
|
|
534
|
-
break;
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
} catch (error) {
|
|
540
|
-
console.error('Error processing locator:', locator, error);
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
const finalAutoHealedLocators = finalLocators.map((obj) => ({
|
|
545
|
-
...obj,
|
|
546
|
-
value: cleanLocatorValue(obj.value, obj.name, obj.isRecorded),
|
|
547
|
-
}));
|
|
548
|
-
|
|
549
|
-
const finalUniqueAutoHealedLocators: Locator[] = finalAutoHealedLocators.reduce(
|
|
550
|
-
(unique: Locator[], locator: Locator) => {
|
|
551
|
-
if (locator.value && !unique.some((l: Locator) => l.value === locator.value)) {
|
|
552
|
-
unique.push(locator);
|
|
553
|
-
}
|
|
554
|
-
return unique;
|
|
555
|
-
}, [] as Locator[]
|
|
556
|
-
);
|
|
557
|
-
|
|
558
|
-
const jsonResult = [
|
|
559
|
-
{
|
|
560
|
-
name: `${name}`,
|
|
561
|
-
desc: `${desc}`,
|
|
562
|
-
type: `${type}`,
|
|
563
|
-
locators: finalUniqueAutoHealedLocators.filter((locator) => locator?.value != null && locator.value !== ''),
|
|
564
|
-
isShared: `${isShared}`,
|
|
565
|
-
projectId: `${projectId}`,
|
|
566
|
-
projectType: `${projectType}`,
|
|
567
|
-
isRecorded: `${isRecorded}`,
|
|
568
|
-
folder: `${folder}`,
|
|
569
|
-
parentId: `${parentId}`,
|
|
570
|
-
parentName: `${parentName}`,
|
|
571
|
-
platform: `${platform}`,
|
|
572
|
-
licenseId: `${licenseId}`,
|
|
573
|
-
licenseType: `${licenseType}`,
|
|
574
|
-
userId: `${userId}`,
|
|
575
|
-
},
|
|
576
|
-
];
|
|
577
|
-
|
|
578
|
-
return jsonResult;
|
|
579
|
-
}
|
|
580
|
-
} catch (error) {
|
|
581
|
-
console.error('Error processing locator:', locator, error);
|
|
582
|
-
continue;
|
|
583
|
-
}
|
|
584
|
-
}
|
|
585
|
-
} catch (error) {
|
|
586
|
-
console.error('Error processing locator:', locator, error);
|
|
587
|
-
continue;
|
|
588
|
-
}
|
|
589
|
-
}
|
|
590
|
-
return null;
|
|
591
|
-
};
|
|
592
|
-
|
|
593
|
-
export { getElementsFromHTML };
|
|
1
|
+
import { ElementRecord, Locator } from "../types/locator.ts";
|
|
2
|
+
import { parseDOM } from "./xpath.ts";
|
|
3
|
+
import { isSvg, normalizeXPath } from "./xpathHelpers.ts";
|
|
4
|
+
|
|
5
|
+
type ElementDetails = {
|
|
6
|
+
id?: string | null;
|
|
7
|
+
className?: string | null;
|
|
8
|
+
xpathByText?: string | null;
|
|
9
|
+
xpathById?: string | null;
|
|
10
|
+
xpathByClass?: string | null;
|
|
11
|
+
xpathAbsolute?: string | null;
|
|
12
|
+
xpathByName?: string | null;
|
|
13
|
+
xpathByPlaceholder?: string | null;
|
|
14
|
+
xpathByType?: string | null;
|
|
15
|
+
visibleText?: string | null;
|
|
16
|
+
relativeXpath?: string | null;
|
|
17
|
+
[key: string]: any;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const getElementFromShadowRoot = (
|
|
21
|
+
element: Element,
|
|
22
|
+
selector: string
|
|
23
|
+
): Element | null => {
|
|
24
|
+
const shadowRoot = (element as HTMLElement).shadowRoot;
|
|
25
|
+
if (shadowRoot && !selector.includes("dynamic")) {
|
|
26
|
+
return shadowRoot.querySelector(selector);
|
|
27
|
+
}
|
|
28
|
+
return null;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const getId = (element: Element | null): string | null => {
|
|
32
|
+
return element?.id || null;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const getClassName = (element: Element): string | null => {
|
|
36
|
+
return (element as HTMLElement).className || null;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const getVisibleText = (element: Element): string | null => {
|
|
40
|
+
return element.textContent?.trim() || null;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const getName = (element: Element): string | null => {
|
|
44
|
+
const elementEl = element as HTMLElement;
|
|
45
|
+
|
|
46
|
+
if (elementEl.hasAttribute("name")) {
|
|
47
|
+
const attrValue = elementEl.getAttribute("name");
|
|
48
|
+
const name = `${attrValue}`;
|
|
49
|
+
return name || null;
|
|
50
|
+
}
|
|
51
|
+
return null;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const relations: string[] = [
|
|
55
|
+
"/preceding-sibling",
|
|
56
|
+
"/following-sibling",
|
|
57
|
+
"/parent",
|
|
58
|
+
"/descendant",
|
|
59
|
+
"/ancestor",
|
|
60
|
+
"/self",
|
|
61
|
+
"/ancestor-or-self",
|
|
62
|
+
"/child",
|
|
63
|
+
"/preceding",
|
|
64
|
+
"/following",
|
|
65
|
+
];
|
|
66
|
+
|
|
67
|
+
function getElementFromXPath(
|
|
68
|
+
tempDiv: HTMLElement,
|
|
69
|
+
xpath: string
|
|
70
|
+
): Element | null {
|
|
71
|
+
let currentElement: Element | null = tempDiv;
|
|
72
|
+
const window = currentElement.ownerDocument.defaultView;
|
|
73
|
+
if (!window) return null;
|
|
74
|
+
|
|
75
|
+
const xpathEvaluator = new window.XPathEvaluator();
|
|
76
|
+
const xpathResult = xpathEvaluator.evaluate(
|
|
77
|
+
xpath,
|
|
78
|
+
currentElement.ownerDocument, //here even tempDiv can be passed
|
|
79
|
+
null,
|
|
80
|
+
window.XPathResult.FIRST_ORDERED_NODE_TYPE,
|
|
81
|
+
null
|
|
82
|
+
);
|
|
83
|
+
return xpathResult.singleNodeValue as Element | null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function checkReferenceElementIsValid(
|
|
87
|
+
locator: string,
|
|
88
|
+
relation: string,
|
|
89
|
+
tempDiv: HTMLElement
|
|
90
|
+
): string | null {
|
|
91
|
+
if (locator.includes(relation)) {
|
|
92
|
+
const locatotSplitArray: string[] = locator.split(relation);
|
|
93
|
+
const sourceLoc = locatotSplitArray[0].trim();
|
|
94
|
+
let currentElement: Element | null = tempDiv;
|
|
95
|
+
const window = currentElement.ownerDocument.defaultView;
|
|
96
|
+
if (!window) return null;
|
|
97
|
+
if (!locator.includes("dynamic")) {
|
|
98
|
+
const xpathEvaluator = new window.XPathEvaluator();
|
|
99
|
+
const xpathResult = xpathEvaluator.evaluate(
|
|
100
|
+
sourceLoc,
|
|
101
|
+
currentElement.ownerDocument,
|
|
102
|
+
null,
|
|
103
|
+
window.XPathResult.FIRST_ORDERED_NODE_TYPE,
|
|
104
|
+
null
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
const sourceElement = xpathResult.singleNodeValue;
|
|
108
|
+
if (sourceElement) {
|
|
109
|
+
const xpathResultComplete = xpathEvaluator.evaluate(
|
|
110
|
+
locator,
|
|
111
|
+
currentElement.ownerDocument,
|
|
112
|
+
null,
|
|
113
|
+
window.XPathResult.FIRST_ORDERED_NODE_TYPE,
|
|
114
|
+
null
|
|
115
|
+
);
|
|
116
|
+
const completeElement = xpathResultComplete.singleNodeValue;
|
|
117
|
+
let relativeXpath: string;
|
|
118
|
+
if (completeElement) {
|
|
119
|
+
relativeXpath = locator;
|
|
120
|
+
return relativeXpath;
|
|
121
|
+
} else {
|
|
122
|
+
console.error("Complete Locator is Invalid:", locator);
|
|
123
|
+
relativeXpath = locator;
|
|
124
|
+
return relativeXpath;
|
|
125
|
+
}
|
|
126
|
+
} else {
|
|
127
|
+
console.error("Source Locator Not Found:", sourceLoc);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const getElementsFromHTML = (
|
|
135
|
+
record: ElementRecord,
|
|
136
|
+
docmt: Document
|
|
137
|
+
): ElementDetails | null => {
|
|
138
|
+
const document = docmt;
|
|
139
|
+
// global.SVGElement = document.defaultView?.SVGElement!;
|
|
140
|
+
const tempDiv = document.createElement("div");
|
|
141
|
+
const elementsToRemove = document.querySelectorAll(
|
|
142
|
+
"script, style, link[rel='stylesheet'], meta, noscript, embed, object, param, source, svg"
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
if (elementsToRemove) {
|
|
146
|
+
elementsToRemove.forEach((tag) => {
|
|
147
|
+
(tag as Element).remove();
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
tempDiv.innerHTML = document.body.innerHTML;
|
|
152
|
+
const finalLocatorsSet: Set<string> = new Set();
|
|
153
|
+
let finalLocators: any[] = [];
|
|
154
|
+
|
|
155
|
+
function createLocator(base: any, overrides: Partial<any> = {}) {
|
|
156
|
+
const newLocator: any = {
|
|
157
|
+
name: overrides.name ?? base?.name,
|
|
158
|
+
type: overrides.type ?? base?.type,
|
|
159
|
+
value: overrides.value ?? base?.value,
|
|
160
|
+
reference: overrides.reference ?? base?.reference,
|
|
161
|
+
status: overrides.status ?? base?.status,
|
|
162
|
+
isRecorded: overrides.isRecorded ?? base?.isRecorded,
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
if (overrides.hasOwnProperty("isSelfHealed")) {
|
|
166
|
+
newLocator.isSelfHealed = overrides.isSelfHealed;
|
|
167
|
+
} else if (base?.hasOwnProperty("isSelfHealed")) {
|
|
168
|
+
newLocator.isSelfHealed = base.isSelfHealed;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
pushUniqueLocator(newLocator);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function pushUniqueLocator(obj: any) {
|
|
175
|
+
const key = `${obj.name}:${obj.value}`;
|
|
176
|
+
if (!finalLocatorsSet.has(key)) {
|
|
177
|
+
finalLocatorsSet.add(key);
|
|
178
|
+
finalLocators.push(obj);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** Locator Value Cleaner (Handles Special Scenarios) **/
|
|
183
|
+
const cleanLocatorValue = (
|
|
184
|
+
val: string | null | undefined,
|
|
185
|
+
type?: string,
|
|
186
|
+
isRecorded?: string
|
|
187
|
+
): string | null => {
|
|
188
|
+
if (!val) return null;
|
|
189
|
+
|
|
190
|
+
let cleaned = val.trim();
|
|
191
|
+
|
|
192
|
+
// Return null for empty or literal "null"
|
|
193
|
+
if (!cleaned || cleaned.toLowerCase() === "null") return null;
|
|
194
|
+
|
|
195
|
+
// Unescape any escaped quotes
|
|
196
|
+
cleaned = cleaned.replace(/\\"/g, '"').replace(/\\'/g, "'");
|
|
197
|
+
|
|
198
|
+
// Remove surrounding single or double quotes
|
|
199
|
+
cleaned = cleaned.replace(/^['"](.+?)['"]$/, "$1");
|
|
200
|
+
|
|
201
|
+
// Replace double single quotes with a single quote inside XPath
|
|
202
|
+
cleaned = cleaned.replace(/''/g, "'");
|
|
203
|
+
|
|
204
|
+
// Normalize double quotes in XPath attribute selectors [@id="" -> [@id='']
|
|
205
|
+
cleaned = cleaned.replace(
|
|
206
|
+
/\[@(id|name)=['"]{2}(.+?)['"]{2}\]/g,
|
|
207
|
+
"[@$1='$2']"
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
// For DOM selectors (id or name), remove ALL quotes
|
|
211
|
+
if (type === "id" || type === "name") {
|
|
212
|
+
cleaned = cleaned.replace(/['"]/g, "").trim();
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (type === "xpath" && isRecorded === "Y" && !val.startsWith("//"))
|
|
216
|
+
return null;
|
|
217
|
+
|
|
218
|
+
// Final check for empty strings
|
|
219
|
+
if (!cleaned || /^['"]{2}$/.test(cleaned)) return null;
|
|
220
|
+
|
|
221
|
+
return cleaned;
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
locators: for (const locator of record.locators) {
|
|
225
|
+
try {
|
|
226
|
+
const isRecorded = String(locator.isRecorded || "");
|
|
227
|
+
const recordedNLocators = record.locators.filter((l) => l.isRecorded === "N");
|
|
228
|
+
|
|
229
|
+
if (recordedNLocators.length > 0) {
|
|
230
|
+
for (const locator of recordedNLocators) {
|
|
231
|
+
createLocator(locator);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const isDynamic = String(locator.value || locator.type || "");
|
|
236
|
+
if (
|
|
237
|
+
isDynamic.includes("dynamic") ||
|
|
238
|
+
isDynamic.match("dynamic") ||
|
|
239
|
+
isDynamic.includes("{") ||
|
|
240
|
+
isDynamic.includes("}")
|
|
241
|
+
) {
|
|
242
|
+
createLocator(locator);
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (record.isShared.includes("Y")) {
|
|
247
|
+
break locators;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
for (const relation of relations) {
|
|
251
|
+
try {
|
|
252
|
+
let targetElement: Element | null = null;
|
|
253
|
+
if (locator.value.startsWith("iframe")) {
|
|
254
|
+
const iframe = tempDiv.querySelector(
|
|
255
|
+
locator.value
|
|
256
|
+
) as HTMLIFrameElement;
|
|
257
|
+
if (iframe) {
|
|
258
|
+
const iframeDocument =
|
|
259
|
+
iframe.contentDocument || iframe.contentWindow?.document;
|
|
260
|
+
if (iframeDocument) {
|
|
261
|
+
targetElement = iframeDocument.querySelector(
|
|
262
|
+
locator.value.slice(6)
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
} else {
|
|
267
|
+
const selectors = locator.value.split(">>>"); // Custom delimiter for shadow DOM
|
|
268
|
+
let currentElement: Element | null = tempDiv;
|
|
269
|
+
|
|
270
|
+
for (const selector of selectors) {
|
|
271
|
+
if (currentElement) {
|
|
272
|
+
const trimmedSelector = selector.trim();
|
|
273
|
+
if (
|
|
274
|
+
locator.name.includes("id") ||
|
|
275
|
+
trimmedSelector.startsWith("#")
|
|
276
|
+
) {
|
|
277
|
+
targetElement = currentElement.querySelector(
|
|
278
|
+
"#" + trimmedSelector
|
|
279
|
+
);
|
|
280
|
+
} else if (
|
|
281
|
+
locator.name.includes("className") ||
|
|
282
|
+
trimmedSelector.startsWith(".")
|
|
283
|
+
) {
|
|
284
|
+
targetElement = currentElement.querySelector(
|
|
285
|
+
"." + trimmedSelector
|
|
286
|
+
);
|
|
287
|
+
} else if (
|
|
288
|
+
(locator.name.includes("xpath") ||
|
|
289
|
+
trimmedSelector.startsWith("//")) &&
|
|
290
|
+
!locator.type.match("dynamic")
|
|
291
|
+
) {
|
|
292
|
+
if (tempDiv.innerHTML) {
|
|
293
|
+
const normalizedXPath = normalizeXPath(trimmedSelector);
|
|
294
|
+
targetElement = getElementFromXPath(
|
|
295
|
+
tempDiv,
|
|
296
|
+
normalizedXPath
|
|
297
|
+
);
|
|
298
|
+
if (targetElement) {
|
|
299
|
+
createLocator(locator, {
|
|
300
|
+
value: trimmedSelector,
|
|
301
|
+
isRecorded: String(locator.isRecorded).includes("N")
|
|
302
|
+
? "N"
|
|
303
|
+
: "Y",
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
} else {
|
|
308
|
+
targetElement = currentElement.querySelector(trimmedSelector);
|
|
309
|
+
if (!targetElement) {
|
|
310
|
+
targetElement = getElementFromShadowRoot(
|
|
311
|
+
currentElement,
|
|
312
|
+
trimmedSelector
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
if (
|
|
318
|
+
!targetElement &&
|
|
319
|
+
isSvg(currentElement) &&
|
|
320
|
+
!locator.type.match("dynamic")
|
|
321
|
+
) {
|
|
322
|
+
targetElement = currentElement.querySelector(trimmedSelector);
|
|
323
|
+
}
|
|
324
|
+
currentElement = targetElement;
|
|
325
|
+
} else {
|
|
326
|
+
console.error("Element not found at:", selector);
|
|
327
|
+
break;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
const locatorExists = (name: string, value: string): boolean => {
|
|
333
|
+
const key = `${name}:${value}`;
|
|
334
|
+
return finalLocatorsSet.has(key);
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
if (targetElement) {
|
|
338
|
+
const idValue = getId(targetElement);
|
|
339
|
+
if (idValue && !locatorExists("id", idValue)) {
|
|
340
|
+
record.locators.forEach((loc) => {
|
|
341
|
+
createLocator(loc, {
|
|
342
|
+
name: "id",
|
|
343
|
+
value: idValue,
|
|
344
|
+
isRecorded:
|
|
345
|
+
loc.value === idValue && loc.name === "id"
|
|
346
|
+
? loc.isRecorded
|
|
347
|
+
: "Y",
|
|
348
|
+
isSelfHealed:
|
|
349
|
+
loc.value === idValue && loc.name === "id"
|
|
350
|
+
? loc.isSelfHealed
|
|
351
|
+
: "Y",
|
|
352
|
+
});
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
const textValue = getVisibleText(targetElement);
|
|
357
|
+
if (textValue) {
|
|
358
|
+
record.locators.forEach((loc) => {
|
|
359
|
+
createLocator(loc, {
|
|
360
|
+
name: "linkText",
|
|
361
|
+
type: "static",
|
|
362
|
+
value: textValue,
|
|
363
|
+
isRecorded: loc.value === textValue ? loc.isRecorded : "Y",
|
|
364
|
+
isSelfHealed:
|
|
365
|
+
loc.value === textValue ? loc.isSelfHealed : "Y",
|
|
366
|
+
});
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
const nameLocator = getName(targetElement);
|
|
371
|
+
if (nameLocator && !locatorExists("name", nameLocator)) {
|
|
372
|
+
record.locators.forEach((loc) => {
|
|
373
|
+
createLocator(loc, {
|
|
374
|
+
name: "name",
|
|
375
|
+
type: "static",
|
|
376
|
+
value: nameLocator,
|
|
377
|
+
isRecorded:
|
|
378
|
+
loc.value === nameLocator && loc.name === "name"
|
|
379
|
+
? loc.isRecorded
|
|
380
|
+
: "Y",
|
|
381
|
+
isSelfHealed:
|
|
382
|
+
loc.value === nameLocator && loc.name === "name"
|
|
383
|
+
? loc.isSelfHealed
|
|
384
|
+
: "Y",
|
|
385
|
+
});
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
const classValue = getClassName(targetElement);
|
|
390
|
+
if (
|
|
391
|
+
classValue &&
|
|
392
|
+
classValue.trim() !== "" &&
|
|
393
|
+
!locatorExists("className", classValue)
|
|
394
|
+
) {
|
|
395
|
+
record.locators.forEach((loc) => {
|
|
396
|
+
createLocator(loc, {
|
|
397
|
+
name: "className",
|
|
398
|
+
value: classValue,
|
|
399
|
+
isRecorded:
|
|
400
|
+
loc.value === classValue && loc.name === "className"
|
|
401
|
+
? loc.isRecorded
|
|
402
|
+
: "Y",
|
|
403
|
+
isSelfHealed:
|
|
404
|
+
loc.value === classValue && loc.name === "className"
|
|
405
|
+
? loc.isSelfHealed
|
|
406
|
+
: "Y",
|
|
407
|
+
});
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
const fnValue = parseDOM(targetElement, document, false, true);
|
|
412
|
+
record.locators.forEach((loc) => {
|
|
413
|
+
createLocator(loc, {
|
|
414
|
+
name: 'xpath',
|
|
415
|
+
value: fnValue,
|
|
416
|
+
isRecorded: fnValue?.find((x) => x.value === loc.value)
|
|
417
|
+
? loc.isRecorded
|
|
418
|
+
: "Y",
|
|
419
|
+
isSelfHealed: fnValue?.find((x) => x.value === loc.value)
|
|
420
|
+
? loc.isSelfHealed
|
|
421
|
+
: "Y",
|
|
422
|
+
});
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
for (const locator of record.locators) {
|
|
426
|
+
try {
|
|
427
|
+
for (const loc of record.locators) {
|
|
428
|
+
if (!loc.value) continue;
|
|
429
|
+
|
|
430
|
+
for (const relation of relations) {
|
|
431
|
+
if (loc.value.includes(relation)) {
|
|
432
|
+
const relativeXpath = checkReferenceElementIsValid(
|
|
433
|
+
loc.value,
|
|
434
|
+
relation,
|
|
435
|
+
tempDiv
|
|
436
|
+
);
|
|
437
|
+
if (relativeXpath) {
|
|
438
|
+
createLocator(loc, {
|
|
439
|
+
name: "xpath",
|
|
440
|
+
value: relativeXpath,
|
|
441
|
+
isRecorded:
|
|
442
|
+
locator.isRecorded !== "" &&
|
|
443
|
+
locator.isRecorded !== null
|
|
444
|
+
? locator.isRecorded
|
|
445
|
+
: "Y",
|
|
446
|
+
});
|
|
447
|
+
break;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
} catch (error) {
|
|
453
|
+
console.error("Error processing locator:", locator, error);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
const finalAutoHealedLocators = finalLocators.map((obj) => ({
|
|
458
|
+
...obj,
|
|
459
|
+
value: cleanLocatorValue(obj.value, obj.name, obj.isRecorded),
|
|
460
|
+
}));
|
|
461
|
+
|
|
462
|
+
const finalUniqueAutoHealedLocators: Locator[] =
|
|
463
|
+
finalAutoHealedLocators.reduce(
|
|
464
|
+
(unique: Locator[], locator: Locator) => {
|
|
465
|
+
if (
|
|
466
|
+
locator.value &&
|
|
467
|
+
!unique.some((l: Locator) => l.value === locator.value)
|
|
468
|
+
) {
|
|
469
|
+
unique.push(locator);
|
|
470
|
+
}
|
|
471
|
+
return unique;
|
|
472
|
+
},
|
|
473
|
+
[] as Locator[]
|
|
474
|
+
);
|
|
475
|
+
|
|
476
|
+
const jsonResult = [
|
|
477
|
+
{
|
|
478
|
+
name: `${record.name}`,
|
|
479
|
+
desc: `${record.desc}`,
|
|
480
|
+
type: `${record.type}`,
|
|
481
|
+
locators: finalUniqueAutoHealedLocators.filter(
|
|
482
|
+
(locator) => locator?.value != null && locator.value !== ""
|
|
483
|
+
),
|
|
484
|
+
isShared: `${record.isShared}`,
|
|
485
|
+
projectId: `${record.projectId}`,
|
|
486
|
+
projectType: `${record.projectType}`,
|
|
487
|
+
isRecorded: `${record.isRecorded}`,
|
|
488
|
+
folder: `${record.folder}`,
|
|
489
|
+
parentId: `${record.parentId}`,
|
|
490
|
+
parentName: `${record.parentName}`,
|
|
491
|
+
platform: `${record.platform}`,
|
|
492
|
+
licenseId: `${record.licenseId}`,
|
|
493
|
+
licenseType: `${record.licenseType}`,
|
|
494
|
+
userId: `${record.userId}`,
|
|
495
|
+
},
|
|
496
|
+
];
|
|
497
|
+
|
|
498
|
+
return jsonResult;
|
|
499
|
+
}
|
|
500
|
+
} catch (error) {
|
|
501
|
+
console.error("Error processing locator:", locator, error);
|
|
502
|
+
continue;
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
} catch (error) {
|
|
506
|
+
console.error("Error processing locator:", locator, error);
|
|
507
|
+
continue;
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
return null;
|
|
511
|
+
};
|
|
512
|
+
|
|
513
|
+
export { getElementsFromHTML };
|