ff-dom 1.0.17 → 1.0.18-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +93 -93
- package/dist/index.browser.cjs +2773 -0
- package/dist/index.browser.cjs.map +1 -0
- package/dist/index.browser.js +2768 -0
- package/dist/index.browser.js.map +1 -0
- package/dist/index.cdn.js +2775 -0
- package/dist/types/browser/browser/xpath.d.ts +110 -0
- package/dist/types/browser/types/locator.d.ts +26 -0
- package/dist/types/browser/utils/cssSelector.d.ts +18 -0
- package/dist/types/browser/utils/getElementsFromHTML.d.ts +17 -0
- package/dist/types/browser/utils/referenceXpath.d.ts +27 -0
- package/dist/types/browser/utils/xpath.d.ts +29 -0
- package/dist/types/browser/utils/xpathHelpers.d.ts +88 -0
- package/dist/types/node/node/xpath.d.ts +8 -0
- package/dist/types/node/types/locator.d.ts +26 -0
- package/dist/xpath.mjs +63 -0
- package/dist/xpath.mjs.map +1 -0
- package/package.json +58 -8
- package/src/browser/xpath.ts +15 -0
- package/src/index.d.ts +2 -0
- package/src/node/xpath.d.ts +9 -0
- package/src/node/xpath.ts +75 -0
- package/src/types/locator.ts +27 -0
- package/src/utils/cssSelector.ts +275 -0
- package/src/utils/getElementsFromHTML.ts +513 -593
- package/src/utils/referenceXpath.ts +936 -0
- package/src/utils/xpath.ts +1105 -0
- package/src/utils/xpathHelpers.ts +1299 -0
- package/tsconfig.browser.json +11 -0
- package/tsconfig.json +17 -13
- package/tsconfig.node.json +11 -0
- package/dist/index.js +0 -5
- package/dist/utils/getElementsFromHTML.js +0 -469
- package/src/index.ts +0 -3
package/tsconfig.json
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "
|
|
4
|
-
"lib": ["dom", "
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
"
|
|
8
|
-
"
|
|
9
|
-
|
|
10
|
-
"
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "esnext",
|
|
4
|
+
"lib": ["dom", "ESNext"],
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"module": "esnext",
|
|
7
|
+
"allowImportingTsExtensions": true,
|
|
8
|
+
"noEmit": false,
|
|
9
|
+
|
|
10
|
+
"strict": true,
|
|
11
|
+
"esModuleInterop": true,
|
|
12
|
+
"skipLibCheck": true,
|
|
13
|
+
"forceConsistentCasingInFileNames": true,
|
|
14
|
+
"allowSyntheticDefaultImports": true,
|
|
15
|
+
},
|
|
16
|
+
"include": ["**/*.ts", "test/node.spec.js"],
|
|
17
|
+
"exclude": ["node_modules", "rollup.config.js"]
|
|
14
18
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "./tsconfig.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"emitDeclarationOnly": true,
|
|
5
|
+
"declaration": true,
|
|
6
|
+
"outDir": "dist/node",
|
|
7
|
+
"declarationDir": "dist/types/node"
|
|
8
|
+
},
|
|
9
|
+
"exclude": ["src/browser"],
|
|
10
|
+
"include": ["src/node", "src/node/xpath.d.ts"]
|
|
11
|
+
}
|
package/dist/index.js
DELETED
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getElementsFromHTML = void 0;
|
|
4
|
-
const getElementsFromHTML_1 = require("./utils/getElementsFromHTML");
|
|
5
|
-
Object.defineProperty(exports, "getElementsFromHTML", { enumerable: true, get: function () { return getElementsFromHTML_1.getElementsFromHTML; } });
|
|
@@ -1,469 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getElementsFromHTML = void 0;
|
|
4
|
-
const jsdom_1 = require("jsdom");
|
|
5
|
-
const isUnique = (xpathResult) => {
|
|
6
|
-
return xpathResult && xpathResult.snapshotLength === 1;
|
|
7
|
-
};
|
|
8
|
-
const getElementFromShadowRoot = (element, selector) => {
|
|
9
|
-
const shadowRoot = element.shadowRoot;
|
|
10
|
-
if (shadowRoot && !selector.includes('dynamic')) {
|
|
11
|
-
return shadowRoot.querySelector(selector);
|
|
12
|
-
}
|
|
13
|
-
return null;
|
|
14
|
-
};
|
|
15
|
-
const isSVGElement = (element) => {
|
|
16
|
-
return (typeof window !== 'undefined' &&
|
|
17
|
-
typeof SVGElement !== 'undefined' &&
|
|
18
|
-
element instanceof SVGElement);
|
|
19
|
-
};
|
|
20
|
-
const getXPath = (element, xpath) => {
|
|
21
|
-
const window = element.ownerDocument.defaultView;
|
|
22
|
-
if (!window)
|
|
23
|
-
return null;
|
|
24
|
-
const xpathEvaluator = new window.XPathEvaluator();
|
|
25
|
-
const xpathResult = xpathEvaluator.evaluate(xpath, element.ownerDocument, null, window.XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
|
|
26
|
-
return isUnique(xpathResult) ? xpath : null;
|
|
27
|
-
};
|
|
28
|
-
const getId = (element) => {
|
|
29
|
-
return (element === null || element === void 0 ? void 0 : element.id) || null;
|
|
30
|
-
};
|
|
31
|
-
const getClassName = (element) => {
|
|
32
|
-
return element.className || null;
|
|
33
|
-
};
|
|
34
|
-
const getVisibleText = (element) => {
|
|
35
|
-
var _a;
|
|
36
|
-
return ((_a = element.textContent) === null || _a === void 0 ? void 0 : _a.trim()) || null;
|
|
37
|
-
};
|
|
38
|
-
const getXPathByText = (element) => {
|
|
39
|
-
const text = getVisibleText(element);
|
|
40
|
-
if (text) {
|
|
41
|
-
const xpath = `//${element.nodeName.toLowerCase()}[text()='${text}']`;
|
|
42
|
-
return getXPath(element, xpath);
|
|
43
|
-
}
|
|
44
|
-
return null;
|
|
45
|
-
};
|
|
46
|
-
const getXPathById = (element) => {
|
|
47
|
-
if (element.id) {
|
|
48
|
-
const xpath = `//${element.nodeName.toLowerCase()}[@id='${element.id}']`;
|
|
49
|
-
return getXPath(element, xpath);
|
|
50
|
-
}
|
|
51
|
-
return null;
|
|
52
|
-
};
|
|
53
|
-
const getXPathByClass = (element) => {
|
|
54
|
-
const classNames = element.classList;
|
|
55
|
-
if (classNames.length > 0) {
|
|
56
|
-
const xpath = `//${element.nodeName.toLowerCase()}[@class='${classNames[0]}']`;
|
|
57
|
-
return getXPath(element, xpath);
|
|
58
|
-
}
|
|
59
|
-
return null;
|
|
60
|
-
};
|
|
61
|
-
const getXpathByName = (element) => {
|
|
62
|
-
const selector = element.nodeName.toLowerCase();
|
|
63
|
-
const elementEl = element;
|
|
64
|
-
if (elementEl.hasAttribute('name')) {
|
|
65
|
-
const attrValue = elementEl.getAttribute('name');
|
|
66
|
-
const xpath = `//${selector}[@name='${attrValue}']`;
|
|
67
|
-
return getXPath(element, xpath) || null;
|
|
68
|
-
}
|
|
69
|
-
return null;
|
|
70
|
-
};
|
|
71
|
-
const getName = (element) => {
|
|
72
|
-
const elementEl = element;
|
|
73
|
-
if (elementEl.hasAttribute('name')) {
|
|
74
|
-
const attrValue = elementEl.getAttribute('name');
|
|
75
|
-
const name = `${attrValue}`;
|
|
76
|
-
return name || null;
|
|
77
|
-
}
|
|
78
|
-
return null;
|
|
79
|
-
};
|
|
80
|
-
const getXpathByPlaceholder = (element) => {
|
|
81
|
-
const selector = element.nodeName.toLowerCase();
|
|
82
|
-
const elementEl = element;
|
|
83
|
-
if (elementEl.hasAttribute('placeholder')) {
|
|
84
|
-
const attrValue = elementEl.getAttribute('placeholder');
|
|
85
|
-
const xpath = `//${selector}[@placeholder='${attrValue}']`;
|
|
86
|
-
return getXPath(element, xpath) || null;
|
|
87
|
-
}
|
|
88
|
-
return null;
|
|
89
|
-
};
|
|
90
|
-
const getXpathByType = (element) => {
|
|
91
|
-
const selector = element.nodeName.toLowerCase();
|
|
92
|
-
const elementEl = element;
|
|
93
|
-
if (elementEl.hasAttribute('type')) {
|
|
94
|
-
const attrValue = elementEl.getAttribute('type');
|
|
95
|
-
const xpath = `//${selector}[@type='${attrValue}']`;
|
|
96
|
-
return getXPath(element, xpath) || null;
|
|
97
|
-
}
|
|
98
|
-
return null;
|
|
99
|
-
};
|
|
100
|
-
const getXPathAbsolute = (element) => {
|
|
101
|
-
const path = [];
|
|
102
|
-
while (element && element.nodeType === 1) {
|
|
103
|
-
// ELEMENT_NODE
|
|
104
|
-
let selector = element.nodeName.toLowerCase();
|
|
105
|
-
let sibling = element;
|
|
106
|
-
let siblingCount = 1;
|
|
107
|
-
while ((sibling = sibling.previousElementSibling)) {
|
|
108
|
-
if (sibling.nodeName.toLowerCase() === element.nodeName.toLowerCase()) {
|
|
109
|
-
siblingCount++;
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
if (siblingCount > 1) {
|
|
113
|
-
selector += `[${siblingCount}]`;
|
|
114
|
-
}
|
|
115
|
-
path.unshift(selector);
|
|
116
|
-
element = element.parentNode;
|
|
117
|
-
}
|
|
118
|
-
return '//' + path.join('/');
|
|
119
|
-
};
|
|
120
|
-
const relations = [
|
|
121
|
-
'/preceding-sibling',
|
|
122
|
-
'/following-sibling',
|
|
123
|
-
'/parent',
|
|
124
|
-
'/descendant',
|
|
125
|
-
'/ancestor',
|
|
126
|
-
'/self',
|
|
127
|
-
'/ancestor-or-self',
|
|
128
|
-
'/child',
|
|
129
|
-
'/preceding',
|
|
130
|
-
'/following',
|
|
131
|
-
];
|
|
132
|
-
const normalizeXPath = (xpath) => {
|
|
133
|
-
// Replace text() comparisons like text() = 'something' with normalize-space(.) = 'something'
|
|
134
|
-
// to avoid issues with whitespace differences
|
|
135
|
-
xpath = xpath.replace(/text\(\)\s*=\s*['"](.+?)['"]/g, "normalize-space(.)='$1'");
|
|
136
|
-
// Replace direct . = 'something' comparisons with normalize-space(.) = 'something' for consistent text matching
|
|
137
|
-
xpath = xpath.replace(/\.\s*=\s*['"](.+?)['"]/g, "normalize-space(.)='$1'");
|
|
138
|
-
return xpath;
|
|
139
|
-
};
|
|
140
|
-
function getElementFromXPath(tempDiv, xpath) {
|
|
141
|
-
let currentElement = tempDiv;
|
|
142
|
-
const window = currentElement.ownerDocument.defaultView;
|
|
143
|
-
if (!window)
|
|
144
|
-
return null;
|
|
145
|
-
const xpathEvaluator = new window.XPathEvaluator();
|
|
146
|
-
const xpathResult = xpathEvaluator.evaluate(xpath, currentElement.ownerDocument, //here even tempDiv can be passed
|
|
147
|
-
null, window.XPathResult.FIRST_ORDERED_NODE_TYPE, null);
|
|
148
|
-
return xpathResult.singleNodeValue;
|
|
149
|
-
}
|
|
150
|
-
function checkReferenceElementIsValid(locator, relation, tempDiv) {
|
|
151
|
-
if (locator.includes(relation)) {
|
|
152
|
-
const locatotSplitArray = locator.split(relation);
|
|
153
|
-
const sourceLoc = locatotSplitArray[0].trim();
|
|
154
|
-
let currentElement = tempDiv;
|
|
155
|
-
const window = currentElement.ownerDocument.defaultView;
|
|
156
|
-
if (!window)
|
|
157
|
-
return null;
|
|
158
|
-
if (!locator.includes('dynamic')) {
|
|
159
|
-
const xpathEvaluator = new window.XPathEvaluator();
|
|
160
|
-
const xpathResult = xpathEvaluator.evaluate(sourceLoc, currentElement.ownerDocument, null, window.XPathResult.FIRST_ORDERED_NODE_TYPE, null);
|
|
161
|
-
const sourceElement = xpathResult.singleNodeValue;
|
|
162
|
-
if (sourceElement) {
|
|
163
|
-
const xpathResultComplete = xpathEvaluator.evaluate(locator, currentElement.ownerDocument, null, window.XPathResult.FIRST_ORDERED_NODE_TYPE, null);
|
|
164
|
-
const completeElement = xpathResultComplete.singleNodeValue;
|
|
165
|
-
let relativeXpath;
|
|
166
|
-
if (completeElement) {
|
|
167
|
-
relativeXpath = locator;
|
|
168
|
-
return relativeXpath;
|
|
169
|
-
}
|
|
170
|
-
else {
|
|
171
|
-
console.error('Complete Locator is Invalid:', locator);
|
|
172
|
-
relativeXpath = locator;
|
|
173
|
-
return relativeXpath;
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
else {
|
|
177
|
-
console.error('Source Locator Not Found:', sourceLoc);
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
return null;
|
|
182
|
-
}
|
|
183
|
-
const getElementsFromHTML = (name, desc, type, locators, isShared, projectId, projectType, isRecorded, folder, parentId, parentName, platform, licenseId, licenseType, userId, htmlString) => {
|
|
184
|
-
var _a;
|
|
185
|
-
const virtualConsole = new jsdom_1.VirtualConsole();
|
|
186
|
-
const dom = new jsdom_1.JSDOM(htmlString, {
|
|
187
|
-
resources: 'usable',
|
|
188
|
-
runScripts: 'outside-only', // Prevents inline script execution in JSDOM
|
|
189
|
-
pretendToBeVisual: true,
|
|
190
|
-
virtualConsole,
|
|
191
|
-
includeNodeLocations: true,
|
|
192
|
-
});
|
|
193
|
-
const document = dom.window.document;
|
|
194
|
-
global.SVGElement = dom.window.SVGElement;
|
|
195
|
-
const tempDiv = document.createElement('div');
|
|
196
|
-
const elementsToRemove = document.querySelectorAll("script, style, link[rel='stylesheet'], meta, noscript, embed, object, param, source, svg");
|
|
197
|
-
if (elementsToRemove) {
|
|
198
|
-
elementsToRemove.forEach((tag) => {
|
|
199
|
-
tag.remove();
|
|
200
|
-
});
|
|
201
|
-
}
|
|
202
|
-
tempDiv.innerHTML = document.body.innerHTML;
|
|
203
|
-
const finalLocatorsSet = new Set();
|
|
204
|
-
let finalLocators = [];
|
|
205
|
-
function createLocator(base, overrides = {}) {
|
|
206
|
-
var _a, _b, _c, _d, _e, _f;
|
|
207
|
-
const newLocator = {
|
|
208
|
-
name: (_a = overrides.name) !== null && _a !== void 0 ? _a : base === null || base === void 0 ? void 0 : base.name,
|
|
209
|
-
type: (_b = overrides.type) !== null && _b !== void 0 ? _b : base === null || base === void 0 ? void 0 : base.type,
|
|
210
|
-
value: (_c = overrides.value) !== null && _c !== void 0 ? _c : base === null || base === void 0 ? void 0 : base.value,
|
|
211
|
-
reference: (_d = overrides.reference) !== null && _d !== void 0 ? _d : base === null || base === void 0 ? void 0 : base.reference,
|
|
212
|
-
status: (_e = overrides.status) !== null && _e !== void 0 ? _e : base === null || base === void 0 ? void 0 : base.status,
|
|
213
|
-
isRecorded: (_f = overrides.isRecorded) !== null && _f !== void 0 ? _f : base === null || base === void 0 ? void 0 : base.isRecorded,
|
|
214
|
-
};
|
|
215
|
-
if (overrides.hasOwnProperty('isSelfHealed')) {
|
|
216
|
-
newLocator.isSelfHealed = overrides.isSelfHealed;
|
|
217
|
-
}
|
|
218
|
-
else if (base === null || base === void 0 ? void 0 : base.hasOwnProperty('isSelfHealed')) {
|
|
219
|
-
newLocator.isSelfHealed = base.isSelfHealed;
|
|
220
|
-
}
|
|
221
|
-
pushUniqueLocator(newLocator);
|
|
222
|
-
}
|
|
223
|
-
function pushUniqueLocator(obj) {
|
|
224
|
-
const key = `${obj.name}:${obj.value}`;
|
|
225
|
-
if (!finalLocatorsSet.has(key)) {
|
|
226
|
-
finalLocatorsSet.add(key);
|
|
227
|
-
finalLocators.push(obj);
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
/** Locator Value Cleaner (Handles Special Scenarios) **/
|
|
231
|
-
const cleanLocatorValue = (val, type, isRecorded) => {
|
|
232
|
-
if (!val)
|
|
233
|
-
return null;
|
|
234
|
-
let cleaned = val.trim();
|
|
235
|
-
// Return null for empty or literal "null"
|
|
236
|
-
if (!cleaned || cleaned.toLowerCase() === 'null')
|
|
237
|
-
return null;
|
|
238
|
-
// Unescape any escaped quotes
|
|
239
|
-
cleaned = cleaned.replace(/\\"/g, '"').replace(/\\'/g, "'");
|
|
240
|
-
// Remove surrounding single or double quotes
|
|
241
|
-
cleaned = cleaned.replace(/^['"](.+?)['"]$/, '$1');
|
|
242
|
-
// Replace double single quotes with a single quote inside XPath
|
|
243
|
-
cleaned = cleaned.replace(/''/g, "'");
|
|
244
|
-
// Normalize double quotes in XPath attribute selectors [@id="" -> [@id='']
|
|
245
|
-
cleaned = cleaned.replace(/\[@(id|name)=['"]{2}(.+?)['"]{2}\]/g, "[@$1='$2']");
|
|
246
|
-
// For DOM selectors (id or name), remove ALL quotes
|
|
247
|
-
if (type === 'id' || type === 'name') {
|
|
248
|
-
cleaned = cleaned.replace(/['"]/g, '').trim();
|
|
249
|
-
}
|
|
250
|
-
if (type === 'xpath' && isRecorded === 'Y' && !val.startsWith('//'))
|
|
251
|
-
return null;
|
|
252
|
-
// Final check for empty strings
|
|
253
|
-
if (!cleaned || /^['"]{2}$/.test(cleaned))
|
|
254
|
-
return null;
|
|
255
|
-
return cleaned;
|
|
256
|
-
};
|
|
257
|
-
locators: for (const locator of locators) {
|
|
258
|
-
try {
|
|
259
|
-
const isRecorded = String(locator.isRecorded || '');
|
|
260
|
-
const recordedNLocators = locators.filter((l) => l.isRecorded === 'N');
|
|
261
|
-
if (recordedNLocators.length > 0) {
|
|
262
|
-
for (const locator of recordedNLocators) {
|
|
263
|
-
createLocator(locator);
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
const isDynamic = String(locator.value || locator.type || '');
|
|
267
|
-
if (isDynamic.includes('dynamic') || isDynamic.match('dynamic') ||
|
|
268
|
-
isDynamic.includes('{') || isDynamic.includes('}')) {
|
|
269
|
-
createLocator(locator);
|
|
270
|
-
continue;
|
|
271
|
-
}
|
|
272
|
-
if (isShared.includes('Y')) {
|
|
273
|
-
break locators;
|
|
274
|
-
}
|
|
275
|
-
for (const relation of relations) {
|
|
276
|
-
try {
|
|
277
|
-
let targetElement = null;
|
|
278
|
-
if (locator.value.startsWith('iframe')) {
|
|
279
|
-
const iframe = tempDiv.querySelector(locator.value);
|
|
280
|
-
if (iframe) {
|
|
281
|
-
const iframeDocument = iframe.contentDocument || ((_a = iframe.contentWindow) === null || _a === void 0 ? void 0 : _a.document);
|
|
282
|
-
if (iframeDocument) {
|
|
283
|
-
targetElement = iframeDocument.querySelector(locator.value.slice(6));
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
else {
|
|
288
|
-
const selectors = locator.value.split('>>>'); // Custom delimiter for shadow DOM
|
|
289
|
-
let currentElement = tempDiv;
|
|
290
|
-
for (const selector of selectors) {
|
|
291
|
-
if (currentElement) {
|
|
292
|
-
const trimmedSelector = selector.trim();
|
|
293
|
-
if (locator.name.includes('id') || trimmedSelector.startsWith('#')) {
|
|
294
|
-
targetElement = currentElement.querySelector('#' + trimmedSelector);
|
|
295
|
-
}
|
|
296
|
-
else if (locator.name.includes('className') || trimmedSelector.startsWith('.')) {
|
|
297
|
-
targetElement = currentElement.querySelector('.' + trimmedSelector);
|
|
298
|
-
}
|
|
299
|
-
else if ((locator.name.includes('xpath') || trimmedSelector.startsWith('//')) &&
|
|
300
|
-
!locator.type.match('dynamic')) {
|
|
301
|
-
if (tempDiv.innerHTML) {
|
|
302
|
-
const normalizedXPath = normalizeXPath(trimmedSelector);
|
|
303
|
-
targetElement = getElementFromXPath(tempDiv, normalizedXPath);
|
|
304
|
-
if (targetElement) {
|
|
305
|
-
createLocator(locator, {
|
|
306
|
-
value: trimmedSelector,
|
|
307
|
-
isRecorded: String(locator.isRecorded).includes('N') ? 'N' : 'Y',
|
|
308
|
-
});
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
else {
|
|
313
|
-
targetElement = currentElement.querySelector(trimmedSelector);
|
|
314
|
-
if (!targetElement) {
|
|
315
|
-
targetElement = getElementFromShadowRoot(currentElement, trimmedSelector);
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
if (!targetElement && isSVGElement(currentElement) && !locator.type.match('dynamic')) {
|
|
319
|
-
targetElement = currentElement.querySelector(trimmedSelector);
|
|
320
|
-
}
|
|
321
|
-
currentElement = targetElement;
|
|
322
|
-
}
|
|
323
|
-
else {
|
|
324
|
-
console.error('Element not found at:', selector);
|
|
325
|
-
break;
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
const locatorExists = (name, value) => {
|
|
330
|
-
const key = `${name}:${value}`;
|
|
331
|
-
return finalLocatorsSet.has(key);
|
|
332
|
-
};
|
|
333
|
-
const xpathFunctions = [
|
|
334
|
-
{ name: 'xpath', value: getXPathByText },
|
|
335
|
-
{ name: 'xpath', value: getXPathById },
|
|
336
|
-
{ name: 'xpath', value: getXPathByClass },
|
|
337
|
-
{ name: 'xpath', value: getXPathAbsolute },
|
|
338
|
-
{ name: 'xpath', value: getXpathByName },
|
|
339
|
-
{ name: 'xpath', value: getXpathByPlaceholder },
|
|
340
|
-
{ name: 'xpath', value: getXpathByType },
|
|
341
|
-
];
|
|
342
|
-
if (targetElement) {
|
|
343
|
-
const idValue = getId(targetElement);
|
|
344
|
-
if (idValue && !locatorExists('id', idValue)) {
|
|
345
|
-
locators.forEach((loc) => {
|
|
346
|
-
createLocator(loc, {
|
|
347
|
-
name: 'id',
|
|
348
|
-
value: idValue,
|
|
349
|
-
isRecorded: loc.value === idValue && loc.name === 'id' ? loc.isRecorded : 'Y',
|
|
350
|
-
isSelfHealed: loc.value === idValue && loc.name === 'id' ? loc.isSelfHealed : 'Y'
|
|
351
|
-
});
|
|
352
|
-
});
|
|
353
|
-
}
|
|
354
|
-
const textValue = getVisibleText(targetElement);
|
|
355
|
-
if (textValue) {
|
|
356
|
-
locators.forEach((loc) => {
|
|
357
|
-
createLocator(loc, {
|
|
358
|
-
name: 'linkText',
|
|
359
|
-
type: 'static',
|
|
360
|
-
value: textValue,
|
|
361
|
-
isRecorded: loc.value === textValue ? loc.isRecorded : 'Y',
|
|
362
|
-
isSelfHealed: loc.value === textValue ? loc.isSelfHealed : 'Y',
|
|
363
|
-
});
|
|
364
|
-
});
|
|
365
|
-
}
|
|
366
|
-
const nameLocator = getName(targetElement);
|
|
367
|
-
if (nameLocator && !locatorExists('name', nameLocator)) {
|
|
368
|
-
locators.forEach((loc) => {
|
|
369
|
-
createLocator(loc, {
|
|
370
|
-
name: 'name',
|
|
371
|
-
type: 'static',
|
|
372
|
-
value: nameLocator,
|
|
373
|
-
isRecorded: loc.value === nameLocator && loc.name === 'name' ? loc.isRecorded : 'Y',
|
|
374
|
-
isSelfHealed: loc.value === nameLocator && loc.name === 'name' ? loc.isSelfHealed : 'Y',
|
|
375
|
-
});
|
|
376
|
-
});
|
|
377
|
-
}
|
|
378
|
-
const classValue = getClassName(targetElement);
|
|
379
|
-
if (classValue && classValue.trim() !== '' && !locatorExists('className', classValue)) {
|
|
380
|
-
locators.forEach((loc) => {
|
|
381
|
-
createLocator(loc, {
|
|
382
|
-
name: 'className',
|
|
383
|
-
value: classValue,
|
|
384
|
-
isRecorded: loc.value === classValue && loc.name === 'className' ? loc.isRecorded : 'Y',
|
|
385
|
-
isSelfHealed: loc.value === classValue && loc.name === 'className' ? loc.isSelfHealed : 'Y',
|
|
386
|
-
});
|
|
387
|
-
});
|
|
388
|
-
}
|
|
389
|
-
xpathFunctions.forEach((fn) => {
|
|
390
|
-
const fnValue = fn.value(targetElement);
|
|
391
|
-
locators.forEach((loc) => {
|
|
392
|
-
createLocator(loc, {
|
|
393
|
-
name: fn.name,
|
|
394
|
-
value: fnValue,
|
|
395
|
-
isRecorded: loc.value === fnValue ? loc.isRecorded : 'Y',
|
|
396
|
-
isSelfHealed: loc.value === fnValue ? loc.isSelfHealed : 'Y',
|
|
397
|
-
});
|
|
398
|
-
});
|
|
399
|
-
});
|
|
400
|
-
for (const locator of locators) {
|
|
401
|
-
try {
|
|
402
|
-
for (const loc of locators) {
|
|
403
|
-
if (!loc.value)
|
|
404
|
-
continue;
|
|
405
|
-
for (const relation of relations) {
|
|
406
|
-
if (loc.value.includes(relation)) {
|
|
407
|
-
const relativeXpath = checkReferenceElementIsValid(loc.value, relation, tempDiv);
|
|
408
|
-
if (relativeXpath) {
|
|
409
|
-
createLocator(loc, {
|
|
410
|
-
name: 'xpath',
|
|
411
|
-
value: relativeXpath,
|
|
412
|
-
isRecorded: locator.isRecorded !== '' && locator.isRecorded !== null ? locator.isRecorded : 'Y',
|
|
413
|
-
});
|
|
414
|
-
break;
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
catch (error) {
|
|
421
|
-
console.error('Error processing locator:', locator, error);
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
const finalAutoHealedLocators = finalLocators.map((obj) => ({
|
|
425
|
-
...obj,
|
|
426
|
-
value: cleanLocatorValue(obj.value, obj.name, obj.isRecorded),
|
|
427
|
-
}));
|
|
428
|
-
const finalUniqueAutoHealedLocators = finalAutoHealedLocators.reduce((unique, locator) => {
|
|
429
|
-
if (locator.value && !unique.some((l) => l.value === locator.value)) {
|
|
430
|
-
unique.push(locator);
|
|
431
|
-
}
|
|
432
|
-
return unique;
|
|
433
|
-
}, []);
|
|
434
|
-
const jsonResult = [
|
|
435
|
-
{
|
|
436
|
-
name: `${name}`,
|
|
437
|
-
desc: `${desc}`,
|
|
438
|
-
type: `${type}`,
|
|
439
|
-
locators: finalUniqueAutoHealedLocators.filter((locator) => (locator === null || locator === void 0 ? void 0 : locator.value) != null && locator.value !== ''),
|
|
440
|
-
isShared: `${isShared}`,
|
|
441
|
-
projectId: `${projectId}`,
|
|
442
|
-
projectType: `${projectType}`,
|
|
443
|
-
isRecorded: `${isRecorded}`,
|
|
444
|
-
folder: `${folder}`,
|
|
445
|
-
parentId: `${parentId}`,
|
|
446
|
-
parentName: `${parentName}`,
|
|
447
|
-
platform: `${platform}`,
|
|
448
|
-
licenseId: `${licenseId}`,
|
|
449
|
-
licenseType: `${licenseType}`,
|
|
450
|
-
userId: `${userId}`,
|
|
451
|
-
},
|
|
452
|
-
];
|
|
453
|
-
return jsonResult;
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
|
-
catch (error) {
|
|
457
|
-
console.error('Error processing locator:', locator, error);
|
|
458
|
-
continue;
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
catch (error) {
|
|
463
|
-
console.error('Error processing locator:', locator, error);
|
|
464
|
-
continue;
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
return null;
|
|
468
|
-
};
|
|
469
|
-
exports.getElementsFromHTML = getElementsFromHTML;
|
package/src/index.ts
DELETED