ff-dom 1.0.18-beta.6 → 2.0.0

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.
@@ -1,6 +1,6 @@
1
1
  import { ElementRecord, Locator } from "../types/locator.ts";
2
2
  import { parseDOM } from "./xpath.ts";
3
- import { isSvg, normalizeXPath } from "./xpathHelpers.ts";
3
+ import { normalizeXPath } from "./xpathHelpers.ts";
4
4
 
5
5
  type ElementDetails = {
6
6
  id?: string | null;
@@ -18,12 +18,30 @@ type ElementDetails = {
18
18
  };
19
19
 
20
20
  const getElementFromShadowRoot = (
21
- element: Element,
21
+ el: Element | ShadowRoot,
22
22
  selector: string
23
23
  ): Element | null => {
24
- const shadowRoot = (element as HTMLElement).shadowRoot;
25
- if (shadowRoot && !selector.includes("dynamic")) {
26
- return shadowRoot.querySelector(selector);
24
+ // const shadowRoot = (element as HTMLElement).shadowRoot;
25
+ // if (shadowRoot && !selector.includes("dynamic")) {
26
+ // return shadowRoot.querySelector(selector);
27
+ // }
28
+
29
+ const elements = Array.from(el.querySelectorAll('*'));
30
+
31
+ try {
32
+ for (let i = 0; i < elements.length; i++) {
33
+ if (elements[i].shadowRoot) {
34
+ const { shadowRoot } = elements[i];
35
+ if (shadowRoot) {
36
+ getElementFromShadowRoot(shadowRoot, selector);
37
+ if (shadowRoot && !selector.includes("dynamic")) {
38
+ return shadowRoot.querySelector(selector);
39
+ }
40
+ }
41
+ }
42
+ }
43
+ } catch (error) {
44
+ console.log(error);
27
45
  }
28
46
  return null;
29
47
  };
@@ -65,17 +83,16 @@ const relations: string[] = [
65
83
  ];
66
84
 
67
85
  function getElementFromXPath(
68
- tempDiv: HTMLElement,
86
+ docmt: Document,
69
87
  xpath: string
70
88
  ): Element | null {
71
- let currentElement: Element | null = tempDiv;
72
- const window = currentElement.ownerDocument.defaultView;
89
+ const window = docmt.defaultView;
73
90
  if (!window) return null;
74
91
 
75
92
  const xpathEvaluator = new window.XPathEvaluator();
76
93
  const xpathResult = xpathEvaluator.evaluate(
77
94
  xpath,
78
- currentElement.ownerDocument, //here even tempDiv can be passed
95
+ docmt,
79
96
  null,
80
97
  window.XPathResult.FIRST_ORDERED_NODE_TYPE,
81
98
  null
@@ -86,19 +103,18 @@ function getElementFromXPath(
86
103
  function checkReferenceElementIsValid(
87
104
  locator: string,
88
105
  relation: string,
89
- tempDiv: HTMLElement
106
+ docmt: Document
90
107
  ): string | null {
91
108
  if (locator.includes(relation)) {
92
109
  const locatotSplitArray: string[] = locator.split(relation);
93
110
  const sourceLoc = locatotSplitArray[0].trim();
94
- let currentElement: Element | null = tempDiv;
95
- const window = currentElement.ownerDocument.defaultView;
111
+ const window = docmt.defaultView;
96
112
  if (!window) return null;
97
113
  if (!locator.includes("dynamic")) {
98
114
  const xpathEvaluator = new window.XPathEvaluator();
99
115
  const xpathResult = xpathEvaluator.evaluate(
100
116
  sourceLoc,
101
- currentElement.ownerDocument,
117
+ docmt,
102
118
  null,
103
119
  window.XPathResult.FIRST_ORDERED_NODE_TYPE,
104
120
  null
@@ -108,7 +124,7 @@ function checkReferenceElementIsValid(
108
124
  if (sourceElement) {
109
125
  const xpathResultComplete = xpathEvaluator.evaluate(
110
126
  locator,
111
- currentElement.ownerDocument,
127
+ docmt,
112
128
  null,
113
129
  window.XPathResult.FIRST_ORDERED_NODE_TYPE,
114
130
  null
@@ -135,10 +151,7 @@ const getElementsFromHTML = (
135
151
  record: ElementRecord,
136
152
  docmt: Document
137
153
  ): ElementDetails | null => {
138
- const document = docmt;
139
- // global.SVGElement = document.defaultView?.SVGElement!;
140
- const tempDiv = document.createElement("div");
141
- const elementsToRemove = document.querySelectorAll(
154
+ const elementsToRemove = docmt.querySelectorAll(
142
155
  "script, style, link[rel='stylesheet'], meta, noscript, embed, object, param, source, svg"
143
156
  );
144
157
 
@@ -148,7 +161,6 @@ const getElementsFromHTML = (
148
161
  });
149
162
  }
150
163
 
151
- tempDiv.innerHTML = document.body?.innerHTML;
152
164
  const finalLocatorsSet: Set<string> = new Set();
153
165
  let finalLocators: any[] = [];
154
166
 
@@ -247,170 +259,158 @@ const getElementsFromHTML = (
247
259
  break locators;
248
260
  }
249
261
 
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
- }
262
+ try {
263
+ let targetElement: Element | null = null;
264
+ if (locator.value.startsWith("iframe")) {
265
+ const iframe = docmt.querySelector(
266
+ locator.value
267
+ ) as HTMLIFrameElement;
268
+ if (iframe) {
269
+ const iframeDocument =
270
+ iframe.contentDocument || iframe.contentWindow?.document;
271
+ if (iframeDocument) {
272
+ targetElement = iframeDocument.querySelector(
273
+ locator.value.slice(6)
274
+ );
265
275
  }
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);
276
+ }
277
+ } else {
278
+ const selectors = locator.value.split(">>>"); // Custom delimiter for shadow DOM
279
+
280
+ for (const selector of selectors) {
281
+ if (docmt) {
282
+ const trimmedSelector = selector.trim();
283
+ if (
284
+ locator.name.includes("id") ||
285
+ trimmedSelector.startsWith("#")
286
+ ) {
287
+ targetElement = docmt.querySelector(
288
+ "#" + trimmedSelector
289
+ );
290
+ } else if (
291
+ locator.name.includes("className") ||
292
+ trimmedSelector.startsWith(".")
293
+ ) {
294
+ targetElement = docmt.querySelector(
295
+ "." + trimmedSelector
296
+ );
297
+ } else if (
298
+ (locator.name.includes("xpath") ||
299
+ trimmedSelector.startsWith("//")) &&
300
+ !locator.type.match("dynamic")
301
+ ) {
302
+ const normalizedXPath = normalizeXPath(trimmedSelector);
303
+ targetElement = getElementFromXPath(
304
+ docmt,
305
+ normalizedXPath
306
+ );
307
+ if (targetElement) {
308
+ createLocator(locator, {
309
+ value: trimmedSelector,
310
+ isRecorded: String(locator.isRecorded).includes("N")
311
+ ? "N"
312
+ : "Y",
313
+ });
323
314
  }
324
- currentElement = targetElement;
325
315
  } else {
326
- console.error("Element not found at:", selector);
327
- break;
316
+ targetElement = docmt.querySelector(trimmedSelector);
317
+ if (!targetElement) {
318
+ targetElement = getElementFromShadowRoot(
319
+ docmt.body,
320
+ trimmedSelector
321
+ );
322
+ }
328
323
  }
324
+ } else {
325
+ console.error("Element not found at:", selector);
326
+ break;
329
327
  }
330
328
  }
329
+ }
331
330
 
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
- });
331
+ const locatorExists = (name: string, value: string): boolean => {
332
+ const key = `${name}:${value}`;
333
+ return finalLocatorsSet.has(key);
334
+ };
335
+
336
+ if (targetElement) {
337
+ const idValue = getId(targetElement);
338
+ if (idValue && !locatorExists("id", idValue)) {
339
+ record.locators.forEach((loc) => {
340
+ createLocator(loc, {
341
+ name: "id",
342
+ value: idValue,
343
+ isRecorded:
344
+ loc.value === idValue && loc.name === "id"
345
+ ? loc.isRecorded
346
+ : "Y",
347
+ isSelfHealed:
348
+ loc.value === idValue && loc.name === "id"
349
+ ? loc.isSelfHealed
350
+ : "Y",
353
351
  });
354
- }
352
+ });
353
+ }
355
354
 
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
- });
355
+ const textValue = getVisibleText(targetElement);
356
+ if (textValue) {
357
+ record.locators.forEach((loc) => {
358
+ createLocator(loc, {
359
+ name: "linkText",
360
+ type: "static",
361
+ value: textValue,
362
+ isRecorded: loc.value === textValue ? loc.isRecorded : "Y",
363
+ isSelfHealed:
364
+ loc.value === textValue ? loc.isSelfHealed : "Y",
367
365
  });
368
- }
366
+ });
367
+ }
369
368
 
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
- });
369
+ const nameLocator = getName(targetElement);
370
+ if (nameLocator && !locatorExists("name", nameLocator)) {
371
+ record.locators.forEach((loc) => {
372
+ createLocator(loc, {
373
+ name: "name",
374
+ type: "static",
375
+ value: nameLocator,
376
+ isRecorded:
377
+ loc.value === nameLocator && loc.name === "name"
378
+ ? loc.isRecorded
379
+ : "Y",
380
+ isSelfHealed:
381
+ loc.value === nameLocator && loc.name === "name"
382
+ ? loc.isSelfHealed
383
+ : "Y",
386
384
  });
387
- }
385
+ });
386
+ }
388
387
 
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
- });
388
+ const classValue = getClassName(targetElement);
389
+ if (
390
+ classValue &&
391
+ classValue.trim() !== "" &&
392
+ !locatorExists("className", classValue)
393
+ ) {
394
+ record.locators.forEach((loc) => {
395
+ createLocator(loc, {
396
+ name: "className",
397
+ value: classValue,
398
+ isRecorded:
399
+ loc.value === classValue && loc.name === "className"
400
+ ? loc.isRecorded
401
+ : "Y",
402
+ isSelfHealed:
403
+ loc.value === classValue && loc.name === "className"
404
+ ? loc.isSelfHealed
405
+ : "Y",
408
406
  });
409
- }
407
+ });
408
+ }
410
409
 
