ff-automationv2 1.0.0 → 2.0.1-beta.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.
- package/bitbucket-pipelines.yml +20 -0
- package/eslint.config.ts +29 -29
- package/package.json +51 -51
- package/src/ai/llmcalls/decodeApiKey.ts +14 -14
- package/src/ai/llmcalls/llmAction.ts +89 -89
- package/src/ai/llmcalls/parseLlmOputput.ts +69 -69
- package/src/ai/llmprompts/systemPrompts/actionExtractorPrompt.ts +70 -70
- package/src/ai/llmprompts/systemPrompts/errorDescriptionPrompt.ts +23 -23
- package/src/ai/llmprompts/systemPrompts/fireflinkElementIndexExtactors.ts +198 -198
- package/src/ai/llmprompts/systemPrompts/userStoryToListPrompt.ts +24 -24
- package/src/ai/llmprompts/systemPrompts/visionPrompt.ts +28 -28
- package/src/automation/actions/executor.ts +74 -74
- package/src/automation/actions/interaction/enterInput.ts +27 -27
- package/src/automation/actions/interface/interactionActionInterface.ts +26 -26
- package/src/automation/actions/interface/navigationActionInterface.ts +21 -21
- package/src/automation/actions/interface/waitActionInterface.ts +5 -5
- package/src/automation/actions/navigation/getTitle.ts +8 -8
- package/src/automation/actions/navigation/goBack.ts +8 -8
- package/src/automation/actions/navigation/navigate.ts +9 -9
- package/src/automation/actions/navigation/refresh.ts +8 -8
- package/src/automation/actions/wait/wait.ts +9 -9
- package/src/automation/browserSession/initiateBrowserSession.ts +81 -81
- package/src/core/constants/supportedActions.ts +7 -7
- package/src/core/interfaces/StableDomInterface.ts +5 -5
- package/src/core/interfaces/actionInterface.ts +13 -13
- package/src/core/interfaces/automationRunnerInterface.ts +2 -2
- package/src/core/interfaces/browserCapabilitiesInterface.ts +4 -4
- package/src/core/interfaces/browserConfigurationInterface.ts +2 -2
- package/src/core/interfaces/domAnalysisInterface.ts +34 -34
- package/src/core/interfaces/executionDetails.ts +29 -29
- package/src/core/interfaces/fireflinkScriptPayloadInterface.ts +39 -39
- package/src/core/interfaces/llmConfigurationInterface.ts +2 -2
- package/src/core/interfaces/llmResponseInterface.ts +38 -38
- package/src/core/interfaces/promptInterface.ts +21 -21
- package/src/core/interfaces/scriptGenrationDataInterface.ts +16 -16
- package/src/core/interfaces/toolsInterface.ts +5 -5
- package/src/core/main/actionHandlerFactory.ts +86 -86
- package/src/core/main/executionContext.ts +18 -18
- package/src/core/main/runAutomationScript.ts +177 -177
- package/src/core/main/stepProcessor.ts +28 -28
- package/src/core/types/llmResponseType.ts +10 -10
- package/src/core/types/visionllmInputType.ts +4 -4
- package/src/domAnalysis/getRelaventElements.ts +24 -24
- package/src/domAnalysis/relativeElementsFromDom.ts +94 -94
- package/src/domAnalysis/searchBest.ts +159 -159
- package/src/domAnalysis/simplifyAndFlatten.ts +118 -118
- package/src/fireflinkData/fireflinkLocators/elementsFromHTML.ts +656 -656
- package/src/fireflinkData/fireflinkLocators/getListOfLocators.ts +31 -31
- package/src/fireflinkData/fireflinkLocators/typeList.ts +36 -36
- package/src/fireflinkData/fireflinkScript/scriptGenrationData.ts +30 -30
- package/src/index.ts +5 -5
- package/src/llmConfig/llmConfiguration.ts +26 -26
- package/src/service/fireflinkApi.service.ts +46 -46
- package/src/service/scriptRunner.service.ts +83 -83
- package/src/utils/DomExtraction/jsForAttributeInjection.ts +254 -254
- package/src/utils/javascript/jsFindElement.ts +161 -161
- package/src/utils/javascript/jsForShadowRoot.ts +216 -216
- package/src/utils/javascript/jsForToaster.ts +60 -60
- package/src/utils/logger/logData.ts +36 -36
- package/tsconfig.json +26 -26
|
@@ -1,656 +1,656 @@
|
|
|
1
|
-
import { JSDOM, VirtualConsole } from "jsdom";
|
|
2
|
-
import { logger } from "../../utils/logger/logData.js"
|
|
3
|
-
type ElementDetails = {
|
|
4
|
-
id?: string | null;
|
|
5
|
-
className?: string | null;
|
|
6
|
-
xpathByText?: string | null;
|
|
7
|
-
xpathById?: string | null;
|
|
8
|
-
xpathByClass?: string | null;
|
|
9
|
-
xpathAbsolute?: string | null;
|
|
10
|
-
xpathByName?: string | null;
|
|
11
|
-
xpathByPlaceholder?: string | null;
|
|
12
|
-
xpathByType?: string | null;
|
|
13
|
-
visibleText?: string | null;
|
|
14
|
-
relativeXpath?: string | null;
|
|
15
|
-
[key: string]: any;
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
const isUnique = (xpathResult: XPathResult): boolean => {
|
|
19
|
-
return xpathResult && xpathResult.snapshotLength === 1;
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
const getElementFromShadowRoot = (
|
|
23
|
-
element: Element,
|
|
24
|
-
selector: string
|
|
25
|
-
): Element | null => {
|
|
26
|
-
const shadowRoot = (element as HTMLElement).shadowRoot;
|
|
27
|
-
if (shadowRoot && !selector.includes("dynamic")) {
|
|
28
|
-
return shadowRoot.querySelector(selector);
|
|
29
|
-
}
|
|
30
|
-
return null;
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
const isSVGElement = (element: Element): boolean => {
|
|
34
|
-
return (
|
|
35
|
-
typeof window !== "undefined" &&
|
|
36
|
-
typeof SVGElement !== "undefined" &&
|
|
37
|
-
element instanceof SVGElement
|
|
38
|
-
);
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
const getXPath = (element: Element, xpath: string): string | null => {
|
|
42
|
-
const window = element.ownerDocument.defaultView;
|
|
43
|
-
if (!window) return null;
|
|
44
|
-
|
|
45
|
-
const xpathEvaluator = new window.XPathEvaluator();
|
|
46
|
-
const xpathResult = xpathEvaluator.evaluate(
|
|
47
|
-
xpath,
|
|
48
|
-
element.ownerDocument,
|
|
49
|
-
null,
|
|
50
|
-
window.XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
|
|
51
|
-
null
|
|
52
|
-
);
|
|
53
|
-
|
|
54
|
-
return isUnique(xpathResult) ? xpath : null;
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
const getId = (element: Element | null): string | null => {
|
|
58
|
-
return element?.id || null;
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
const getClassName = (element: Element): string | null => {
|
|
62
|
-
return (element as HTMLElement).className || null;
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
const getVisibleText = (element: Element): string | null => {
|
|
66
|
-
return element.textContent?.trim() || null;
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
const getXPathByText = (element: Element): string | null => {
|
|
70
|
-
const text = getVisibleText(element);
|
|
71
|
-
if (text) {
|
|
72
|
-
const xpath = `//${element.nodeName.toLowerCase()}[text()='${text}']`;
|
|
73
|
-
return getXPath(element, xpath);
|
|
74
|
-
}
|
|
75
|
-
return null;
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
const getXPathById = (element: Element): string | null => {
|
|
83
|
-
if (element.id) {
|
|
84
|
-
const xpath = `//${element.nodeName.toLowerCase()}[@id='${element.id}']`;
|
|
85
|
-
return getXPath(element, xpath);
|
|
86
|
-
}
|
|
87
|
-
return null;
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
const getXPathByClass = (element: Element): string | null => {
|
|
91
|
-
const classNames = (element as HTMLElement).classList;
|
|
92
|
-
if (classNames.length > 0) {
|
|
93
|
-
const xpath = `//${element.nodeName.toLowerCase()}[@class='${classNames[0]
|
|
94
|
-
}']`;
|
|
95
|
-
return getXPath(element, xpath);
|
|
96
|
-
}
|
|
97
|
-
return null;
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
const getXpathByName = (element: Element): string | null => {
|
|
101
|
-
const selector = element.nodeName.toLowerCase();
|
|
102
|
-
const elementEl = element as HTMLElement;
|
|
103
|
-
|
|
104
|
-
if (elementEl.hasAttribute("name")) {
|
|
105
|
-
const attrValue = elementEl.getAttribute("name");
|
|
106
|
-
const xpath = `//${selector}[@name='${attrValue}']`;
|
|
107
|
-
return getXPath(element, xpath) || null;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
return null;
|
|
111
|
-
};
|
|
112
|
-
|
|
113
|
-
const getName = (element: Element): string | null => {
|
|
114
|
-
const elementEl = element as HTMLElement;
|
|
115
|
-
|
|
116
|
-
if (elementEl.hasAttribute("name")) {
|
|
117
|
-
const attrValue = elementEl.getAttribute("name");
|
|
118
|
-
const name = `${attrValue}`;
|
|
119
|
-
return name || null;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
return null;
|
|
123
|
-
};
|
|
124
|
-
|
|
125
|
-
const getFFxpath = (element: Element): string | null => {
|
|
126
|
-
const elementEl = element as HTMLElement;
|
|
127
|
-
|
|
128
|
-
if (elementEl.hasAttribute("ff-xpath") && !elementEl.getAttribute("ff-xpath")?.includes("/iframe")) {
|
|
129
|
-
const attrValue = elementEl.getAttribute("ff-xpath");
|
|
130
|
-
const name = `${attrValue}`;
|
|
131
|
-
return name || null;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
return null;
|
|
135
|
-
};
|
|
136
|
-
|
|
137
|
-
const getXpathByPlaceholder = (element: Element): string | null => {
|
|
138
|
-
const selector = element.nodeName.toLowerCase();
|
|
139
|
-
const elementEl = element as HTMLElement;
|
|
140
|
-
|
|
141
|
-
if (elementEl.hasAttribute("placeholder")) {
|
|
142
|
-
const attrValue = elementEl.getAttribute("placeholder");
|
|
143
|
-
const xpath = `//${selector}[@placeholder='${attrValue}']`;
|
|
144
|
-
return getXPath(element, xpath) || null;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
return null;
|
|
148
|
-
};
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
const getXpathByType = (element: Element): string | null => {
|
|
153
|
-
const selector = element.nodeName.toLowerCase();
|
|
154
|
-
const elementEl = element as HTMLElement;
|
|
155
|
-
|
|
156
|
-
if (elementEl.hasAttribute("type")) {
|
|
157
|
-
const attrValue = elementEl.getAttribute("type");
|
|
158
|
-
const xpath = `//${selector}[@type='${attrValue}']`;
|
|
159
|
-
return getXPath(element, xpath) || null;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
return null;
|
|
163
|
-
};
|
|
164
|
-
|
|
165
|
-
const getXPathAbsolute = (element: Element): string => {
|
|
166
|
-
const path: string[] = [];
|
|
167
|
-
while (element && element.nodeType === 1) {
|
|
168
|
-
// ELEMENT_NODE
|
|
169
|
-
let selector = element.nodeName.toLowerCase();
|
|
170
|
-
let sibling = element;
|
|
171
|
-
let siblingCount = 1;
|
|
172
|
-
|
|
173
|
-
while ((sibling = sibling.previousElementSibling as Element)) {
|
|
174
|
-
if (sibling.nodeName.toLowerCase() === element.nodeName.toLowerCase()) {
|
|
175
|
-
siblingCount++;
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
if (siblingCount > 1) {
|
|
180
|
-
selector += `[${siblingCount}]`;
|
|
181
|
-
}
|
|
182
|
-
path.unshift(selector);
|
|
183
|
-
element = element.parentNode as Element;
|
|
184
|
-
}
|
|
185
|
-
return "//" + path.join("/");
|
|
186
|
-
};
|
|
187
|
-
|
|
188
|
-
const relations: string[] = [
|
|
189
|
-
"/preceding-sibling",
|
|
190
|
-
"/following-sibling",
|
|
191
|
-
"/parent",
|
|
192
|
-
"/descendant",
|
|
193
|
-
"/ancestor",
|
|
194
|
-
"/self",
|
|
195
|
-
"/ancestor-or-self",
|
|
196
|
-
"/child",
|
|
197
|
-
"/preceding",
|
|
198
|
-
"/following",
|
|
199
|
-
];
|
|
200
|
-
|
|
201
|
-
function getElementFromXPath(
|
|
202
|
-
tempDiv: HTMLElement,
|
|
203
|
-
xpath: string
|
|
204
|
-
): Element | null {
|
|
205
|
-
const currentElement: Element | null = tempDiv;
|
|
206
|
-
const window = currentElement.ownerDocument.defaultView;
|
|
207
|
-
if (!window) return null;
|
|
208
|
-
|
|
209
|
-
const xpathEvaluator = new window.XPathEvaluator();
|
|
210
|
-
const xpathResult = xpathEvaluator.evaluate(
|
|
211
|
-
xpath,
|
|
212
|
-
currentElement.ownerDocument,
|
|
213
|
-
null,
|
|
214
|
-
window.XPathResult.FIRST_ORDERED_NODE_TYPE,
|
|
215
|
-
null
|
|
216
|
-
);
|
|
217
|
-
return xpathResult.singleNodeValue as Element | null;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
function checkReferenceElementIsValid(
|
|
221
|
-
locator: string,
|
|
222
|
-
relation: string,
|
|
223
|
-
tempDiv: HTMLElement
|
|
224
|
-
): string | null {
|
|
225
|
-
if (locator.includes(relation)) {
|
|
226
|
-
const locatotSplitArray: string[] = locator.split(relation);
|
|
227
|
-
const sourceLoc = locatotSplitArray[0].trim();
|
|
228
|
-
const currentElement: Element | null = tempDiv;
|
|
229
|
-
const window = currentElement.ownerDocument.defaultView;
|
|
230
|
-
if (!window) return null;
|
|
231
|
-
if (!locator.includes("dynamic")) {
|
|
232
|
-
const xpathEvaluator = new window.XPathEvaluator();
|
|
233
|
-
const xpathResult = xpathEvaluator.evaluate(
|
|
234
|
-
sourceLoc,
|
|
235
|
-
currentElement.ownerDocument,
|
|
236
|
-
null,
|
|
237
|
-
window.XPathResult.FIRST_ORDERED_NODE_TYPE,
|
|
238
|
-
null
|
|
239
|
-
);
|
|
240
|
-
|
|
241
|
-
const sourceElement = xpathResult.singleNodeValue;
|
|
242
|
-
if (sourceElement) {
|
|
243
|
-
const xpathResultComplete = xpathEvaluator.evaluate(
|
|
244
|
-
locator,
|
|
245
|
-
currentElement.ownerDocument,
|
|
246
|
-
null,
|
|
247
|
-
window.XPathResult.FIRST_ORDERED_NODE_TYPE,
|
|
248
|
-
null
|
|
249
|
-
);
|
|
250
|
-
const completeElement = xpathResultComplete.singleNodeValue;
|
|
251
|
-
let relativeXpath: string;
|
|
252
|
-
if (completeElement) {
|
|
253
|
-
relativeXpath = locator;
|
|
254
|
-
return relativeXpath;
|
|
255
|
-
} else {
|
|
256
|
-
logger.error("Complete Locator is Invalid:", locator);
|
|
257
|
-
relativeXpath = locator;
|
|
258
|
-
return relativeXpath;
|
|
259
|
-
}
|
|
260
|
-
} else {
|
|
261
|
-
logger.error("Source Locator Not Found:", sourceLoc);
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
return null;
|
|
266
|
-
}
|
|
267
|
-
interface Locator {
|
|
268
|
-
name: string;
|
|
269
|
-
type: string;
|
|
270
|
-
value: string;
|
|
271
|
-
reference: string;
|
|
272
|
-
status: string;
|
|
273
|
-
isRecorded: string;
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
const getElementsFromHTML = (
|
|
277
|
-
locators: Locator[],
|
|
278
|
-
htmlString: string
|
|
279
|
-
): ElementDetails | null => {
|
|
280
|
-
const virtualConsole = new VirtualConsole();
|
|
281
|
-
const dom = new JSDOM(htmlString, {
|
|
282
|
-
resources: "usable",
|
|
283
|
-
runScripts: "outside-only", // Prevents inline script execution in JSDOM
|
|
284
|
-
pretendToBeVisual: true,
|
|
285
|
-
virtualConsole,
|
|
286
|
-
includeNodeLocations: true,
|
|
287
|
-
});
|
|
288
|
-
|
|
289
|
-
const document = dom.window.document;
|
|
290
|
-
global.SVGElement = dom.window.SVGElement;
|
|
291
|
-
const tempDiv = document.createElement("div");
|
|
292
|
-
const elementsToRemove = document.querySelectorAll(
|
|
293
|
-
"script, style, link[rel='stylesheet'], meta, noscript, embed, object, param, source, svg"
|
|
294
|
-
);
|
|
295
|
-
|
|
296
|
-
if (elementsToRemove) {
|
|
297
|
-
elementsToRemove.forEach((tag) => {
|
|
298
|
-
(tag as Element).remove();
|
|
299
|
-
});
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
tempDiv.innerHTML = document.body.innerHTML;
|
|
303
|
-
// tempDiv.innerHTML = htmlString;
|
|
304
|
-
const finalLocators: any = [];
|
|
305
|
-
|
|
306
|
-
for (const locator of locators) {
|
|
307
|
-
try {
|
|
308
|
-
const isDynamic = String(locator.value || locator.type || "");
|
|
309
|
-
if (
|
|
310
|
-
isDynamic.includes("dynamic") ||
|
|
311
|
-
isDynamic.match("dynamic") ||
|
|
312
|
-
isDynamic.includes("{") ||
|
|
313
|
-
isDynamic.includes("}")
|
|
314
|
-
) {
|
|
315
|
-
finalLocators.push({
|
|
316
|
-
name: locator.name,
|
|
317
|
-
type: locator.type,
|
|
318
|
-
value: locator.value,
|
|
319
|
-
reference: locator.reference,
|
|
320
|
-
status: locator.status,
|
|
321
|
-
isRecorded: locator.isRecorded,
|
|
322
|
-
});
|
|
323
|
-
continue;
|
|
324
|
-
}
|
|
325
|
-
const isRecorded = String(locator.isRecorded || "");
|
|
326
|
-
|
|
327
|
-
if (isRecorded.includes("N") || isRecorded.match("N")) {
|
|
328
|
-
finalLocators.push({
|
|
329
|
-
name: locator.name,
|
|
330
|
-
type: locator.type,
|
|
331
|
-
value: locator.value,
|
|
332
|
-
reference: locator.reference,
|
|
333
|
-
status: locator.status,
|
|
334
|
-
isRecorded: locator.isRecorded,
|
|
335
|
-
});
|
|
336
|
-
}
|
|
337
|
-
try {
|
|
338
|
-
let targetElement: Element | null = null;
|
|
339
|
-
if (locator.value.startsWith("iframe")) {
|
|
340
|
-
const iframe = tempDiv.querySelector(
|
|
341
|
-
locator.value
|
|
342
|
-
) as HTMLIFrameElement;
|
|
343
|
-
if (iframe) {
|
|
344
|
-
const iframeDocument =
|
|
345
|
-
iframe.contentDocument || iframe.contentWindow?.document;
|
|
346
|
-
if (iframeDocument) {
|
|
347
|
-
targetElement = iframeDocument.querySelector(
|
|
348
|
-
locator.value.slice(6)
|
|
349
|
-
);
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
} else {
|
|
353
|
-
const selectors = locator.value.split(">>>"); // Custom delimiter for shadow DOM
|
|
354
|
-
let currentElement: Element | null = tempDiv;
|
|
355
|
-
|
|
356
|
-
for (const selector of selectors) {
|
|
357
|
-
if (currentElement) {
|
|
358
|
-
const trimmedSelector = selector.trim();
|
|
359
|
-
if (
|
|
360
|
-
locator.name.includes("id") ||
|
|
361
|
-
trimmedSelector.startsWith("#")
|
|
362
|
-
) {
|
|
363
|
-
targetElement = currentElement.querySelector(
|
|
364
|
-
"#" + trimmedSelector
|
|
365
|
-
);
|
|
366
|
-
} else if (
|
|
367
|
-
locator.name.includes("className") ||
|
|
368
|
-
trimmedSelector.startsWith(".")
|
|
369
|
-
) {
|
|
370
|
-
targetElement = currentElement.querySelector(
|
|
371
|
-
"." + trimmedSelector
|
|
372
|
-
);
|
|
373
|
-
} else if (
|
|
374
|
-
(locator.name.includes("xpath") ||
|
|
375
|
-
trimmedSelector.startsWith("//")) &&
|
|
376
|
-
!locator.type.match("dynamic")
|
|
377
|
-
) {
|
|
378
|
-
targetElement = getElementFromXPath(tempDiv, trimmedSelector);
|
|
379
|
-
} else {
|
|
380
|
-
targetElement = currentElement.querySelector(trimmedSelector);
|
|
381
|
-
if (!targetElement) {
|
|
382
|
-
targetElement = getElementFromShadowRoot(
|
|
383
|
-
currentElement,
|
|
384
|
-
trimmedSelector
|
|
385
|
-
);
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
if (
|
|
390
|
-
!targetElement &&
|
|
391
|
-
isSVGElement(currentElement) &&
|
|
392
|
-
!locator.type.match("dynamic")
|
|
393
|
-
) {
|
|
394
|
-
targetElement = currentElement.querySelector(trimmedSelector);
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
currentElement = targetElement;
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
const locatorExists = (name: string, value: string) => {
|
|
403
|
-
return finalLocators.some(
|
|
404
|
-
(_loc: any) =>
|
|
405
|
-
_loc.name === name &&
|
|
406
|
-
_loc.value === value &&
|
|
407
|
-
(!_loc.value.includes("dynamic") ||
|
|
408
|
-
!locator.type.match("dynamic"))
|
|
409
|
-
);
|
|
410
|
-
};
|
|
411
|
-
|
|
412
|
-
const xpathFunctions = [
|
|
413
|
-
{ name: "xpath", value: getXPathByText },
|
|
414
|
-
{ name: "xpath", value: getXPathById },
|
|
415
|
-
{ name: "xpath", value: getXPathByClass },
|
|
416
|
-
{ name: "xpath", value: getXPathAbsolute },
|
|
417
|
-
{ name: "xpath", value: getXpathByName },
|
|
418
|
-
{ name: "xpath", value: getXpathByPlaceholder },
|
|
419
|
-
{ name: "xpath", value: getXpathByType },
|
|
420
|
-
];
|
|
421
|
-
|
|
422
|
-
if (targetElement) {
|
|
423
|
-
const idValue = getId(targetElement);
|
|
424
|
-
if (idValue && !locatorExists("id", idValue)) {
|
|
425
|
-
locators.forEach(() => {
|
|
426
|
-
if (locator.value === idValue) {
|
|
427
|
-
finalLocators.push({
|
|
428
|
-
name: "id",
|
|
429
|
-
type: locator.type,
|
|
430
|
-
value: idValue,
|
|
431
|
-
reference: locator.reference,
|
|
432
|
-
status: locator.status,
|
|
433
|
-
isRecorded: locator.isRecorded,
|
|
434
|
-
isAiRecorded: "Y",
|
|
435
|
-
});
|
|
436
|
-
} else {
|
|
437
|
-
finalLocators.push({
|
|
438
|
-
name: "id",
|
|
439
|
-
type: locator.type,
|
|
440
|
-
value: idValue,
|
|
441
|
-
reference: locator.reference,
|
|
442
|
-
status: locator.status,
|
|
443
|
-
isRecorded: "Y",
|
|
444
|
-
isAiRecorded: "Y",
|
|
445
|
-
});
|
|
446
|
-
}
|
|
447
|
-
});
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
if (getVisibleText(targetElement)) {
|
|
452
|
-
const textValue = getVisibleText(targetElement);
|
|
453
|
-
locators.forEach((_loc) => {
|
|
454
|
-
if (_loc.value === textValue) {
|
|
455
|
-
finalLocators.push({
|
|
456
|
-
name: "linkText",
|
|
457
|
-
type: "static",
|
|
458
|
-
value: textValue,
|
|
459
|
-
reference: locator.reference,
|
|
460
|
-
status: locator.status,
|
|
461
|
-
isRecorded: locator.isRecorded,
|
|
462
|
-
isAiRecorded: "Y",
|
|
463
|
-
});
|
|
464
|
-
} else {
|
|
465
|
-
finalLocators.push({
|
|
466
|
-
name: "linkText",
|
|
467
|
-
type: "static",
|
|
468
|
-
value: textValue,
|
|
469
|
-
reference: locator.reference,
|
|
470
|
-
status: locator.status,
|
|
471
|
-
isRecorded: "Y",
|
|
472
|
-
isAiRecorded: "Y",
|
|
473
|
-
});
|
|
474
|
-
}
|
|
475
|
-
});
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
if (getName(targetElement)) {
|
|
479
|
-
const nameLocator = getName(targetElement);
|
|
480
|
-
locators.forEach((_loc) => {
|
|
481
|
-
if (_loc.value === nameLocator) {
|
|
482
|
-
finalLocators.push({
|
|
483
|
-
name: "name",
|
|
484
|
-
type: "static",
|
|
485
|
-
value: nameLocator,
|
|
486
|
-
reference: locator.reference,
|
|
487
|
-
status: locator.status,
|
|
488
|
-
isRecorded: locator.isRecorded,
|
|
489
|
-
isAiRecorded: "Y",
|
|
490
|
-
});
|
|
491
|
-
} else {
|
|
492
|
-
finalLocators.push({
|
|
493
|
-
name: "name",
|
|
494
|
-
type: "static",
|
|
495
|
-
value: nameLocator,
|
|
496
|
-
reference: locator.reference,
|
|
497
|
-
status: locator.status,
|
|
498
|
-
isRecorded: "Y",
|
|
499
|
-
isAiRecorded: "Y",
|
|
500
|
-
});
|
|
501
|
-
}
|
|
502
|
-
});
|
|
503
|
-
}
|
|
504
|
-
if (getFFxpath(targetElement)) {
|
|
505
|
-
const nameLocator = getFFxpath(targetElement);
|
|
506
|
-
locators.forEach((_loc) => {
|
|
507
|
-
if (_loc.value === nameLocator) {
|
|
508
|
-
finalLocators.push({
|
|
509
|
-
name: "xpath",
|
|
510
|
-
type: "static",
|
|
511
|
-
value: nameLocator,
|
|
512
|
-
reference: locator.reference,
|
|
513
|
-
status: locator.status,
|
|
514
|
-
isRecorded: locator.isRecorded,
|
|
515
|
-
isAiRecorded: "Y",
|
|
516
|
-
});
|
|
517
|
-
} else {
|
|
518
|
-
finalLocators.push({
|
|
519
|
-
name: "xpath",
|
|
520
|
-
type: "static",
|
|
521
|
-
value: nameLocator,
|
|
522
|
-
reference: locator.reference,
|
|
523
|
-
status: locator.status,
|
|
524
|
-
isRecorded: "Y",
|
|
525
|
-
isAiRecorded: "Y",
|
|
526
|
-
});
|
|
527
|
-
}
|
|
528
|
-
});
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
const classValue = getClassName(targetElement);
|
|
532
|
-
if (
|
|
533
|
-
classValue &&
|
|
534
|
-
classValue.trim() !== "" &&
|
|
535
|
-
!locatorExists("className", classValue)
|
|
536
|
-
) {
|
|
537
|
-
locators.forEach((_loc) => {
|
|
538
|
-
if (_loc.value === classValue) {
|
|
539
|
-
finalLocators.push({
|
|
540
|
-
name: "className",
|
|
541
|
-
type: locator.type,
|
|
542
|
-
value: classValue,
|
|
543
|
-
reference: locator.reference,
|
|
544
|
-
status: locator.status,
|
|
545
|
-
isRecorded: locator.isRecorded,
|
|
546
|
-
isAiRecorded: "Y",
|
|
547
|
-
});
|
|
548
|
-
} else {
|
|
549
|
-
finalLocators.push({
|
|
550
|
-
name: "className",
|
|
551
|
-
type: locator.type,
|
|
552
|
-
value: classValue,
|
|
553
|
-
reference: locator.reference,
|
|
554
|
-
status: locator.status,
|
|
555
|
-
isRecorded: "Y",
|
|
556
|
-
isAiRecorded: "Y",
|
|
557
|
-
});
|
|
558
|
-
}
|
|
559
|
-
});
|
|
560
|
-
}
|
|
561
|
-
xpathFunctions.forEach((fn) => {
|
|
562
|
-
locators.forEach((_loc) => {
|
|
563
|
-
if (_loc.value === fn.value(targetElement)) {
|
|
564
|
-
finalLocators.push({
|
|
565
|
-
name: fn.name,
|
|
566
|
-
type: locator.type,
|
|
567
|
-
value: fn.value(targetElement),
|
|
568
|
-
reference: locator.reference,
|
|
569
|
-
status: locator.status,
|
|
570
|
-
isRecorded: locator.isRecorded,
|
|
571
|
-
isAiRecorded: "Y",
|
|
572
|
-
});
|
|
573
|
-
} else {
|
|
574
|
-
finalLocators.push({
|
|
575
|
-
name: fn.name,
|
|
576
|
-
type: locator.type,
|
|
577
|
-
value: fn.value(targetElement),
|
|
578
|
-
reference: locator.reference,
|
|
579
|
-
status: locator.status,
|
|
580
|
-
isRecorded: "Y",
|
|
581
|
-
isAiRecorded: "Y",
|
|
582
|
-
});
|
|
583
|
-
}
|
|
584
|
-
});
|
|
585
|
-
});
|
|
586
|
-
|
|
587
|
-
for (const locator of locators) {
|
|
588
|
-
try {
|
|
589
|
-
let relativeXpath = null;
|
|
590
|
-
if (locator.value) {
|
|
591
|
-
for (const relation of relations) {
|
|
592
|
-
if (locator.value.includes(relation)) {
|
|
593
|
-
relativeXpath = checkReferenceElementIsValid(
|
|
594
|
-
locator.value,
|
|
595
|
-
relation,
|
|
596
|
-
tempDiv
|
|
597
|
-
);
|
|
598
|
-
|
|
599
|
-
if (relativeXpath) {
|
|
600
|
-
finalLocators.push({
|
|
601
|
-
name: "xpath",
|
|
602
|
-
type: locator.type,
|
|
603
|
-
value: relativeXpath,
|
|
604
|
-
reference: locator.reference,
|
|
605
|
-
status: locator.status,
|
|
606
|
-
isRecorded:
|
|
607
|
-
locator.isRecorded !== "" &&
|
|
608
|
-
locator.isRecorded !== null
|
|
609
|
-
? locator.isRecorded
|
|
610
|
-
: "Y",
|
|
611
|
-
});
|
|
612
|
-
break;
|
|
613
|
-
}
|
|
614
|
-
}
|
|
615
|
-
}
|
|
616
|
-
}
|
|
617
|
-
} catch (error: any) {
|
|
618
|
-
logger.error(`[${Object.getPrototypeOf(this).constructor.name}] : Error processing locator:`, locator, error.message);
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
const uniqueLocators = new Map();
|
|
623
|
-
finalLocators.forEach((_loc: any) => {
|
|
624
|
-
if (!uniqueLocators.has(_loc.value)) {
|
|
625
|
-
uniqueLocators.set(_loc.value, _loc);
|
|
626
|
-
}
|
|
627
|
-
});
|
|
628
|
-
|
|
629
|
-
const ffLoc = Array.from(uniqueLocators.values());
|
|
630
|
-
|
|
631
|
-
const jsonResult = [
|
|
632
|
-
{
|
|
633
|
-
locators: ffLoc.filter(
|
|
634
|
-
(locator) =>
|
|
635
|
-
Object.prototype.hasOwnProperty.call(locator, "value") &&
|
|
636
|
-
locator?.value !== null &&
|
|
637
|
-
locator.value !== ""
|
|
638
|
-
),
|
|
639
|
-
},
|
|
640
|
-
];
|
|
641
|
-
|
|
642
|
-
return jsonResult;
|
|
643
|
-
}
|
|
644
|
-
} catch (error: any) {
|
|
645
|
-
logger.error(`[${Object.getPrototypeOf(this).constructor.name}] : Error processing locator:`, locator, error.message);
|
|
646
|
-
continue;
|
|
647
|
-
}
|
|
648
|
-
} catch (error: any) {
|
|
649
|
-
logger.error(`[${Object.getPrototypeOf(this).constructor.name}] : Error processing locator:`, locator, error.message);
|
|
650
|
-
continue;
|
|
651
|
-
}
|
|
652
|
-
}
|
|
653
|
-
return null;
|
|
654
|
-
};
|
|
655
|
-
|
|
656
|
-
export { getElementsFromHTML };
|
|
1
|
+
import { JSDOM, VirtualConsole } from "jsdom";
|
|
2
|
+
import { logger } from "../../utils/logger/logData.js"
|
|
3
|
+
type ElementDetails = {
|
|
4
|
+
id?: string | null;
|
|
5
|
+
className?: string | null;
|
|
6
|
+
xpathByText?: string | null;
|
|
7
|
+
xpathById?: string | null;
|
|
8
|
+
xpathByClass?: string | null;
|
|
9
|
+
xpathAbsolute?: string | null;
|
|
10
|
+
xpathByName?: string | null;
|
|
11
|
+
xpathByPlaceholder?: string | null;
|
|
12
|
+
xpathByType?: string | null;
|
|
13
|
+
visibleText?: string | null;
|
|
14
|
+
relativeXpath?: string | null;
|
|
15
|
+
[key: string]: any;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const isUnique = (xpathResult: XPathResult): boolean => {
|
|
19
|
+
return xpathResult && xpathResult.snapshotLength === 1;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const getElementFromShadowRoot = (
|
|
23
|
+
element: Element,
|
|
24
|
+
selector: string
|
|
25
|
+
): Element | null => {
|
|
26
|
+
const shadowRoot = (element as HTMLElement).shadowRoot;
|
|
27
|
+
if (shadowRoot && !selector.includes("dynamic")) {
|
|
28
|
+
return shadowRoot.querySelector(selector);
|
|
29
|
+
}
|
|
30
|
+
return null;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const isSVGElement = (element: Element): boolean => {
|
|
34
|
+
return (
|
|
35
|
+
typeof window !== "undefined" &&
|
|
36
|
+
typeof SVGElement !== "undefined" &&
|
|
37
|
+
element instanceof SVGElement
|
|
38
|
+
);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const getXPath = (element: Element, xpath: string): string | null => {
|
|
42
|
+
const window = element.ownerDocument.defaultView;
|
|
43
|
+
if (!window) return null;
|
|
44
|
+
|
|
45
|
+
const xpathEvaluator = new window.XPathEvaluator();
|
|
46
|
+
const xpathResult = xpathEvaluator.evaluate(
|
|
47
|
+
xpath,
|
|
48
|
+
element.ownerDocument,
|
|
49
|
+
null,
|
|
50
|
+
window.XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
|
|
51
|
+
null
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
return isUnique(xpathResult) ? xpath : null;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const getId = (element: Element | null): string | null => {
|
|
58
|
+
return element?.id || null;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const getClassName = (element: Element): string | null => {
|
|
62
|
+
return (element as HTMLElement).className || null;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const getVisibleText = (element: Element): string | null => {
|
|
66
|
+
return element.textContent?.trim() || null;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const getXPathByText = (element: Element): string | null => {
|
|
70
|
+
const text = getVisibleText(element);
|
|
71
|
+
if (text) {
|
|
72
|
+
const xpath = `//${element.nodeName.toLowerCase()}[text()='${text}']`;
|
|
73
|
+
return getXPath(element, xpath);
|
|
74
|
+
}
|
|
75
|
+
return null;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
const getXPathById = (element: Element): string | null => {
|
|
83
|
+
if (element.id) {
|
|
84
|
+
const xpath = `//${element.nodeName.toLowerCase()}[@id='${element.id}']`;
|
|
85
|
+
return getXPath(element, xpath);
|
|
86
|
+
}
|
|
87
|
+
return null;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const getXPathByClass = (element: Element): string | null => {
|
|
91
|
+
const classNames = (element as HTMLElement).classList;
|
|
92
|
+
if (classNames.length > 0) {
|
|
93
|
+
const xpath = `//${element.nodeName.toLowerCase()}[@class='${classNames[0]
|
|
94
|
+
}']`;
|
|
95
|
+
return getXPath(element, xpath);
|
|
96
|
+
}
|
|
97
|
+
return null;
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const getXpathByName = (element: Element): string | null => {
|
|
101
|
+
const selector = element.nodeName.toLowerCase();
|
|
102
|
+
const elementEl = element as HTMLElement;
|
|
103
|
+
|
|
104
|
+
if (elementEl.hasAttribute("name")) {
|
|
105
|
+
const attrValue = elementEl.getAttribute("name");
|
|
106
|
+
const xpath = `//${selector}[@name='${attrValue}']`;
|
|
107
|
+
return getXPath(element, xpath) || null;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return null;
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const getName = (element: Element): string | null => {
|
|
114
|
+
const elementEl = element as HTMLElement;
|
|
115
|
+
|
|
116
|
+
if (elementEl.hasAttribute("name")) {
|
|
117
|
+
const attrValue = elementEl.getAttribute("name");
|
|
118
|
+
const name = `${attrValue}`;
|
|
119
|
+
return name || null;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return null;
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const getFFxpath = (element: Element): string | null => {
|
|
126
|
+
const elementEl = element as HTMLElement;
|
|
127
|
+
|
|
128
|
+
if (elementEl.hasAttribute("ff-xpath") && !elementEl.getAttribute("ff-xpath")?.includes("/iframe")) {
|
|
129
|
+
const attrValue = elementEl.getAttribute("ff-xpath");
|
|
130
|
+
const name = `${attrValue}`;
|
|
131
|
+
return name || null;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return null;
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const getXpathByPlaceholder = (element: Element): string | null => {
|
|
138
|
+
const selector = element.nodeName.toLowerCase();
|
|
139
|
+
const elementEl = element as HTMLElement;
|
|
140
|
+
|
|
141
|
+
if (elementEl.hasAttribute("placeholder")) {
|
|
142
|
+
const attrValue = elementEl.getAttribute("placeholder");
|
|
143
|
+
const xpath = `//${selector}[@placeholder='${attrValue}']`;
|
|
144
|
+
return getXPath(element, xpath) || null;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return null;
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
const getXpathByType = (element: Element): string | null => {
|
|
153
|
+
const selector = element.nodeName.toLowerCase();
|
|
154
|
+
const elementEl = element as HTMLElement;
|
|
155
|
+
|
|
156
|
+
if (elementEl.hasAttribute("type")) {
|
|
157
|
+
const attrValue = elementEl.getAttribute("type");
|
|
158
|
+
const xpath = `//${selector}[@type='${attrValue}']`;
|
|
159
|
+
return getXPath(element, xpath) || null;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return null;
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
const getXPathAbsolute = (element: Element): string => {
|
|
166
|
+
const path: string[] = [];
|
|
167
|
+
while (element && element.nodeType === 1) {
|
|
168
|
+
// ELEMENT_NODE
|
|
169
|
+
let selector = element.nodeName.toLowerCase();
|
|
170
|
+
let sibling = element;
|
|
171
|
+
let siblingCount = 1;
|
|
172
|
+
|
|
173
|
+
while ((sibling = sibling.previousElementSibling as Element)) {
|
|
174
|
+
if (sibling.nodeName.toLowerCase() === element.nodeName.toLowerCase()) {
|
|
175
|
+
siblingCount++;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (siblingCount > 1) {
|
|
180
|
+
selector += `[${siblingCount}]`;
|
|
181
|
+
}
|
|
182
|
+
path.unshift(selector);
|
|
183
|
+
element = element.parentNode as Element;
|
|
184
|
+
}
|
|
185
|
+
return "//" + path.join("/");
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
const relations: string[] = [
|
|
189
|
+
"/preceding-sibling",
|
|
190
|
+
"/following-sibling",
|
|
191
|
+
"/parent",
|
|
192
|
+
"/descendant",
|
|
193
|
+
"/ancestor",
|
|
194
|
+
"/self",
|
|
195
|
+
"/ancestor-or-self",
|
|
196
|
+
"/child",
|
|
197
|
+
"/preceding",
|
|
198
|
+
"/following",
|
|
199
|
+
];
|
|
200
|
+
|
|
201
|
+
function getElementFromXPath(
|
|
202
|
+
tempDiv: HTMLElement,
|
|
203
|
+
xpath: string
|
|
204
|
+
): Element | null {
|
|
205
|
+
const currentElement: Element | null = tempDiv;
|
|
206
|
+
const window = currentElement.ownerDocument.defaultView;
|
|
207
|
+
if (!window) return null;
|
|
208
|
+
|
|
209
|
+
const xpathEvaluator = new window.XPathEvaluator();
|
|
210
|
+
const xpathResult = xpathEvaluator.evaluate(
|
|
211
|
+
xpath,
|
|
212
|
+
currentElement.ownerDocument,
|
|
213
|
+
null,
|
|
214
|
+
window.XPathResult.FIRST_ORDERED_NODE_TYPE,
|
|
215
|
+
null
|
|
216
|
+
);
|
|
217
|
+
return xpathResult.singleNodeValue as Element | null;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function checkReferenceElementIsValid(
|
|
221
|
+
locator: string,
|
|
222
|
+
relation: string,
|
|
223
|
+
tempDiv: HTMLElement
|
|
224
|
+
): string | null {
|
|
225
|
+
if (locator.includes(relation)) {
|
|
226
|
+
const locatotSplitArray: string[] = locator.split(relation);
|
|
227
|
+
const sourceLoc = locatotSplitArray[0].trim();
|
|
228
|
+
const currentElement: Element | null = tempDiv;
|
|
229
|
+
const window = currentElement.ownerDocument.defaultView;
|
|
230
|
+
if (!window) return null;
|
|
231
|
+
if (!locator.includes("dynamic")) {
|
|
232
|
+
const xpathEvaluator = new window.XPathEvaluator();
|
|
233
|
+
const xpathResult = xpathEvaluator.evaluate(
|
|
234
|
+
sourceLoc,
|
|
235
|
+
currentElement.ownerDocument,
|
|
236
|
+
null,
|
|
237
|
+
window.XPathResult.FIRST_ORDERED_NODE_TYPE,
|
|
238
|
+
null
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
const sourceElement = xpathResult.singleNodeValue;
|
|
242
|
+
if (sourceElement) {
|
|
243
|
+
const xpathResultComplete = xpathEvaluator.evaluate(
|
|
244
|
+
locator,
|
|
245
|
+
currentElement.ownerDocument,
|
|
246
|
+
null,
|
|
247
|
+
window.XPathResult.FIRST_ORDERED_NODE_TYPE,
|
|
248
|
+
null
|
|
249
|
+
);
|
|
250
|
+
const completeElement = xpathResultComplete.singleNodeValue;
|
|
251
|
+
let relativeXpath: string;
|
|
252
|
+
if (completeElement) {
|
|
253
|
+
relativeXpath = locator;
|
|
254
|
+
return relativeXpath;
|
|
255
|
+
} else {
|
|
256
|
+
logger.error("Complete Locator is Invalid:", locator);
|
|
257
|
+
relativeXpath = locator;
|
|
258
|
+
return relativeXpath;
|
|
259
|
+
}
|
|
260
|
+
} else {
|
|
261
|
+
logger.error("Source Locator Not Found:", sourceLoc);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
return null;
|
|
266
|
+
}
|
|
267
|
+
interface Locator {
|
|
268
|
+
name: string;
|
|
269
|
+
type: string;
|
|
270
|
+
value: string;
|
|
271
|
+
reference: string;
|
|
272
|
+
status: string;
|
|
273
|
+
isRecorded: string;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const getElementsFromHTML = (
|
|
277
|
+
locators: Locator[],
|
|
278
|
+
htmlString: string
|
|
279
|
+
): ElementDetails | null => {
|
|
280
|
+
const virtualConsole = new VirtualConsole();
|
|
281
|
+
const dom = new JSDOM(htmlString, {
|
|
282
|
+
resources: "usable",
|
|
283
|
+
runScripts: "outside-only", // Prevents inline script execution in JSDOM
|
|
284
|
+
pretendToBeVisual: true,
|
|
285
|
+
virtualConsole,
|
|
286
|
+
includeNodeLocations: true,
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
const document = dom.window.document;
|
|
290
|
+
global.SVGElement = dom.window.SVGElement;
|
|
291
|
+
const tempDiv = document.createElement("div");
|
|
292
|
+
const elementsToRemove = document.querySelectorAll(
|
|
293
|
+
"script, style, link[rel='stylesheet'], meta, noscript, embed, object, param, source, svg"
|
|
294
|
+
);
|
|
295
|
+
|
|
296
|
+
if (elementsToRemove) {
|
|
297
|
+
elementsToRemove.forEach((tag) => {
|
|
298
|
+
(tag as Element).remove();
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
tempDiv.innerHTML = document.body.innerHTML;
|
|
303
|
+
// tempDiv.innerHTML = htmlString;
|
|
304
|
+
const finalLocators: any = [];
|
|
305
|
+
|
|
306
|
+
for (const locator of locators) {
|
|
307
|
+
try {
|
|
308
|
+
const isDynamic = String(locator.value || locator.type || "");
|
|
309
|
+
if (
|
|
310
|
+
isDynamic.includes("dynamic") ||
|
|
311
|
+
isDynamic.match("dynamic") ||
|
|
312
|
+
isDynamic.includes("{") ||
|
|
313
|
+
isDynamic.includes("}")
|
|
314
|
+
) {
|
|
315
|
+
finalLocators.push({
|
|
316
|
+
name: locator.name,
|
|
317
|
+
type: locator.type,
|
|
318
|
+
value: locator.value,
|
|
319
|
+
reference: locator.reference,
|
|
320
|
+
status: locator.status,
|
|
321
|
+
isRecorded: locator.isRecorded,
|
|
322
|
+
});
|
|
323
|
+
continue;
|
|
324
|
+
}
|
|
325
|
+
const isRecorded = String(locator.isRecorded || "");
|
|
326
|
+
|
|
327
|
+
if (isRecorded.includes("N") || isRecorded.match("N")) {
|
|
328
|
+
finalLocators.push({
|
|
329
|
+
name: locator.name,
|
|
330
|
+
type: locator.type,
|
|
331
|
+
value: locator.value,
|
|
332
|
+
reference: locator.reference,
|
|
333
|
+
status: locator.status,
|
|
334
|
+
isRecorded: locator.isRecorded,
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
try {
|
|
338
|
+
let targetElement: Element | null = null;
|
|
339
|
+
if (locator.value.startsWith("iframe")) {
|
|
340
|
+
const iframe = tempDiv.querySelector(
|
|
341
|
+
locator.value
|
|
342
|
+
) as HTMLIFrameElement;
|
|
343
|
+
if (iframe) {
|
|
344
|
+
const iframeDocument =
|
|
345
|
+
iframe.contentDocument || iframe.contentWindow?.document;
|
|
346
|
+
if (iframeDocument) {
|
|
347
|
+
targetElement = iframeDocument.querySelector(
|
|
348
|
+
locator.value.slice(6)
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
} else {
|
|
353
|
+
const selectors = locator.value.split(">>>"); // Custom delimiter for shadow DOM
|
|
354
|
+
let currentElement: Element | null = tempDiv;
|
|
355
|
+
|
|
356
|
+
for (const selector of selectors) {
|
|
357
|
+
if (currentElement) {
|
|
358
|
+
const trimmedSelector = selector.trim();
|
|
359
|
+
if (
|
|
360
|
+
locator.name.includes("id") ||
|
|
361
|
+
trimmedSelector.startsWith("#")
|
|
362
|
+
) {
|
|
363
|
+
targetElement = currentElement.querySelector(
|
|
364
|
+
"#" + trimmedSelector
|
|
365
|
+
);
|
|
366
|
+
} else if (
|
|
367
|
+
locator.name.includes("className") ||
|
|
368
|
+
trimmedSelector.startsWith(".")
|
|
369
|
+
) {
|
|
370
|
+
targetElement = currentElement.querySelector(
|
|
371
|
+
"." + trimmedSelector
|
|
372
|
+
);
|
|
373
|
+
} else if (
|
|
374
|
+
(locator.name.includes("xpath") ||
|
|
375
|
+
trimmedSelector.startsWith("//")) &&
|
|
376
|
+
!locator.type.match("dynamic")
|
|
377
|
+
) {
|
|
378
|
+
targetElement = getElementFromXPath(tempDiv, trimmedSelector);
|
|
379
|
+
} else {
|
|
380
|
+
targetElement = currentElement.querySelector(trimmedSelector);
|
|
381
|
+
if (!targetElement) {
|
|
382
|
+
targetElement = getElementFromShadowRoot(
|
|
383
|
+
currentElement,
|
|
384
|
+
trimmedSelector
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
if (
|
|
390
|
+
!targetElement &&
|
|
391
|
+
isSVGElement(currentElement) &&
|
|
392
|
+
!locator.type.match("dynamic")
|
|
393
|
+
) {
|
|
394
|
+
targetElement = currentElement.querySelector(trimmedSelector);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
currentElement = targetElement;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
const locatorExists = (name: string, value: string) => {
|
|
403
|
+
return finalLocators.some(
|
|
404
|
+
(_loc: any) =>
|
|
405
|
+
_loc.name === name &&
|
|
406
|
+
_loc.value === value &&
|
|
407
|
+
(!_loc.value.includes("dynamic") ||
|
|
408
|
+
!locator.type.match("dynamic"))
|
|
409
|
+
);
|
|
410
|
+
};
|
|
411
|
+
|
|
412
|
+
const xpathFunctions = [
|
|
413
|
+
{ name: "xpath", value: getXPathByText },
|
|
414
|
+
{ name: "xpath", value: getXPathById },
|
|
415
|
+
{ name: "xpath", value: getXPathByClass },
|
|
416
|
+
{ name: "xpath", value: getXPathAbsolute },
|
|
417
|
+
{ name: "xpath", value: getXpathByName },
|
|
418
|
+
{ name: "xpath", value: getXpathByPlaceholder },
|
|
419
|
+
{ name: "xpath", value: getXpathByType },
|
|
420
|
+
];
|
|
421
|
+
|
|
422
|
+
if (targetElement) {
|
|
423
|
+
const idValue = getId(targetElement);
|
|
424
|
+
if (idValue && !locatorExists("id", idValue)) {
|
|
425
|
+
locators.forEach(() => {
|
|
426
|
+
if (locator.value === idValue) {
|
|
427
|
+
finalLocators.push({
|
|
428
|
+
name: "id",
|
|
429
|
+
type: locator.type,
|
|
430
|
+
value: idValue,
|
|
431
|
+
reference: locator.reference,
|
|
432
|
+
status: locator.status,
|
|
433
|
+
isRecorded: locator.isRecorded,
|
|
434
|
+
isAiRecorded: "Y",
|
|
435
|
+
});
|
|
436
|
+
} else {
|
|
437
|
+
finalLocators.push({
|
|
438
|
+
name: "id",
|
|
439
|
+
type: locator.type,
|
|
440
|
+
value: idValue,
|
|
441
|
+
reference: locator.reference,
|
|
442
|
+
status: locator.status,
|
|
443
|
+
isRecorded: "Y",
|
|
444
|
+
isAiRecorded: "Y",
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
if (getVisibleText(targetElement)) {
|
|
452
|
+
const textValue = getVisibleText(targetElement);
|
|
453
|
+
locators.forEach((_loc) => {
|
|
454
|
+
if (_loc.value === textValue) {
|
|
455
|
+
finalLocators.push({
|
|
456
|
+
name: "linkText",
|
|
457
|
+
type: "static",
|
|
458
|
+
value: textValue,
|
|
459
|
+
reference: locator.reference,
|
|
460
|
+
status: locator.status,
|
|
461
|
+
isRecorded: locator.isRecorded,
|
|
462
|
+
isAiRecorded: "Y",
|
|
463
|
+
});
|
|
464
|
+
} else {
|
|
465
|
+
finalLocators.push({
|
|
466
|
+
name: "linkText",
|
|
467
|
+
type: "static",
|
|
468
|
+
value: textValue,
|
|
469
|
+
reference: locator.reference,
|
|
470
|
+
status: locator.status,
|
|
471
|
+
isRecorded: "Y",
|
|
472
|
+
isAiRecorded: "Y",
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
if (getName(targetElement)) {
|
|
479
|
+
const nameLocator = getName(targetElement);
|
|
480
|
+
locators.forEach((_loc) => {
|
|
481
|
+
if (_loc.value === nameLocator) {
|
|
482
|
+
finalLocators.push({
|
|
483
|
+
name: "name",
|
|
484
|
+
type: "static",
|
|
485
|
+
value: nameLocator,
|
|
486
|
+
reference: locator.reference,
|
|
487
|
+
status: locator.status,
|
|
488
|
+
isRecorded: locator.isRecorded,
|
|
489
|
+
isAiRecorded: "Y",
|
|
490
|
+
});
|
|
491
|
+
} else {
|
|
492
|
+
finalLocators.push({
|
|
493
|
+
name: "name",
|
|
494
|
+
type: "static",
|
|
495
|
+
value: nameLocator,
|
|
496
|
+
reference: locator.reference,
|
|
497
|
+
status: locator.status,
|
|
498
|
+
isRecorded: "Y",
|
|
499
|
+
isAiRecorded: "Y",
|
|
500
|
+
});
|
|
501
|
+
}
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
if (getFFxpath(targetElement)) {
|
|
505
|
+
const nameLocator = getFFxpath(targetElement);
|
|
506
|
+
locators.forEach((_loc) => {
|
|
507
|
+
if (_loc.value === nameLocator) {
|
|
508
|
+
finalLocators.push({
|
|
509
|
+
name: "xpath",
|
|
510
|
+
type: "static",
|
|
511
|
+
value: nameLocator,
|
|
512
|
+
reference: locator.reference,
|
|
513
|
+
status: locator.status,
|
|
514
|
+
isRecorded: locator.isRecorded,
|
|
515
|
+
isAiRecorded: "Y",
|
|
516
|
+
});
|
|
517
|
+
} else {
|
|
518
|
+
finalLocators.push({
|
|
519
|
+
name: "xpath",
|
|
520
|
+
type: "static",
|
|
521
|
+
value: nameLocator,
|
|
522
|
+
reference: locator.reference,
|
|
523
|
+
status: locator.status,
|
|
524
|
+
isRecorded: "Y",
|
|
525
|
+
isAiRecorded: "Y",
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
const classValue = getClassName(targetElement);
|
|
532
|
+
if (
|
|
533
|
+
classValue &&
|
|
534
|
+
classValue.trim() !== "" &&
|
|
535
|
+
!locatorExists("className", classValue)
|
|
536
|
+
) {
|
|
537
|
+
locators.forEach((_loc) => {
|
|
538
|
+
if (_loc.value === classValue) {
|
|
539
|
+
finalLocators.push({
|
|
540
|
+
name: "className",
|
|
541
|
+
type: locator.type,
|
|
542
|
+
value: classValue,
|
|
543
|
+
reference: locator.reference,
|
|
544
|
+
status: locator.status,
|
|
545
|
+
isRecorded: locator.isRecorded,
|
|
546
|
+
isAiRecorded: "Y",
|
|
547
|
+
});
|
|
548
|
+
} else {
|
|
549
|
+
finalLocators.push({
|
|
550
|
+
name: "className",
|
|
551
|
+
type: locator.type,
|
|
552
|
+
value: classValue,
|
|
553
|
+
reference: locator.reference,
|
|
554
|
+
status: locator.status,
|
|
555
|
+
isRecorded: "Y",
|
|
556
|
+
isAiRecorded: "Y",
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
xpathFunctions.forEach((fn) => {
|
|
562
|
+
locators.forEach((_loc) => {
|
|
563
|
+
if (_loc.value === fn.value(targetElement)) {
|
|
564
|
+
finalLocators.push({
|
|
565
|
+
name: fn.name,
|
|
566
|
+
type: locator.type,
|
|
567
|
+
value: fn.value(targetElement),
|
|
568
|
+
reference: locator.reference,
|
|
569
|
+
status: locator.status,
|
|
570
|
+
isRecorded: locator.isRecorded,
|
|
571
|
+
isAiRecorded: "Y",
|
|
572
|
+
});
|
|
573
|
+
} else {
|
|
574
|
+
finalLocators.push({
|
|
575
|
+
name: fn.name,
|
|
576
|
+
type: locator.type,
|
|
577
|
+
value: fn.value(targetElement),
|
|
578
|
+
reference: locator.reference,
|
|
579
|
+
status: locator.status,
|
|
580
|
+
isRecorded: "Y",
|
|
581
|
+
isAiRecorded: "Y",
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
});
|
|
585
|
+
});
|
|
586
|
+
|
|
587
|
+
for (const locator of locators) {
|
|
588
|
+
try {
|
|
589
|
+
let relativeXpath = null;
|
|
590
|
+
if (locator.value) {
|
|
591
|
+
for (const relation of relations) {
|
|
592
|
+
if (locator.value.includes(relation)) {
|
|
593
|
+
relativeXpath = checkReferenceElementIsValid(
|
|
594
|
+
locator.value,
|
|
595
|
+
relation,
|
|
596
|
+
tempDiv
|
|
597
|
+
);
|
|
598
|
+
|
|
599
|
+
if (relativeXpath) {
|
|
600
|
+
finalLocators.push({
|
|
601
|
+
name: "xpath",
|
|
602
|
+
type: locator.type,
|
|
603
|
+
value: relativeXpath,
|
|
604
|
+
reference: locator.reference,
|
|
605
|
+
status: locator.status,
|
|
606
|
+
isRecorded:
|
|
607
|
+
locator.isRecorded !== "" &&
|
|
608
|
+
locator.isRecorded !== null
|
|
609
|
+
? locator.isRecorded
|
|
610
|
+
: "Y",
|
|
611
|
+
});
|
|
612
|
+
break;
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
} catch (error: any) {
|
|
618
|
+
logger.error(`[${Object.getPrototypeOf(this).constructor.name}] : Error processing locator:`, locator, error.message);
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
const uniqueLocators = new Map();
|
|
623
|
+
finalLocators.forEach((_loc: any) => {
|
|
624
|
+
if (!uniqueLocators.has(_loc.value)) {
|
|
625
|
+
uniqueLocators.set(_loc.value, _loc);
|
|
626
|
+
}
|
|
627
|
+
});
|
|
628
|
+
|
|
629
|
+
const ffLoc = Array.from(uniqueLocators.values());
|
|
630
|
+
|
|
631
|
+
const jsonResult = [
|
|
632
|
+
{
|
|
633
|
+
locators: ffLoc.filter(
|
|
634
|
+
(locator) =>
|
|
635
|
+
Object.prototype.hasOwnProperty.call(locator, "value") &&
|
|
636
|
+
locator?.value !== null &&
|
|
637
|
+
locator.value !== ""
|
|
638
|
+
),
|
|
639
|
+
},
|
|
640
|
+
];
|
|
641
|
+
|
|
642
|
+
return jsonResult;
|
|
643
|
+
}
|
|
644
|
+
} catch (error: any) {
|
|
645
|
+
logger.error(`[${Object.getPrototypeOf(this).constructor.name}] : Error processing locator:`, locator, error.message);
|
|
646
|
+
continue;
|
|
647
|
+
}
|
|
648
|
+
} catch (error: any) {
|
|
649
|
+
logger.error(`[${Object.getPrototypeOf(this).constructor.name}] : Error processing locator:`, locator, error.message);
|
|
650
|
+
continue;
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
return null;
|
|
654
|
+
};
|
|
655
|
+
|
|
656
|
+
export { getElementsFromHTML };
|