@dev-blinq/cucumber_client 1.0.1275-dev → 1.0.1277-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
- const regex = text.replace(/\d+/g, "\\d+");
236
-
237
- finalSelector += `internal:text=${regex} >> `;
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.1275-dev",
3
+ "version": "1.0.1277-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.755-dev",
34
+ "automation_model": "1.0.757-dev",
35
35
  "axios": "^1.7.4",
36
36
  "chokidar": "^3.6.0",
37
37
  "create-require": "^1.1.1",