ff-dom 1.0.15 → 1.0.17
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/dist/utils/getElementsFromHTML.js +174 -242
- package/package.json +1 -1
- package/src/utils/getElementsFromHTML.ts +204 -298
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
import { JSDOM, VirtualConsole } from "jsdom";
|
|
1
|
+
import { JSDOM, VirtualConsole } from 'jsdom';
|
|
3
2
|
|
|
4
3
|
type ElementDetails = {
|
|
5
4
|
id?: string | null;
|
|
@@ -25,7 +24,7 @@ const getElementFromShadowRoot = (
|
|
|
25
24
|
selector: string
|
|
26
25
|
): Element | null => {
|
|
27
26
|
const shadowRoot = (element as HTMLElement).shadowRoot;
|
|
28
|
-
if (shadowRoot && !selector.includes(
|
|
27
|
+
if (shadowRoot && !selector.includes('dynamic')) {
|
|
29
28
|
return shadowRoot.querySelector(selector);
|
|
30
29
|
}
|
|
31
30
|
return null;
|
|
@@ -33,8 +32,8 @@ const getElementFromShadowRoot = (
|
|
|
33
32
|
|
|
34
33
|
const isSVGElement = (element: Element): boolean => {
|
|
35
34
|
return (
|
|
36
|
-
typeof window !==
|
|
37
|
-
typeof SVGElement !==
|
|
35
|
+
typeof window !== 'undefined' &&
|
|
36
|
+
typeof SVGElement !== 'undefined' &&
|
|
38
37
|
element instanceof SVGElement
|
|
39
38
|
);
|
|
40
39
|
};
|
|
@@ -44,13 +43,7 @@ const getXPath = (element: Element, xpath: string): string | null => {
|
|
|
44
43
|
if (!window) return null;
|
|
45
44
|
|
|
46
45
|
const xpathEvaluator = new window.XPathEvaluator();
|
|
47
|
-
const xpathResult = xpathEvaluator.evaluate(
|
|
48
|
-
xpath,
|
|
49
|
-
element.ownerDocument,
|
|
50
|
-
null,
|
|
51
|
-
window.XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
|
|
52
|
-
null
|
|
53
|
-
);
|
|
46
|
+
const xpathResult = xpathEvaluator.evaluate(xpath, element.ownerDocument, null, window.XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
|
|
54
47
|
|
|
55
48
|
return isUnique(xpathResult) ? xpath : null;
|
|
56
49
|
};
|
|
@@ -99,24 +92,22 @@ const getXpathByName = (element: Element): string | null => {
|
|
|
99
92
|
const selector = element.nodeName.toLowerCase();
|
|
100
93
|
const elementEl = element as HTMLElement;
|
|
101
94
|
|
|
102
|
-
if (elementEl.hasAttribute(
|
|
103
|
-
const attrValue = elementEl.getAttribute(
|
|
95
|
+
if (elementEl.hasAttribute('name')) {
|
|
96
|
+
const attrValue = elementEl.getAttribute('name');
|
|
104
97
|
const xpath = `//${selector}[@name='${attrValue}']`;
|
|
105
98
|
return getXPath(element, xpath) || null;
|
|
106
99
|
}
|
|
107
|
-
|
|
108
100
|
return null;
|
|
109
101
|
};
|
|
110
102
|
|
|
111
103
|
const getName = (element: Element): string | null => {
|
|
112
104
|
const elementEl = element as HTMLElement;
|
|
113
105
|
|
|
114
|
-
if (elementEl.hasAttribute(
|
|
115
|
-
const attrValue = elementEl.getAttribute(
|
|
106
|
+
if (elementEl.hasAttribute('name')) {
|
|
107
|
+
const attrValue = elementEl.getAttribute('name');
|
|
116
108
|
const name = `${attrValue}`;
|
|
117
109
|
return name || null;
|
|
118
110
|
}
|
|
119
|
-
|
|
120
111
|
return null;
|
|
121
112
|
};
|
|
122
113
|
|
|
@@ -124,12 +115,11 @@ const getXpathByPlaceholder = (element: Element): string | null => {
|
|
|
124
115
|
const selector = element.nodeName.toLowerCase();
|
|
125
116
|
const elementEl = element as HTMLElement;
|
|
126
117
|
|
|
127
|
-
if (elementEl.hasAttribute(
|
|
128
|
-
const attrValue = elementEl.getAttribute(
|
|
118
|
+
if (elementEl.hasAttribute('placeholder')) {
|
|
119
|
+
const attrValue = elementEl.getAttribute('placeholder');
|
|
129
120
|
const xpath = `//${selector}[@placeholder='${attrValue}']`;
|
|
130
121
|
return getXPath(element, xpath) || null;
|
|
131
122
|
}
|
|
132
|
-
|
|
133
123
|
return null;
|
|
134
124
|
};
|
|
135
125
|
|
|
@@ -137,12 +127,11 @@ const getXpathByType = (element: Element): string | null => {
|
|
|
137
127
|
const selector = element.nodeName.toLowerCase();
|
|
138
128
|
const elementEl = element as HTMLElement;
|
|
139
129
|
|
|
140
|
-
if (elementEl.hasAttribute(
|
|
141
|
-
const attrValue = elementEl.getAttribute(
|
|
130
|
+
if (elementEl.hasAttribute('type')) {
|
|
131
|
+
const attrValue = elementEl.getAttribute('type');
|
|
142
132
|
const xpath = `//${selector}[@type='${attrValue}']`;
|
|
143
133
|
return getXPath(element, xpath) || null;
|
|
144
134
|
}
|
|
145
|
-
|
|
146
135
|
return null;
|
|
147
136
|
};
|
|
148
137
|
|
|
@@ -166,33 +155,33 @@ const getXPathAbsolute = (element: Element): string => {
|
|
|
166
155
|
path.unshift(selector);
|
|
167
156
|
element = element.parentNode as Element;
|
|
168
157
|
}
|
|
169
|
-
return
|
|
158
|
+
return '//' + path.join('/');
|
|
170
159
|
};
|
|
171
160
|
|
|
172
161
|
const relations: string[] = [
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
162
|
+
'/preceding-sibling',
|
|
163
|
+
'/following-sibling',
|
|
164
|
+
'/parent',
|
|
165
|
+
'/descendant',
|
|
166
|
+
'/ancestor',
|
|
167
|
+
'/self',
|
|
168
|
+
'/ancestor-or-self',
|
|
169
|
+
'/child',
|
|
170
|
+
'/preceding',
|
|
171
|
+
'/following',
|
|
183
172
|
];
|
|
184
173
|
|
|
185
174
|
const normalizeXPath = (xpath: string): string => {
|
|
175
|
+
// Replace text() comparisons like text() = 'something' with normalize-space(.) = 'something'
|
|
176
|
+
// to avoid issues with whitespace differences
|
|
186
177
|
xpath = xpath.replace(/text\(\)\s*=\s*['"](.+?)['"]/g, "normalize-space(.)='$1'");
|
|
187
|
-
xpath = xpath.replace(/\.\s*=\s*['"](.+?)['"]/g, "normalize-space(.)='$1'");
|
|
188
178
|
|
|
179
|
+
// Replace direct . = 'something' comparisons with normalize-space(.) = 'something' for consistent text matching
|
|
180
|
+
xpath = xpath.replace(/\.\s*=\s*['"](.+?)['"]/g, "normalize-space(.)='$1'");
|
|
189
181
|
return xpath;
|
|
190
182
|
};
|
|
191
183
|
|
|
192
|
-
function getElementFromXPath(
|
|
193
|
-
tempDiv: HTMLElement,
|
|
194
|
-
xpath: string
|
|
195
|
-
): Element | null {
|
|
184
|
+
function getElementFromXPath(tempDiv: HTMLElement, xpath: string): Element | null {
|
|
196
185
|
let currentElement: Element | null = tempDiv;
|
|
197
186
|
const window = currentElement.ownerDocument.defaultView;
|
|
198
187
|
if (!window) return null;
|
|
@@ -208,18 +197,14 @@ function getElementFromXPath(
|
|
|
208
197
|
return xpathResult.singleNodeValue as Element | null;
|
|
209
198
|
}
|
|
210
199
|
|
|
211
|
-
function checkReferenceElementIsValid(
|
|
212
|
-
locator: string,
|
|
213
|
-
relation: string,
|
|
214
|
-
tempDiv: HTMLElement
|
|
215
|
-
): string | null {
|
|
200
|
+
function checkReferenceElementIsValid(locator: string, relation: string, tempDiv: HTMLElement): string | null {
|
|
216
201
|
if (locator.includes(relation)) {
|
|
217
202
|
const locatotSplitArray: string[] = locator.split(relation);
|
|
218
203
|
const sourceLoc = locatotSplitArray[0].trim();
|
|
219
204
|
let currentElement: Element | null = tempDiv;
|
|
220
205
|
const window = currentElement.ownerDocument.defaultView;
|
|
221
206
|
if (!window) return null;
|
|
222
|
-
if (!locator.includes(
|
|
207
|
+
if (!locator.includes('dynamic')) {
|
|
223
208
|
const xpathEvaluator = new window.XPathEvaluator();
|
|
224
209
|
const xpathResult = xpathEvaluator.evaluate(
|
|
225
210
|
sourceLoc,
|
|
@@ -244,12 +229,12 @@ function checkReferenceElementIsValid(
|
|
|
244
229
|
relativeXpath = locator;
|
|
245
230
|
return relativeXpath;
|
|
246
231
|
} else {
|
|
247
|
-
console.error(
|
|
232
|
+
console.error('Complete Locator is Invalid:', locator);
|
|
248
233
|
relativeXpath = locator;
|
|
249
234
|
return relativeXpath;
|
|
250
235
|
}
|
|
251
236
|
} else {
|
|
252
|
-
console.error(
|
|
237
|
+
console.error('Source Locator Not Found:', sourceLoc);
|
|
253
238
|
}
|
|
254
239
|
}
|
|
255
240
|
}
|
|
@@ -262,6 +247,7 @@ interface Locator {
|
|
|
262
247
|
reference: string;
|
|
263
248
|
status: string;
|
|
264
249
|
isRecorded: string;
|
|
250
|
+
isSelfHealed?: string;
|
|
265
251
|
}
|
|
266
252
|
|
|
267
253
|
const getElementsFromHTML = (
|
|
@@ -284,8 +270,8 @@ const getElementsFromHTML = (
|
|
|
284
270
|
): ElementDetails | null => {
|
|
285
271
|
const virtualConsole = new VirtualConsole();
|
|
286
272
|
const dom = new JSDOM(htmlString, {
|
|
287
|
-
resources:
|
|
288
|
-
runScripts:
|
|
273
|
+
resources: 'usable',
|
|
274
|
+
runScripts: 'outside-only', // Prevents inline script execution in JSDOM
|
|
289
275
|
pretendToBeVisual: true,
|
|
290
276
|
virtualConsole,
|
|
291
277
|
includeNodeLocations: true,
|
|
@@ -293,10 +279,8 @@ const getElementsFromHTML = (
|
|
|
293
279
|
|
|
294
280
|
const document = dom.window.document;
|
|
295
281
|
global.SVGElement = dom.window.SVGElement;
|
|
296
|
-
const tempDiv = document.createElement(
|
|
297
|
-
const elementsToRemove = document.querySelectorAll(
|
|
298
|
-
"script, style, link[rel='stylesheet'], meta, noscript, embed, object, param, source, svg"
|
|
299
|
-
);
|
|
282
|
+
const tempDiv = document.createElement('div');
|
|
283
|
+
const elementsToRemove = document.querySelectorAll("script, style, link[rel='stylesheet'], meta, noscript, embed, object, param, source, svg");
|
|
300
284
|
|
|
301
285
|
if (elementsToRemove) {
|
|
302
286
|
elementsToRemove.forEach((tag) => {
|
|
@@ -305,52 +289,101 @@ const getElementsFromHTML = (
|
|
|
305
289
|
}
|
|
306
290
|
|
|
307
291
|
tempDiv.innerHTML = document.body.innerHTML;
|
|
308
|
-
|
|
292
|
+
const finalLocatorsSet: Set<string> = new Set();
|
|
293
|
+
let finalLocators: any[] = [];
|
|
294
|
+
|
|
295
|
+
function createLocator(base: any, overrides: Partial<any> = {}) {
|
|
296
|
+
const newLocator: any = {
|
|
297
|
+
name: overrides.name ?? base?.name,
|
|
298
|
+
type: overrides.type ?? base?.type,
|
|
299
|
+
value: overrides.value ?? base?.value,
|
|
300
|
+
reference: overrides.reference ?? base?.reference,
|
|
301
|
+
status: overrides.status ?? base?.status,
|
|
302
|
+
isRecorded: overrides.isRecorded ?? base?.isRecorded,
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
if (overrides.hasOwnProperty('isSelfHealed')) {
|
|
306
|
+
newLocator.isSelfHealed = overrides.isSelfHealed;
|
|
307
|
+
} else if (base?.hasOwnProperty('isSelfHealed')) {
|
|
308
|
+
newLocator.isSelfHealed = base.isSelfHealed;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
pushUniqueLocator(newLocator);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function pushUniqueLocator(obj: any) {
|
|
315
|
+
const key = `${obj.name}:${obj.value}`;
|
|
316
|
+
if (!finalLocatorsSet.has(key)) {
|
|
317
|
+
finalLocatorsSet.add(key);
|
|
318
|
+
finalLocators.push(obj);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/** Locator Value Cleaner (Handles Special Scenarios) **/
|
|
323
|
+
const cleanLocatorValue = (val: string | null | undefined, type?: string, isRecorded?: string): string | null => {
|
|
324
|
+
if (!val) return null;
|
|
325
|
+
|
|
326
|
+
let cleaned = val.trim();
|
|
327
|
+
|
|
328
|
+
// Return null for empty or literal "null"
|
|
329
|
+
if (!cleaned || cleaned.toLowerCase() === 'null') return null;
|
|
330
|
+
|
|
331
|
+
// Unescape any escaped quotes
|
|
332
|
+
cleaned = cleaned.replace(/\\"/g, '"').replace(/\\'/g, "'");
|
|
333
|
+
|
|
334
|
+
// Remove surrounding single or double quotes
|
|
335
|
+
cleaned = cleaned.replace(/^['"](.+?)['"]$/, '$1');
|
|
336
|
+
|
|
337
|
+
// Replace double single quotes with a single quote inside XPath
|
|
338
|
+
cleaned = cleaned.replace(/''/g, "'");
|
|
339
|
+
|
|
340
|
+
// Normalize double quotes in XPath attribute selectors [@id="" -> [@id='']
|
|
341
|
+
cleaned = cleaned.replace(
|
|
342
|
+
/\[@(id|name)=['"]{2}(.+?)['"]{2}\]/g,
|
|
343
|
+
"[@$1='$2']"
|
|
344
|
+
);
|
|
345
|
+
|
|
346
|
+
// For DOM selectors (id or name), remove ALL quotes
|
|
347
|
+
if (type === 'id' || type === 'name') {
|
|
348
|
+
cleaned = cleaned.replace(/['"]/g, '').trim();
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
if(type === 'xpath' && isRecorded === 'Y' && !val.startsWith('//')) return null;
|
|
352
|
+
|
|
353
|
+
// Final check for empty strings
|
|
354
|
+
if (!cleaned || /^['"]{2}$/.test(cleaned)) return null;
|
|
355
|
+
|
|
356
|
+
return cleaned;
|
|
357
|
+
};
|
|
309
358
|
|
|
310
359
|
locators: for (const locator of locators) {
|
|
311
360
|
try {
|
|
312
|
-
const isRecorded = String(locator.isRecorded ||
|
|
313
|
-
const recordedNLocators = locators.filter((l) => l.isRecorded ===
|
|
361
|
+
const isRecorded = String(locator.isRecorded || '');
|
|
362
|
+
const recordedNLocators = locators.filter((l) => l.isRecorded === 'N');
|
|
314
363
|
|
|
315
364
|
if (recordedNLocators.length > 0) {
|
|
316
365
|
for (const locator of recordedNLocators) {
|
|
317
|
-
|
|
318
|
-
name: locator.name,
|
|
319
|
-
type: locator.type,
|
|
320
|
-
value: locator.value,
|
|
321
|
-
reference: locator.reference,
|
|
322
|
-
status: locator.status,
|
|
323
|
-
isRecorded: locator.isRecorded,
|
|
324
|
-
});
|
|
366
|
+
createLocator(locator);
|
|
325
367
|
}
|
|
326
368
|
}
|
|
327
369
|
|
|
328
|
-
|
|
329
|
-
const isDynamic = String(locator.value || locator.type || "");
|
|
370
|
+
const isDynamic = String(locator.value || locator.type || '');
|
|
330
371
|
if (
|
|
331
|
-
isDynamic.includes(
|
|
332
|
-
isDynamic.
|
|
333
|
-
isDynamic.includes("{") ||
|
|
334
|
-
isDynamic.includes("}")
|
|
372
|
+
isDynamic.includes('dynamic') || isDynamic.match('dynamic') ||
|
|
373
|
+
isDynamic.includes('{') || isDynamic.includes('}')
|
|
335
374
|
) {
|
|
336
|
-
|
|
337
|
-
name: locator.name,
|
|
338
|
-
type: locator.type,
|
|
339
|
-
value: locator.value,
|
|
340
|
-
reference: locator.reference,
|
|
341
|
-
status: locator.status,
|
|
342
|
-
isRecorded: locator.isRecorded,
|
|
343
|
-
});
|
|
375
|
+
createLocator(locator);
|
|
344
376
|
continue;
|
|
345
377
|
}
|
|
346
378
|
|
|
347
|
-
if (isShared.includes(
|
|
379
|
+
if (isShared.includes('Y')) {
|
|
348
380
|
break locators;
|
|
349
381
|
}
|
|
382
|
+
|
|
350
383
|
for (const relation of relations) {
|
|
351
384
|
try {
|
|
352
385
|
let targetElement: Element | null = null;
|
|
353
|
-
if (locator.value.startsWith(
|
|
386
|
+
if (locator.value.startsWith('iframe')) {
|
|
354
387
|
const iframe = tempDiv.querySelector(
|
|
355
388
|
locator.value
|
|
356
389
|
) as HTMLIFrameElement;
|
|
@@ -364,254 +397,139 @@ const getElementsFromHTML = (
|
|
|
364
397
|
}
|
|
365
398
|
}
|
|
366
399
|
} else {
|
|
367
|
-
const selectors = locator.value.split(
|
|
400
|
+
const selectors = locator.value.split('>>>'); // Custom delimiter for shadow DOM
|
|
368
401
|
let currentElement: Element | null = tempDiv;
|
|
369
402
|
|
|
370
403
|
for (const selector of selectors) {
|
|
371
404
|
if (currentElement) {
|
|
372
405
|
const trimmedSelector = selector.trim();
|
|
373
|
-
if (
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
targetElement = currentElement.querySelector(
|
|
378
|
-
"#" + trimmedSelector
|
|
379
|
-
);
|
|
406
|
+
if (locator.name.includes('id') || trimmedSelector.startsWith('#')) {
|
|
407
|
+
targetElement = currentElement.querySelector('#' + trimmedSelector);
|
|
408
|
+
} else if (locator.name.includes('className') || trimmedSelector.startsWith('.')) {
|
|
409
|
+
targetElement = currentElement.querySelector('.' + trimmedSelector);
|
|
380
410
|
} else if (
|
|
381
|
-
locator.name.includes(
|
|
382
|
-
|
|
383
|
-
) {
|
|
384
|
-
targetElement = currentElement.querySelector(
|
|
385
|
-
"." + trimmedSelector
|
|
386
|
-
);
|
|
387
|
-
} else if (
|
|
388
|
-
(locator.name.includes("xpath") ||
|
|
389
|
-
trimmedSelector.startsWith("//")) &&
|
|
390
|
-
!locator.type.match("dynamic")
|
|
411
|
+
(locator.name.includes('xpath') || trimmedSelector.startsWith('//')) &&
|
|
412
|
+
!locator.type.match('dynamic')
|
|
391
413
|
) {
|
|
392
414
|
if (tempDiv.innerHTML) {
|
|
393
415
|
const normalizedXPath = normalizeXPath(trimmedSelector);
|
|
394
|
-
targetElement = getElementFromXPath(
|
|
395
|
-
tempDiv,
|
|
396
|
-
normalizedXPath
|
|
397
|
-
);
|
|
416
|
+
targetElement = getElementFromXPath(tempDiv,normalizedXPath);
|
|
398
417
|
if (targetElement) {
|
|
399
|
-
|
|
400
|
-
name: locator.name,
|
|
401
|
-
type: locator.type,
|
|
418
|
+
createLocator(locator, {
|
|
402
419
|
value: trimmedSelector,
|
|
403
|
-
|
|
404
|
-
status: locator.status,
|
|
405
|
-
isRecorded: String(locator.isRecorded).includes("N")
|
|
406
|
-
? "N"
|
|
407
|
-
: "Y",
|
|
420
|
+
isRecorded: String(locator.isRecorded).includes('N') ? 'N' : 'Y',
|
|
408
421
|
});
|
|
409
422
|
}
|
|
410
423
|
}
|
|
411
424
|
} else {
|
|
412
425
|
targetElement = currentElement.querySelector(trimmedSelector);
|
|
413
426
|
if (!targetElement) {
|
|
414
|
-
targetElement = getElementFromShadowRoot(
|
|
415
|
-
currentElement,
|
|
416
|
-
trimmedSelector
|
|
417
|
-
);
|
|
427
|
+
targetElement = getElementFromShadowRoot(currentElement, trimmedSelector);
|
|
418
428
|
}
|
|
419
429
|
}
|
|
420
430
|
|
|
421
|
-
if (
|
|
422
|
-
!targetElement &&
|
|
423
|
-
isSVGElement(currentElement) &&
|
|
424
|
-
!locator.type.match("dynamic")
|
|
425
|
-
) {
|
|
431
|
+
if (!targetElement && isSVGElement(currentElement) && !locator.type.match('dynamic')) {
|
|
426
432
|
targetElement = currentElement.querySelector(trimmedSelector);
|
|
427
433
|
}
|
|
428
|
-
|
|
429
434
|
currentElement = targetElement;
|
|
430
435
|
} else {
|
|
431
|
-
console.error(
|
|
436
|
+
console.error('Element not found at:', selector);
|
|
432
437
|
break;
|
|
433
438
|
}
|
|
434
439
|
}
|
|
435
440
|
}
|
|
436
441
|
|
|
437
|
-
const locatorExists = (name: string, value: string) => {
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
loc.name === name &&
|
|
441
|
-
loc.value === value &&
|
|
442
|
-
(!loc.value.includes("dynamic") ||
|
|
443
|
-
!locator.type.match("dynamic"))
|
|
444
|
-
);
|
|
442
|
+
const locatorExists = (name: string, value: string): boolean => {
|
|
443
|
+
const key = `${name}:${value}`;
|
|
444
|
+
return finalLocatorsSet.has(key);
|
|
445
445
|
};
|
|
446
446
|
|
|
447
447
|
const xpathFunctions = [
|
|
448
|
-
{ name:
|
|
449
|
-
{ name:
|
|
450
|
-
{ name:
|
|
451
|
-
{ name:
|
|
452
|
-
{ name:
|
|
453
|
-
{ name:
|
|
454
|
-
{ name:
|
|
448
|
+
{ name: 'xpath', value: getXPathByText },
|
|
449
|
+
{ name: 'xpath', value: getXPathById },
|
|
450
|
+
{ name: 'xpath', value: getXPathByClass },
|
|
451
|
+
{ name: 'xpath', value: getXPathAbsolute },
|
|
452
|
+
{ name: 'xpath', value: getXpathByName },
|
|
453
|
+
{ name: 'xpath', value: getXpathByPlaceholder },
|
|
454
|
+
{ name: 'xpath', value: getXpathByType },
|
|
455
455
|
];
|
|
456
456
|
|
|
457
457
|
if (targetElement) {
|
|
458
458
|
const idValue = getId(targetElement);
|
|
459
|
-
if (idValue && !locatorExists(
|
|
459
|
+
if (idValue && !locatorExists('id', idValue)) {
|
|
460
460
|
locators.forEach((loc) => {
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
status: locator.status,
|
|
468
|
-
isRecorded: locator.isRecorded,
|
|
469
|
-
});
|
|
470
|
-
} else {
|
|
471
|
-
finalLocators.push({
|
|
472
|
-
name: "id",
|
|
473
|
-
type: locator.type,
|
|
474
|
-
value: idValue,
|
|
475
|
-
reference: locator.reference,
|
|
476
|
-
status: locator.status,
|
|
477
|
-
isRecorded: "Y",
|
|
478
|
-
isSelfHealed: "Y",
|
|
479
|
-
});
|
|
480
|
-
}
|
|
461
|
+
createLocator(loc, {
|
|
462
|
+
name: 'id',
|
|
463
|
+
value: idValue,
|
|
464
|
+
isRecorded: loc.value === idValue && loc.name === 'id' ? loc.isRecorded : 'Y',
|
|
465
|
+
isSelfHealed: loc.value === idValue && loc.name === 'id' ? loc.isSelfHealed : 'Y'
|
|
466
|
+
});
|
|
481
467
|
});
|
|
482
468
|
}
|
|
483
|
-
|
|
484
|
-
|
|
469
|
+
|
|
470
|
+
const textValue = getVisibleText(targetElement);
|
|
471
|
+
if (textValue) {
|
|
485
472
|
locators.forEach((loc) => {
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
isRecorded: locator.isRecorded,
|
|
494
|
-
});
|
|
495
|
-
} else {
|
|
496
|
-
finalLocators.push({
|
|
497
|
-
name: "linkText",
|
|
498
|
-
type: "static",
|
|
499
|
-
value: textValue,
|
|
500
|
-
reference: locator.reference,
|
|
501
|
-
status: locator.status,
|
|
502
|
-
isRecorded: "Y",
|
|
503
|
-
isSelfHealed: "Y",
|
|
504
|
-
});
|
|
505
|
-
}
|
|
473
|
+
createLocator(loc, {
|
|
474
|
+
name: 'linkText',
|
|
475
|
+
type: 'static',
|
|
476
|
+
value: textValue,
|
|
477
|
+
isRecorded: loc.value === textValue ? loc.isRecorded : 'Y',
|
|
478
|
+
isSelfHealed: loc.value === textValue ? loc.isSelfHealed : 'Y',
|
|
479
|
+
});
|
|
506
480
|
});
|
|
507
481
|
}
|
|
508
482
|
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
reference: locator.reference,
|
|
519
|
-
status: locator.status,
|
|
520
|
-
isRecorded: locator.isRecorded,
|
|
521
|
-
});
|
|
522
|
-
} else {
|
|
523
|
-
finalLocators.push({
|
|
524
|
-
name: "name",
|
|
525
|
-
type: "static",
|
|
526
|
-
value: nameLocator,
|
|
527
|
-
reference: locator.reference,
|
|
528
|
-
status: locator.status,
|
|
529
|
-
isRecorded: "Y",
|
|
530
|
-
isSelfHealed: "Y",
|
|
531
|
-
});
|
|
532
|
-
}
|
|
483
|
+
const nameLocator = getName(targetElement);
|
|
484
|
+
if (nameLocator && !locatorExists('name', nameLocator)) {
|
|
485
|
+
locators.forEach((loc) => {
|
|
486
|
+
createLocator(loc, {
|
|
487
|
+
name: 'name',
|
|
488
|
+
type: 'static',
|
|
489
|
+
value: nameLocator,
|
|
490
|
+
isRecorded: loc.value === nameLocator && loc.name === 'name' ? loc.isRecorded : 'Y',
|
|
491
|
+
isSelfHealed: loc.value === nameLocator && loc.name === 'name' ? loc.isSelfHealed : 'Y',
|
|
533
492
|
});
|
|
534
|
-
}
|
|
493
|
+
});
|
|
535
494
|
}
|
|
536
495
|
|
|
537
496
|
const classValue = getClassName(targetElement);
|
|
538
|
-
if (
|
|
539
|
-
classValue &&
|
|
540
|
-
classValue.trim() !== "" &&
|
|
541
|
-
!locatorExists("className", classValue)
|
|
542
|
-
) {
|
|
497
|
+
if (classValue && classValue.trim() !== '' && !locatorExists('className', classValue)) {
|
|
543
498
|
locators.forEach((loc) => {
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
status: locator.status,
|
|
551
|
-
isRecorded: locator.isRecorded,
|
|
552
|
-
});
|
|
553
|
-
} else {
|
|
554
|
-
finalLocators.push({
|
|
555
|
-
name: "className",
|
|
556
|
-
type: locator.type,
|
|
557
|
-
value: classValue,
|
|
558
|
-
reference: locator.reference,
|
|
559
|
-
status: locator.status,
|
|
560
|
-
isRecorded: "Y",
|
|
561
|
-
isSelfHealed: "Y",
|
|
562
|
-
});
|
|
563
|
-
}
|
|
499
|
+
createLocator(loc, {
|
|
500
|
+
name: 'className',
|
|
501
|
+
value: classValue,
|
|
502
|
+
isRecorded: loc.value === classValue && loc.name === 'className' ? loc.isRecorded : 'Y',
|
|
503
|
+
isSelfHealed: loc.value === classValue && loc.name === 'className' ? loc.isSelfHealed : 'Y',
|
|
504
|
+
});
|
|
564
505
|
});
|
|
565
506
|
}
|
|
507
|
+
|
|
566
508
|
xpathFunctions.forEach((fn) => {
|
|
509
|
+
const fnValue = fn.value(targetElement);
|
|
567
510
|
locators.forEach((loc) => {
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
status: locator.status,
|
|
575
|
-
isRecorded: locator.isRecorded,
|
|
576
|
-
});
|
|
577
|
-
} else {
|
|
578
|
-
finalLocators.push({
|
|
579
|
-
name: fn.name,
|
|
580
|
-
type: locator.type,
|
|
581
|
-
value: fn.value(targetElement),
|
|
582
|
-
reference: locator.reference,
|
|
583
|
-
status: locator.status,
|
|
584
|
-
isRecorded: "Y",
|
|
585
|
-
isSelfHealed: "Y",
|
|
586
|
-
});
|
|
587
|
-
}
|
|
511
|
+
createLocator(loc, {
|
|
512
|
+
name: fn.name,
|
|
513
|
+
value: fnValue,
|
|
514
|
+
isRecorded: loc.value === fnValue ? loc.isRecorded : 'Y',
|
|
515
|
+
isSelfHealed: loc.value === fnValue ? loc.isSelfHealed : 'Y',
|
|
516
|
+
});
|
|
588
517
|
});
|
|
589
518
|
});
|
|
590
519
|
|
|
591
520
|
for (const locator of locators) {
|
|
592
521
|
try {
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
for (const relation of relations) {
|
|
596
|
-
if (locator.value.includes(relation)) {
|
|
597
|
-
relativeXpath = checkReferenceElementIsValid(
|
|
598
|
-
locator.value,
|
|
599
|
-
relation,
|
|
600
|
-
tempDiv
|
|
601
|
-
);
|
|
522
|
+
for (const loc of locators) {
|
|
523
|
+
if (!loc.value) continue;
|
|
602
524
|
|
|
525
|
+
for (const relation of relations) {
|
|
526
|
+
if (loc.value.includes(relation)) {
|
|
527
|
+
const relativeXpath = checkReferenceElementIsValid(loc.value, relation, tempDiv);
|
|
603
528
|
if (relativeXpath) {
|
|
604
|
-
|
|
605
|
-
name:
|
|
606
|
-
type: locator.type,
|
|
529
|
+
createLocator(loc, {
|
|
530
|
+
name: 'xpath',
|
|
607
531
|
value: relativeXpath,
|
|
608
|
-
|
|
609
|
-
status: locator.status,
|
|
610
|
-
isRecorded:
|
|
611
|
-
locator.isRecorded !== "" ||
|
|
612
|
-
locator.isRecorded !== null
|
|
613
|
-
? locator.isRecorded
|
|
614
|
-
: "Y",
|
|
532
|
+
isRecorded: locator.isRecorded !== '' && locator.isRecorded !== null ? locator.isRecorded : 'Y',
|
|
615
533
|
});
|
|
616
534
|
break;
|
|
617
535
|
}
|
|
@@ -619,42 +537,30 @@ const getElementsFromHTML = (
|
|
|
619
537
|
}
|
|
620
538
|
}
|
|
621
539
|
} catch (error) {
|
|
622
|
-
console.error(
|
|
540
|
+
console.error('Error processing locator:', locator, error);
|
|
623
541
|
}
|
|
624
542
|
}
|
|
625
543
|
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
...obj,
|
|
631
|
-
value: String(obj.value).replace(/"/g, "'"),
|
|
632
|
-
}));
|
|
633
|
-
}
|
|
634
|
-
});
|
|
635
|
-
finalLocators.forEach((loc) => {
|
|
636
|
-
if (loc.isRecorded === "N") {
|
|
637
|
-
uniqueLocators.set(loc.value, loc);
|
|
638
|
-
} else {
|
|
639
|
-
if (!uniqueLocators.has(loc.value)) {
|
|
640
|
-
uniqueLocators.set(loc.value, loc);
|
|
641
|
-
}
|
|
642
|
-
}
|
|
643
|
-
});
|
|
544
|
+
const finalAutoHealedLocators = finalLocators.map((obj) => ({
|
|
545
|
+
...obj,
|
|
546
|
+
value: cleanLocatorValue(obj.value, obj.name, obj.isRecorded),
|
|
547
|
+
}));
|
|
644
548
|
|
|
645
|
-
|
|
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
|
+
);
|
|
646
557
|
|
|
647
558
|
const jsonResult = [
|
|
648
559
|
{
|
|
649
560
|
name: `${name}`,
|
|
650
561
|
desc: `${desc}`,
|
|
651
562
|
type: `${type}`,
|
|
652
|
-
locators:
|
|
653
|
-
(locator) =>
|
|
654
|
-
locator.hasOwnProperty("value") &&
|
|
655
|
-
locator?.value !== null &&
|
|
656
|
-
locator.value !== ""
|
|
657
|
-
),
|
|
563
|
+
locators: finalUniqueAutoHealedLocators.filter((locator) => locator?.value != null && locator.value !== ''),
|
|
658
564
|
isShared: `${isShared}`,
|
|
659
565
|
projectId: `${projectId}`,
|
|
660
566
|
projectType: `${projectType}`,
|
|
@@ -672,16 +578,16 @@ const getElementsFromHTML = (
|
|
|
672
578
|
return jsonResult;
|
|
673
579
|
}
|
|
674
580
|
} catch (error) {
|
|
675
|
-
console.error(
|
|
581
|
+
console.error('Error processing locator:', locator, error);
|
|
676
582
|
continue;
|
|
677
583
|
}
|
|
678
584
|
}
|
|
679
585
|
} catch (error) {
|
|
680
|
-
console.error(
|
|
586
|
+
console.error('Error processing locator:', locator, error);
|
|
681
587
|
continue;
|
|
682
588
|
}
|
|
683
589
|
}
|
|
684
590
|
return null;
|
|
685
591
|
};
|
|
686
592
|
|
|
687
|
-
export { getElementsFromHTML };
|
|
593
|
+
export { getElementsFromHTML };
|