@dev-blinq/cucumber_client 1.0.1275-dev → 1.0.1276-dev
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1,6 +1,42 @@
|
|
|
1
1
|
import DOM_Parent from "./dom_parent";
|
|
2
2
|
import { __PW } from "./pw"
|
|
3
|
+
const candidateAttributes = [
|
|
4
|
+
"slot",
|
|
5
|
+
"alt",
|
|
6
|
+
"name",
|
|
7
|
+
"placeholder",
|
|
8
|
+
"title",
|
|
9
|
+
"aria-label",
|
|
10
|
+
"aria-placeholder",
|
|
11
|
+
"role",
|
|
12
|
+
"data-testid",
|
|
13
|
+
"data-cy",
|
|
14
|
+
];
|
|
15
|
+
function quoteCSSAttributeValue(text) {
|
|
16
|
+
return `"${text.replace(/["\\]/g, (char) => "\\" + char)}"`;
|
|
17
|
+
}
|
|
18
|
+
function makeSelectorForId(id) {
|
|
19
|
+
return /^[a-zA-Z][a-zA-Z0-9\-\_]+$/.test(id) ? "#" + id : `[id=${quoteCSSAttributeValue(id)}]`;
|
|
20
|
+
}
|
|
3
21
|
|
|
22
|
+
function escapeClassName(className) {
|
|
23
|
+
let result = "";
|
|
24
|
+
for (let i = 0; i < className.length; i++)
|
|
25
|
+
result += cssEscapeCharacter(className, i);
|
|
26
|
+
return result;
|
|
27
|
+
}
|
|
28
|
+
function cssEscapeCharacter(s, i) {
|
|
29
|
+
const c = s.charCodeAt(i);
|
|
30
|
+
if (c === 0)
|
|
31
|
+
return "\uFFFD";
|
|
32
|
+
if (c >= 1 && c <= 31 || c >= 48 && c <= 57 && (i === 0 || i === 1 && s.charCodeAt(0) === 45))
|
|
33
|
+
return "\\" + c.toString(16) + " ";
|
|
34
|
+
if (i === 0 && c === 45 && s.length === 1)
|
|
35
|
+
return "\\" + s.charAt(i);
|
|
36
|
+
if (c >= 128 || c === 45 || c === 95 || c >= 48 && c <= 57 || c >= 65 && c <= 90 || c >= 97 && c <= 122)
|
|
37
|
+
return s.charAt(i);
|
|
38
|
+
return "\\" + s.charAt(i);
|
|
39
|
+
}
|
|
4
40
|
class LocatorGenerator {
|
|
5
41
|
constructor(injectedScript, options = {}) {
|
|
6
42
|
this.locatorStrategies = {
|
|
@@ -18,6 +54,83 @@ class LocatorGenerator {
|
|
|
18
54
|
this.cache = new Map();
|
|
19
55
|
}
|
|
20
56
|
|
|
57
|
+
generateUniqueCSSSelector(element, options) {
|
|
58
|
+
const root = options?.root || window.document;
|
|
59
|
+
const separator = options?.separator || " > ";
|
|
60
|
+
const isUnique = options?.isunique || ((selector) => this.getMatchingElements(selector, options).length === 1);
|
|
61
|
+
const noCSSId = options?.noCSSId || false;
|
|
62
|
+
|
|
63
|
+
if (!(element instanceof Element)) return "";
|
|
64
|
+
|
|
65
|
+
if (!root.contains(element)) return "";
|
|
66
|
+
|
|
67
|
+
let selector = "";
|
|
68
|
+
const id = element.getAttribute("id");
|
|
69
|
+
if (id && !/\d/.test(id) && (!noCSSId)) {
|
|
70
|
+
selector = makeSelectorForId(id);
|
|
71
|
+
if (isUnique(selector)) return selector;
|
|
72
|
+
}
|
|
73
|
+
if (element.tagName) {
|
|
74
|
+
selector = element.tagName.toLowerCase() + selector;
|
|
75
|
+
if (isUnique(selector)) return selector;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const classList = Array.from(element.classList).filter((c) => !/\d/.test(c));
|
|
79
|
+
if (classList.length) {
|
|
80
|
+
selector += classList.map((c) => "." + escapeClassName(c)).join("");
|
|
81
|
+
if (isUnique(selector)) return selector;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
for (let attr of candidateAttributes) {
|
|
85
|
+
if (element.hasAttribute(attr)) {
|
|
86
|
+
let value = element.getAttribute(attr);
|
|
87
|
+
if (value === "") continue;
|
|
88
|
+
if (/\d/.test(value)) continue;
|
|
89
|
+
// Escape special characters in attribute value
|
|
90
|
+
value = value.replace(/[!"#$%&'()*+,./:;<=>?@[\]^`{|}~]/g, "\\$&");
|
|
91
|
+
selector += `[${attr}="${this.PW.selectorUtils.escapeForAttributeSelector(value)}"]`;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
if (isUnique(selector)) return selector;
|
|
95
|
+
|
|
96
|
+
if (element === root) return selector;
|
|
97
|
+
|
|
98
|
+
let parentElement = element.parentElement;
|
|
99
|
+
if (!parentElement) {
|
|
100
|
+
// if element is shadowRoot
|
|
101
|
+
if (element.parentNode instanceof ShadowRoot) {
|
|
102
|
+
const parentElement = element.parentNode.host;
|
|
103
|
+
if (parentElement && parentElement !== root) {
|
|
104
|
+
const parentSelector = this.generateUniqueCSSSelector(parentElement, options);
|
|
105
|
+
selector = parentSelector + " >>> " + selector;
|
|
106
|
+
if (isUnique(selector)) return selector;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
} else if (parentElement !== root) {
|
|
110
|
+
// if is a slotted element
|
|
111
|
+
if (element.assignedSlot) {
|
|
112
|
+
parentElement = element.assignedSlot.parentElement ?? element.assignedSlot.parentNode.host;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (parentElement && parentElement !== root) {
|
|
116
|
+
const parentSelector = this.generateUniqueCSSSelector(parentElement, options);
|
|
117
|
+
|
|
118
|
+
selector = parentSelector + separator + selector;
|
|
119
|
+
if (isUnique(selector)) return selector;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const siblings = element.parentElement?.children;
|
|
124
|
+
if (siblings) {
|
|
125
|
+
for (let i = 0; i < siblings.length; i++) {
|
|
126
|
+
if (siblings[i] === element) {
|
|
127
|
+
return selector + `:nth-child(${i + 1})`;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return ""
|
|
132
|
+
}
|
|
133
|
+
|
|
21
134
|
getMatchingElements(selector, options = {}) {
|
|
22
135
|
const { root = window.document, prefix, visible = true } = options;
|
|
23
136
|
if (visible) {
|
|
@@ -99,7 +212,7 @@ class LocatorGenerator {
|
|
|
99
212
|
}
|
|
100
213
|
return result;
|
|
101
214
|
}
|
|
102
|
-
getContextLocators(locators) {
|
|
215
|
+
getContextLocators(element, locators) {
|
|
103
216
|
|
|
104
217
|
if (!locators || !Array.isArray(locators)) {
|
|
105
218
|
console.error("Locators must be an array");
|
|
@@ -163,12 +276,28 @@ class LocatorGenerator {
|
|
|
163
276
|
const textLocator = `internal:text=${textBody}`;
|
|
164
277
|
const elements = this.getMatchingElements(textLocator, {});
|
|
165
278
|
if (elements.length !== 1) {
|
|
166
|
-
throw new Error("Context locator must have exactly one element matching the text part");
|
|
279
|
+
// throw new Error("Context locator must have exactly one element matching the text part");
|
|
280
|
+
console.error("Context locator must have exactly one element matching the text part");
|
|
281
|
+
continue;
|
|
167
282
|
|
|
168
283
|
}
|
|
169
284
|
const textElement = elements[0];
|
|
170
285
|
// const text = this.PW.selectorUtils.elementText(textElement);
|
|
171
286
|
const text = this.injectedScript.utils.elementText(new Map(), textElement).full;
|
|
287
|
+
|
|
288
|
+
const fullSelector = `${textLocator} >> xpath=${xpath} >> ${restOfSelector}`;
|
|
289
|
+
const fullElements = this.getMatchingElements(fullSelector, {});
|
|
290
|
+
if (fullElements.length !== 1) {
|
|
291
|
+
// throw new Error("Context locator must have exactly one element matching the full selector");
|
|
292
|
+
console.error("Context locator must have exactly one element matching the full selector");
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
295
|
+
const fullElement = fullElements[0];
|
|
296
|
+
if (fullElement !== element) {
|
|
297
|
+
// throw new Error("Context locator must have the text element as the full element");
|
|
298
|
+
console.error("Context locator must have the text element as the full element");
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
172
301
|
if (!textSet.has(text)) {
|
|
173
302
|
textSet.add(text);
|
|
174
303
|
const loc = {
|
|
@@ -182,13 +311,10 @@ class LocatorGenerator {
|
|
|
182
311
|
}
|
|
183
312
|
result.push(loc);
|
|
184
313
|
}
|
|
185
|
-
|
|
186
|
-
|
|
187
314
|
}
|
|
188
315
|
} catch (error) {
|
|
189
316
|
console.error("Error parsing climb string:", error);
|
|
190
317
|
continue;
|
|
191
|
-
|
|
192
318
|
}
|
|
193
319
|
|
|
194
320
|
}
|
|
@@ -196,7 +322,7 @@ class LocatorGenerator {
|
|
|
196
322
|
result.sort((a, b) => a.text.length - b.text.length);
|
|
197
323
|
return result;
|
|
198
324
|
}
|
|
199
|
-
getDigitIgnoreLocators(locators) {
|
|
325
|
+
getDigitIgnoreLocators(element, locators) {
|
|
200
326
|
const result = [];
|
|
201
327
|
if (!locators || !Array.isArray(locators)) {
|
|
202
328
|
console.error("Locators must be an array");
|
|
@@ -232,9 +358,9 @@ class LocatorGenerator {
|
|
|
232
358
|
const digitsRegex = /\d+/g;
|
|
233
359
|
hasDigitsInText = digitsRegex.test(text);
|
|
234
360
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
finalSelector += `internal:text
|
|
361
|
+
let pattern = this.PW.stringUtils.escapeRegExp(text.substring(1, text.length - 2));
|
|
362
|
+
pattern = pattern.replace(digitsRegex, "\\d+");
|
|
363
|
+
finalSelector += `internal:text=/${pattern}/ >> `;
|
|
238
364
|
|
|
239
365
|
}
|
|
240
366
|
if (!hasDigitsInText) {
|
|
@@ -244,6 +370,15 @@ class LocatorGenerator {
|
|
|
244
370
|
finalSelector = finalSelector.slice(0, -4);
|
|
245
371
|
}
|
|
246
372
|
if (finalSelector) {
|
|
373
|
+
const elements = this.getMatchingElements(finalSelector, {});
|
|
374
|
+
if (elements.length !== 1) {
|
|
375
|
+
console.error("Digit ignore locator must have exactly one element matching the final selector");
|
|
376
|
+
continue;
|
|
377
|
+
}
|
|
378
|
+
if (elements[0] !== element) {
|
|
379
|
+
console.error("Digit ignore locator must match the original element");
|
|
380
|
+
continue;
|
|
381
|
+
}
|
|
247
382
|
result.push({
|
|
248
383
|
css: finalSelector,
|
|
249
384
|
priority: locator.priority || 1,
|
|
@@ -319,14 +454,6 @@ class LocatorGenerator {
|
|
|
319
454
|
return { unique, nonUnique };
|
|
320
455
|
}
|
|
321
456
|
|
|
322
|
-
getCategorizedLocators(element, options = {}) {
|
|
323
|
-
if (this.cache.has(element)) {
|
|
324
|
-
return this.cache.get(element);
|
|
325
|
-
}
|
|
326
|
-
const locators = this.getElementLocators(element, options);
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
|
|
330
457
|
getUniqueLocators(element, locatorGenerator = this.getNoTextLocators, options = {}) {
|
|
331
458
|
return this.getUniqueLocators2(element, locatorGenerator, options);
|
|
332
459
|
}
|
|
@@ -639,19 +766,30 @@ class LocatorGenerator {
|
|
|
639
766
|
if (textWithIndexLocators.length > 0) {
|
|
640
767
|
allStrategyLocators[this.locatorStrategies.text_with_index] = textWithIndexLocators;
|
|
641
768
|
}
|
|
642
|
-
const digitIgnoreLocators = this.getDigitIgnoreLocators(basicLocators);
|
|
769
|
+
const digitIgnoreLocators = this.getDigitIgnoreLocators(element, basicLocators);
|
|
643
770
|
if (digitIgnoreLocators.length > 0) {
|
|
644
771
|
allStrategyLocators[this.locatorStrategies.digitIgnore] = digitIgnoreLocators;
|
|
645
772
|
}
|
|
646
|
-
const contextLocators = this.getContextLocators(basicLocators);
|
|
773
|
+
const contextLocators = this.getContextLocators(element, basicLocators);
|
|
647
774
|
if (contextLocators.length > 0) {
|
|
648
775
|
allStrategyLocators[this.locatorStrategies.context] = contextLocators;
|
|
649
776
|
}
|
|
650
777
|
}
|
|
651
778
|
console.groupCollapsed("Generating No Text locators for element:", element);
|
|
652
779
|
const noTextLocators = this.getUniqueLocators(element, this.getNoTextLocators.bind(this), options);
|
|
780
|
+
|
|
653
781
|
if (noTextLocators.length > 0) {
|
|
654
782
|
allStrategyLocators[this.locatorStrategies.no_text] = noTextLocators;
|
|
783
|
+
} else {
|
|
784
|
+
const _locators = []
|
|
785
|
+
_locators.push({
|
|
786
|
+
css: this.generateUniqueCSSSelector(element, options),
|
|
787
|
+
score: 500,
|
|
788
|
+
priority: 3,
|
|
789
|
+
})
|
|
790
|
+
if (_locators.length > 0) {
|
|
791
|
+
allStrategyLocators[this.locatorStrategies.no_text] = _locators;
|
|
792
|
+
}
|
|
655
793
|
}
|
|
656
794
|
console.groupEnd();
|
|
657
795
|
|
|
@@ -662,7 +800,7 @@ class LocatorGenerator {
|
|
|
662
800
|
}
|
|
663
801
|
allStrategyLocators.strategy = bestStrategy;
|
|
664
802
|
|
|
665
|
-
const locators = allStrategyLocators[allStrategyLocators.strategy];
|
|
803
|
+
const locators = allStrategyLocators[allStrategyLocators.strategy] ?? [];
|
|
666
804
|
const result = {
|
|
667
805
|
allStrategyLocators,
|
|
668
806
|
locators,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dev-blinq/cucumber_client",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1276-dev",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "bin/index.js",
|
|
6
6
|
"types": "bin/index.d.ts",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"@cucumber/tag-expressions": "^6.1.1",
|
|
32
32
|
"@dev-blinq/cucumber-js": "1.0.175-dev",
|
|
33
33
|
"@faker-js/faker": "^8.1.0",
|
|
34
|
-
"automation_model": "1.0.
|
|
34
|
+
"automation_model": "1.0.756-dev",
|
|
35
35
|
"axios": "^1.7.4",
|
|
36
36
|
"chokidar": "^3.6.0",
|
|
37
37
|
"create-require": "^1.1.1",
|