411
- const xpathResults = parseDOM(targetElement, document, false, true);
412
- xpathResults.forEach((result) => {
413
- const fnValue = result.value;
410
+ const xpathResults = parseDOM(targetElement, docmt, false, true);
411
+ xpathResults.forEach((result) => {
412
+ const fnValue = result.value;
413
+ if (fnValue && !locatorExists(result.key.replace('xpath by', '').trim(), fnValue)) {
414
414
  record.locators.forEach((loc) => {
415
415
  createLocator(loc, {
416
416
  name: 'xpath',
@@ -419,87 +419,87 @@ const getElementsFromHTML = (
419
419
  isSelfHealed: loc.value === fnValue ? loc.isSelfHealed : 'Y',
420
420
  });
421
421
  });
422
- })
423
-
424
- for (const locator of record.locators) {
425
- try {
426
- for (const loc of record.locators) {
427
- if (!loc.value) continue;
428
-
429
- for (const relation of relations) {
430
- if (loc.value.includes(relation)) {
431
- const relativeXpath = checkReferenceElementIsValid(
432
- loc.value,
433
- relation,
434
- tempDiv
435
- );
436
- if (relativeXpath) {
437
- createLocator(loc, {
438
- name: "xpath",
439
- value: relativeXpath,
440
- isRecorded:
441
- locator.isRecorded !== "" &&
442
- locator.isRecorded !== null
443
- ? locator.isRecorded
444
- : "Y",
445
- });
446
- break;
447
- }
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
+ docmt
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
448
  }
449
449
  }
450
450
  }
451
- } catch (error) {
452
- console.error("Error processing locator:", locator, error);
453
451
  }
452
+ } catch (error) {
453
+ console.error("Error processing locator:", locator, error);
454
454
  }
455
+ }
455
456
 
456
- const finalAutoHealedLocators = finalLocators.map((obj) => ({
457
- ...obj,
458
- value: cleanLocatorValue(obj.value, obj.name, obj.isRecorded),
459
- }));
460
-
461
- const finalUniqueAutoHealedLocators: Locator[] =
462
- finalAutoHealedLocators.reduce(
463
- (unique: Locator[], locator: Locator) => {
464
- if (
465
- locator.value &&
466
- !unique.some((l: Locator) => l.value === locator.value)
467
- ) {
468
- unique.push(locator);
469
- }
470
- return unique;
471
- },
472
- [] as Locator[]
473
- );
457
+ const finalAutoHealedLocators = finalLocators.map((obj) => ({
458
+ ...obj,
459
+ value: cleanLocatorValue(obj.value, obj.name, obj.isRecorded),
460
+ }));
474
461
 
475
- const jsonResult = [
476
- {
477
- name: `${record.name}`,
478
- desc: `${record.desc}`,
479
- type: `${record.type}`,
480
- locators: finalUniqueAutoHealedLocators.filter(
481
- (locator) => locator?.value != null && locator.value !== ""
482
- ),
483
- isShared: `${record.isShared}`,
484
- projectId: `${record.projectId}`,
485
- projectType: `${record.projectType}`,
486
- isRecorded: `${record.isRecorded}`,
487
- folder: `${record.folder}`,
488
- parentId: `${record.parentId}`,
489
- parentName: `${record.parentName}`,
490
- platform: `${record.platform}`,
491
- licenseId: `${record.licenseId}`,
492
- licenseType: `${record.licenseType}`,
493
- userId: `${record.userId}`,
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;
494
472
  },
495
- ];
496
-
497
- return jsonResult;
498
- }
499
- } catch (error) {
500
- console.error("Error processing locator:", locator, error);
501
- continue;
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;
502
499
  }
500
+ } catch (error) {
501
+ console.error("Error processing locator:", locator, error);
502
+ continue;
503
503
  }
504
504
  } catch (error) {
505
505
  console.error("Error processing locator:", locator, error);
@@ -1224,9 +1224,9 @@ export const getReferenceElementsXpath = (
1224
1224
  return xpaths1;
1225
1225
  };
1226
1226
 
1227
- export const parseXml = (xmlStr: string): Document | null => {
1227
+ export const parseXml = (xmlStr: string, type: DOMParserSupportedType): Document | null => {
1228
1228
  if (window.DOMParser) {
1229
- return new window.DOMParser().parseFromString(xmlStr, "text/xml");
1229
+ return new window.DOMParser().parseFromString(xmlStr, type);
1230
1230
  }
1231
1231
 
1232
1232
  return null;