@dev-blinq/cucumber_client 1.0.1265-dev → 1.0.1267-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.
- package/bin/assets/bundled_scripts/recorder.js +164 -8
- package/bin/assets/scripts/dom_parent.js +22 -0
- package/bin/assets/scripts/unique_locators.js +156 -10
- package/bin/assets/templates/utils_template.txt +43 -25
- package/bin/client/code_gen/code_inversion.js +8 -0
- package/bin/client/recorderv3/bvt_recorder.js +132 -22
- package/bin/client/recording.js +2 -0
- package/package.json +1 -1
|
@@ -316,6 +316,27 @@ var DOM_Parent = class {
|
|
|
316
316
|
}
|
|
317
317
|
return ancestors;
|
|
318
318
|
}
|
|
319
|
+
getFullAncestorChainToRoot(element, root) {
|
|
320
|
+
if (!element || !(element instanceof Element)) {
|
|
321
|
+
throw new Error("Invalid element provided");
|
|
322
|
+
}
|
|
323
|
+
if (!root || !(root instanceof Element)) {
|
|
324
|
+
throw new Error("Invalid root provided");
|
|
325
|
+
}
|
|
326
|
+
if (!this.containsElementCrossShadow(root, element)) {
|
|
327
|
+
throw new Error("Root does not contain the element");
|
|
328
|
+
}
|
|
329
|
+
const ancestors = [];
|
|
330
|
+
let currentElement = element;
|
|
331
|
+
while (currentElement && currentElement !== root && this.containsElementCrossShadow(root, currentElement)) {
|
|
332
|
+
ancestors.push(currentElement);
|
|
333
|
+
currentElement = this.getActualParent(currentElement);
|
|
334
|
+
}
|
|
335
|
+
if (currentElement === root) {
|
|
336
|
+
ancestors.push(currentElement);
|
|
337
|
+
}
|
|
338
|
+
return ancestors;
|
|
339
|
+
}
|
|
319
340
|
getClimbCountToParent(element, targetParent) {
|
|
320
341
|
if (!element || !(element instanceof Element)) {
|
|
321
342
|
throw new Error("Invalid element provided");
|
|
@@ -8230,6 +8251,7 @@ var LocatorGenerator = class {
|
|
|
8230
8251
|
this.dom_Parent = new dom_parent_default();
|
|
8231
8252
|
this.PW = __PW;
|
|
8232
8253
|
this.injectedScript = injectedScript;
|
|
8254
|
+
this.cache = /* @__PURE__ */ new Map();
|
|
8233
8255
|
}
|
|
8234
8256
|
getMatchingElements(selector, options = {}) {
|
|
8235
8257
|
const { root = window.document, prefix, visible = true } = options;
|
|
@@ -8375,12 +8397,16 @@ var LocatorGenerator = class {
|
|
|
8375
8397
|
const text = this.injectedScript.utils.elementText(/* @__PURE__ */ new Map(), textElement).full;
|
|
8376
8398
|
if (!textSet.has(text)) {
|
|
8377
8399
|
textSet.add(text);
|
|
8378
|
-
|
|
8400
|
+
const loc = {
|
|
8379
8401
|
css: restOfSelector,
|
|
8380
8402
|
climb: climbCount,
|
|
8381
8403
|
text,
|
|
8382
8404
|
priority: 1
|
|
8383
|
-
}
|
|
8405
|
+
};
|
|
8406
|
+
if (locator.index !== void 0) {
|
|
8407
|
+
loc.index = locator.index;
|
|
8408
|
+
}
|
|
8409
|
+
result.push(loc);
|
|
8384
8410
|
}
|
|
8385
8411
|
}
|
|
8386
8412
|
} catch (error) {
|
|
@@ -8481,7 +8507,10 @@ var LocatorGenerator = class {
|
|
|
8481
8507
|
const nonUnique = [];
|
|
8482
8508
|
for (const locator of locators) {
|
|
8483
8509
|
const elements = this.getMatchingElements(locator.css, options);
|
|
8484
|
-
if (elements.length ===
|
|
8510
|
+
if (elements.length === 0) {
|
|
8511
|
+
console.warn(`No elements found for locator: ${locator.css}`);
|
|
8512
|
+
continue;
|
|
8513
|
+
} else if (elements.length === 1) {
|
|
8485
8514
|
if (element === elements[0]) {
|
|
8486
8515
|
locator.priority = 1;
|
|
8487
8516
|
unique.push(locator);
|
|
@@ -8501,9 +8530,18 @@ var LocatorGenerator = class {
|
|
|
8501
8530
|
}
|
|
8502
8531
|
return { unique, nonUnique };
|
|
8503
8532
|
}
|
|
8533
|
+
getCategorizedLocators(element, options = {}) {
|
|
8534
|
+
if (this.cache.has(element)) {
|
|
8535
|
+
return this.cache.get(element);
|
|
8536
|
+
}
|
|
8537
|
+
const locators = this.getElementLocators(element, options);
|
|
8538
|
+
}
|
|
8504
8539
|
getUniqueLocators(element, locatorGenerator = this.getNoTextLocators, options = {}) {
|
|
8540
|
+
return this.getUniqueLocators2(element, locatorGenerator, options);
|
|
8541
|
+
}
|
|
8542
|
+
getUniqueLocators1(element, locatorGenerator = this.getNoTextLocators, options = {}) {
|
|
8505
8543
|
try {
|
|
8506
|
-
const { maxLocators = 5, root = window.document.body, next = "LCA" } = options;
|
|
8544
|
+
const { maxLocators = 5, root = window.document.body, next = "LCA", minLocators = 3 } = options;
|
|
8507
8545
|
if (!element) {
|
|
8508
8546
|
return [];
|
|
8509
8547
|
}
|
|
@@ -8584,7 +8622,7 @@ var LocatorGenerator = class {
|
|
|
8584
8622
|
result.push(_locator);
|
|
8585
8623
|
} else {
|
|
8586
8624
|
const index = _elements.indexOf(element);
|
|
8587
|
-
if (index !== -1) {
|
|
8625
|
+
if (index !== -1 && index < 5) {
|
|
8588
8626
|
_locator.css = fullSelector;
|
|
8589
8627
|
_locator.index = index;
|
|
8590
8628
|
_locator.priority = 2;
|
|
@@ -8595,7 +8633,7 @@ var LocatorGenerator = class {
|
|
|
8595
8633
|
}
|
|
8596
8634
|
} else {
|
|
8597
8635
|
const index = elements.indexOf(element);
|
|
8598
|
-
if (index !== -1) {
|
|
8636
|
+
if (index !== -1 && index < 5) {
|
|
8599
8637
|
locator.index = index;
|
|
8600
8638
|
locator.priority = 2;
|
|
8601
8639
|
result.push(locator);
|
|
@@ -8603,7 +8641,7 @@ var LocatorGenerator = class {
|
|
|
8603
8641
|
}
|
|
8604
8642
|
} else {
|
|
8605
8643
|
const index = elements.indexOf(element);
|
|
8606
|
-
if (index !== -1) {
|
|
8644
|
+
if (index !== -1 && index < 5) {
|
|
8607
8645
|
locator.index = index;
|
|
8608
8646
|
locator.priority = 2;
|
|
8609
8647
|
result.push(locator);
|
|
@@ -8614,7 +8652,7 @@ var LocatorGenerator = class {
|
|
|
8614
8652
|
delete locator.engine;
|
|
8615
8653
|
delete locator.selector;
|
|
8616
8654
|
}
|
|
8617
|
-
if (result.length
|
|
8655
|
+
if (result.length < minLocators && root && root.contains(element)) {
|
|
8618
8656
|
const parent = this.dom_Parent.getActualParent(element);
|
|
8619
8657
|
const locs = this.getUniqueLocators(parent, locatorGenerator, {
|
|
8620
8658
|
...options,
|
|
@@ -8631,6 +8669,124 @@ var LocatorGenerator = class {
|
|
|
8631
8669
|
return [];
|
|
8632
8670
|
}
|
|
8633
8671
|
}
|
|
8672
|
+
getUniqueLocators2(element, locatorGenerator = this.getNoTextLocators, options = {}) {
|
|
8673
|
+
try {
|
|
8674
|
+
const { maxLocators = 5, root = window.document.body } = options;
|
|
8675
|
+
if (!element) {
|
|
8676
|
+
return [];
|
|
8677
|
+
}
|
|
8678
|
+
if (element === root) {
|
|
8679
|
+
if (element === window.document.documentElement) {
|
|
8680
|
+
return [
|
|
8681
|
+
{
|
|
8682
|
+
css: "html",
|
|
8683
|
+
score: 1,
|
|
8684
|
+
priority: 1
|
|
8685
|
+
},
|
|
8686
|
+
{
|
|
8687
|
+
css: ":root",
|
|
8688
|
+
score: 1,
|
|
8689
|
+
priority: 1
|
|
8690
|
+
}
|
|
8691
|
+
];
|
|
8692
|
+
} else {
|
|
8693
|
+
return [
|
|
8694
|
+
{
|
|
8695
|
+
css: ":root",
|
|
8696
|
+
score: 1,
|
|
8697
|
+
priority: 1
|
|
8698
|
+
// }, {
|
|
8699
|
+
// css: ":root",
|
|
8700
|
+
// score: 1,
|
|
8701
|
+
// priority: 1,
|
|
8702
|
+
}
|
|
8703
|
+
];
|
|
8704
|
+
}
|
|
8705
|
+
}
|
|
8706
|
+
console.log("Generating locators for element:", element);
|
|
8707
|
+
const locators = locatorGenerator(element, options);
|
|
8708
|
+
console.log("Generated locators:", locators);
|
|
8709
|
+
if (!locators || !Array.isArray(locators)) {
|
|
8710
|
+
console.error("Locator generator did not return an array of locators");
|
|
8711
|
+
return [];
|
|
8712
|
+
}
|
|
8713
|
+
console.log("Categorizing locators for element:", element);
|
|
8714
|
+
const categorizedLocators = this.categorizeLocators(element, locators, options);
|
|
8715
|
+
console.log("Categorized locators:", categorizedLocators);
|
|
8716
|
+
const { unique, nonUnique } = categorizedLocators;
|
|
8717
|
+
const result = [];
|
|
8718
|
+
if (unique.length > 0) {
|
|
8719
|
+
result.push(...unique);
|
|
8720
|
+
}
|
|
8721
|
+
if (result.length >= maxLocators) {
|
|
8722
|
+
return result.slice(0, maxLocators);
|
|
8723
|
+
}
|
|
8724
|
+
const elementsCache = /* @__PURE__ */ new Map();
|
|
8725
|
+
const allAncestors = this.dom_Parent.getFullAncestorChainToRoot(element, root);
|
|
8726
|
+
allAncestors.shift();
|
|
8727
|
+
const ancestorLocators = [];
|
|
8728
|
+
for (const ancestor of allAncestors) {
|
|
8729
|
+
const _locators = locatorGenerator(ancestor, options);
|
|
8730
|
+
if (!_locators || !Array.isArray(_locators) || _locators.length === 0) {
|
|
8731
|
+
continue;
|
|
8732
|
+
}
|
|
8733
|
+
const _categorized = this.categorizeLocators(ancestor, _locators, options);
|
|
8734
|
+
ancestorLocators.push({
|
|
8735
|
+
element: ancestor,
|
|
8736
|
+
locators: _categorized
|
|
8737
|
+
});
|
|
8738
|
+
elementsCache.set(ancestor, _categorized);
|
|
8739
|
+
if (_categorized.unique.length > 0) {
|
|
8740
|
+
break;
|
|
8741
|
+
}
|
|
8742
|
+
}
|
|
8743
|
+
const uniqueAncestor = ancestorLocators[ancestorLocators.length - 1];
|
|
8744
|
+
for (const locator of nonUnique) {
|
|
8745
|
+
const selector = locator.css ?? locator.selector;
|
|
8746
|
+
const elements = locator.elements || this.getMatchingElements(selector, options);
|
|
8747
|
+
if (elements.length === 0) {
|
|
8748
|
+
console.warn(`No elements found for locator: ${selector}`);
|
|
8749
|
+
continue;
|
|
8750
|
+
}
|
|
8751
|
+
for (const unique_locator of uniqueAncestor.locators.unique) {
|
|
8752
|
+
const fullSelector = `${unique_locator.css} >> ${selector}`;
|
|
8753
|
+
const elements2 = this.getMatchingElements(fullSelector, options);
|
|
8754
|
+
if (elements2.length === 1 && elements2[0] === element) {
|
|
8755
|
+
const effectiveScore = (unique_locator.score + locator.score) / 2 + 100;
|
|
8756
|
+
const newLocator = {
|
|
8757
|
+
...unique_locator,
|
|
8758
|
+
css: fullSelector,
|
|
8759
|
+
score: effectiveScore,
|
|
8760
|
+
priority: 1
|
|
8761
|
+
// unique locators have higher priority
|
|
8762
|
+
};
|
|
8763
|
+
result.push(newLocator);
|
|
8764
|
+
} else {
|
|
8765
|
+
const index = elements2.indexOf(element);
|
|
8766
|
+
if (index !== -1 && index < 5) {
|
|
8767
|
+
const effectiveScore = (unique_locator.score + locator.score) / 2;
|
|
8768
|
+
const newLocator = {
|
|
8769
|
+
...unique_locator,
|
|
8770
|
+
css: fullSelector,
|
|
8771
|
+
index,
|
|
8772
|
+
score: effectiveScore + 200,
|
|
8773
|
+
priority: 2
|
|
8774
|
+
// non-unique locators have lower priority
|
|
8775
|
+
};
|
|
8776
|
+
result.push(newLocator);
|
|
8777
|
+
}
|
|
8778
|
+
}
|
|
8779
|
+
}
|
|
8780
|
+
}
|
|
8781
|
+
result.sort((a, b) => a.score - b.score);
|
|
8782
|
+
console.log("Final locators:", result, element);
|
|
8783
|
+
console.groupEnd();
|
|
8784
|
+
return result.slice(0, maxLocators);
|
|
8785
|
+
} catch (error) {
|
|
8786
|
+
console.error("Error in getUniqueLocators:", error);
|
|
8787
|
+
return [];
|
|
8788
|
+
}
|
|
8789
|
+
}
|
|
8634
8790
|
getElementLocators(element, options = {}) {
|
|
8635
8791
|
var _a;
|
|
8636
8792
|
const { excludeText = false } = options;
|
|
@@ -35,6 +35,28 @@ class DOM_Parent {
|
|
|
35
35
|
|
|
36
36
|
return ancestors;
|
|
37
37
|
}
|
|
38
|
+
getFullAncestorChainToRoot(element, root) {
|
|
39
|
+
if (!element || !(element instanceof Element)) {
|
|
40
|
+
throw new Error('Invalid element provided');
|
|
41
|
+
}
|
|
42
|
+
if (!root || !(root instanceof Element)) {
|
|
43
|
+
throw new Error('Invalid root provided');
|
|
44
|
+
}
|
|
45
|
+
if (!this.containsElementCrossShadow(root, element)) {
|
|
46
|
+
throw new Error('Root does not contain the element');
|
|
47
|
+
}
|
|
48
|
+
const ancestors = [];
|
|
49
|
+
let currentElement = element;
|
|
50
|
+
while (currentElement && currentElement !== root && this.containsElementCrossShadow(root, currentElement)) {
|
|
51
|
+
ancestors.push(currentElement);
|
|
52
|
+
currentElement = this.getActualParent(currentElement);
|
|
53
|
+
|
|
54
|
+
}
|
|
55
|
+
if (currentElement === root) {
|
|
56
|
+
ancestors.push(currentElement);
|
|
57
|
+
}
|
|
58
|
+
return ancestors;
|
|
59
|
+
}
|
|
38
60
|
getClimbCountToParent(element, targetParent) {
|
|
39
61
|
if (!element || !(element instanceof Element)) {
|
|
40
62
|
throw new Error('Invalid element provided');
|
|
@@ -19,6 +19,7 @@ class LocatorGenerator {
|
|
|
19
19
|
this.dom_Parent = new DOM_Parent();
|
|
20
20
|
this.PW = __PW;
|
|
21
21
|
this.injectedScript = injectedScript;
|
|
22
|
+
this.cache = new Map();
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
getMatchingElements(selector, options = {}) {
|
|
@@ -176,13 +177,16 @@ class LocatorGenerator {
|
|
|
176
177
|
const text = this.injectedScript.utils.elementText(new Map(), textElement).full;
|
|
177
178
|
if (!textSet.has(text)) {
|
|
178
179
|
textSet.add(text);
|
|
179
|
-
|
|
180
|
+
const loc = {
|
|
180
181
|
css: restOfSelector,
|
|
181
182
|
climb: climbCount,
|
|
182
183
|
text,
|
|
183
184
|
priority: 1,
|
|
184
|
-
}
|
|
185
|
-
|
|
185
|
+
}
|
|
186
|
+
if (locator.index !== undefined) {
|
|
187
|
+
loc.index = locator.index;
|
|
188
|
+
}
|
|
189
|
+
result.push(loc);
|
|
186
190
|
}
|
|
187
191
|
|
|
188
192
|
|
|
@@ -297,7 +301,10 @@ class LocatorGenerator {
|
|
|
297
301
|
const nonUnique = [];
|
|
298
302
|
for (const locator of locators) {
|
|
299
303
|
const elements = this.getMatchingElements(locator.css, options);
|
|
300
|
-
if (elements.length ===
|
|
304
|
+
if (elements.length === 0) {
|
|
305
|
+
console.warn(`No elements found for locator: ${locator.css}`);
|
|
306
|
+
continue;
|
|
307
|
+
} else if (elements.length === 1) {
|
|
301
308
|
if (element === elements[0]) {
|
|
302
309
|
locator.priority = 1;
|
|
303
310
|
unique.push(locator);
|
|
@@ -318,13 +325,21 @@ class LocatorGenerator {
|
|
|
318
325
|
return { unique, nonUnique };
|
|
319
326
|
}
|
|
320
327
|
|
|
321
|
-
|
|
328
|
+
getCategorizedLocators(element, options = {}) {
|
|
329
|
+
if (this.cache.has(element)) {
|
|
330
|
+
return this.cache.get(element);
|
|
331
|
+
}
|
|
332
|
+
const locators = this.getElementLocators(element, options);
|
|
333
|
+
}
|
|
322
334
|
|
|
323
335
|
|
|
324
336
|
getUniqueLocators(element, locatorGenerator = this.getNoTextLocators, options = {}) {
|
|
337
|
+
return this.getUniqueLocators2(element, locatorGenerator, options);
|
|
338
|
+
}
|
|
339
|
+
getUniqueLocators1(element, locatorGenerator = this.getNoTextLocators, options = {}) {
|
|
325
340
|
try {
|
|
326
341
|
|
|
327
|
-
const { maxLocators = 5, root = window.document.body, next = "LCA" } = options;
|
|
342
|
+
const { maxLocators = 5, root = window.document.body, next = "LCA", minLocators = 3 } = options;
|
|
328
343
|
|
|
329
344
|
if (!element) {
|
|
330
345
|
return [];
|
|
@@ -414,7 +429,7 @@ class LocatorGenerator {
|
|
|
414
429
|
result.push(_locator);
|
|
415
430
|
} else {
|
|
416
431
|
const index = _elements.indexOf(element);
|
|
417
|
-
if (index !== -1) {
|
|
432
|
+
if (index !== -1 && index < 5) {
|
|
418
433
|
// _locator.selector = fullSelector;
|
|
419
434
|
_locator.css = fullSelector;
|
|
420
435
|
_locator.index = index;
|
|
@@ -428,7 +443,7 @@ class LocatorGenerator {
|
|
|
428
443
|
|
|
429
444
|
} else {
|
|
430
445
|
const index = elements.indexOf(element);
|
|
431
|
-
if (index !== -1) {
|
|
446
|
+
if (index !== -1 && index < 5) {
|
|
432
447
|
locator.index = index
|
|
433
448
|
locator.priority = 2; // non-unique locators have lower priority
|
|
434
449
|
result.push(locator);
|
|
@@ -437,7 +452,7 @@ class LocatorGenerator {
|
|
|
437
452
|
|
|
438
453
|
} else {
|
|
439
454
|
const index = elements.indexOf(element);
|
|
440
|
-
if (index !== -1) {
|
|
455
|
+
if (index !== -1 && index < 5) {
|
|
441
456
|
locator.index = index
|
|
442
457
|
locator.priority = 2; // non-unique locators have lower priority
|
|
443
458
|
result.push(locator);
|
|
@@ -450,7 +465,7 @@ class LocatorGenerator {
|
|
|
450
465
|
delete locator.selector;
|
|
451
466
|
}
|
|
452
467
|
|
|
453
|
-
if (result.length
|
|
468
|
+
if (result.length < minLocators && root && root.contains(element)) {
|
|
454
469
|
const parent = this.dom_Parent.getActualParent(element);
|
|
455
470
|
const locs = this.getUniqueLocators(parent, locatorGenerator, {
|
|
456
471
|
...options,
|
|
@@ -470,6 +485,137 @@ class LocatorGenerator {
|
|
|
470
485
|
return [];
|
|
471
486
|
}
|
|
472
487
|
}
|
|
488
|
+
|
|
489
|
+
getUniqueLocators2(element, locatorGenerator = this.getNoTextLocators, options = {}) {
|
|
490
|
+
try {
|
|
491
|
+
|
|
492
|
+
const { maxLocators = 5, root = window.document.body } = options;
|
|
493
|
+
|
|
494
|
+
if (!element) {
|
|
495
|
+
return [];
|
|
496
|
+
}
|
|
497
|
+
if (element === root) {
|
|
498
|
+
if (element === window.document.documentElement) {
|
|
499
|
+
return [{
|
|
500
|
+
css: "html",
|
|
501
|
+
score: 1,
|
|
502
|
+
priority: 1,
|
|
503
|
+
}, {
|
|
504
|
+
css: ":root",
|
|
505
|
+
score: 1,
|
|
506
|
+
priority: 1,
|
|
507
|
+
}
|
|
508
|
+
]
|
|
509
|
+
} else {
|
|
510
|
+
return [{
|
|
511
|
+
css: ":root",
|
|
512
|
+
score: 1,
|
|
513
|
+
priority: 1,
|
|
514
|
+
// }, {
|
|
515
|
+
// css: ":root",
|
|
516
|
+
// score: 1,
|
|
517
|
+
// priority: 1,
|
|
518
|
+
}
|
|
519
|
+
]
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
console.log("Generating locators for element:", element);
|
|
524
|
+
const locators = locatorGenerator(element, options);
|
|
525
|
+
console.log("Generated locators:", locators);
|
|
526
|
+
if (!locators || !Array.isArray(locators)) {
|
|
527
|
+
// throw new Error("Locator generator did not return an array of locators");
|
|
528
|
+
console.error("Locator generator did not return an array of locators");
|
|
529
|
+
return [];
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
console.log("Categorizing locators for element:", element);
|
|
533
|
+
const categorizedLocators = this.categorizeLocators(element, locators, options);
|
|
534
|
+
console.log("Categorized locators:", categorizedLocators);
|
|
535
|
+
// categorizedLocators.unique = limitLocators(categorizedLocators.unique, options);
|
|
536
|
+
// categorizedLocators.nonUnique = limitLocators(categorizedLocators.nonUnique, options);
|
|
537
|
+
|
|
538
|
+
const { unique, nonUnique } = categorizedLocators;
|
|
539
|
+
const result = [];
|
|
540
|
+
if (unique.length > 0) {
|
|
541
|
+
result.push(...unique);
|
|
542
|
+
}
|
|
543
|
+
if (result.length >= maxLocators) {
|
|
544
|
+
return result.slice(0, maxLocators);
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
const elementsCache = new Map();
|
|
548
|
+
|
|
549
|
+
const allAncestors = this.dom_Parent.getFullAncestorChainToRoot(element, root);
|
|
550
|
+
allAncestors.shift(); // remove the element itself from the ancestors
|
|
551
|
+
|
|
552
|
+
const ancestorLocators = [];
|
|
553
|
+
for (const ancestor of allAncestors) {
|
|
554
|
+
const _locators = locatorGenerator(ancestor, options);
|
|
555
|
+
if (!_locators || !Array.isArray(_locators) || _locators.length
|
|
556
|
+
=== 0) {
|
|
557
|
+
continue;
|
|
558
|
+
}
|
|
559
|
+
const _categorized = this.categorizeLocators(ancestor, _locators, options);
|
|
560
|
+
ancestorLocators.push({
|
|
561
|
+
element: ancestor,
|
|
562
|
+
locators: _categorized,
|
|
563
|
+
});
|
|
564
|
+
elementsCache.set(ancestor, _categorized);
|
|
565
|
+
if (_categorized.unique.length > 0) {
|
|
566
|
+
break;
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
const uniqueAncestor = ancestorLocators[ancestorLocators.length - 1];
|
|
571
|
+
|
|
572
|
+
for (const locator of nonUnique) {
|
|
573
|
+
const selector = locator.css ?? locator.selector;
|
|
574
|
+
const elements = locator.elements || this.getMatchingElements(selector, options);
|
|
575
|
+
if (elements.length === 0) {
|
|
576
|
+
console.warn(`No elements found for locator: ${selector}`);
|
|
577
|
+
continue;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
for (const unique_locator of uniqueAncestor.locators.unique) {
|
|
581
|
+
const fullSelector = `${unique_locator.css} >> ${selector}`;
|
|
582
|
+
const elements = this.getMatchingElements(fullSelector, options);
|
|
583
|
+
if (elements.length === 1 && elements[0] === element) {
|
|
584
|
+
const effectiveScore = (unique_locator.score + locator.score) / 2 + 100;
|
|
585
|
+
const newLocator = {
|
|
586
|
+
...unique_locator,
|
|
587
|
+
css: fullSelector,
|
|
588
|
+
score: effectiveScore,
|
|
589
|
+
priority: 1, // unique locators have higher priority
|
|
590
|
+
};
|
|
591
|
+
result.push(newLocator);
|
|
592
|
+
} else {
|
|
593
|
+
const index = elements.indexOf(element);
|
|
594
|
+
if (index !== -1 && index < 5) {
|
|
595
|
+
const effectiveScore = (unique_locator.score + locator.score) / 2;
|
|
596
|
+
const newLocator = {
|
|
597
|
+
...unique_locator,
|
|
598
|
+
css: fullSelector,
|
|
599
|
+
index,
|
|
600
|
+
score: effectiveScore + 200,
|
|
601
|
+
priority: 2, // non-unique locators have lower priority
|
|
602
|
+
};
|
|
603
|
+
result.push(newLocator);
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
}
|
|
609
|
+
result.sort((a, b) => a.score - b.score);
|
|
610
|
+
console.log("Final locators:", result, element);
|
|
611
|
+
console.groupEnd();
|
|
612
|
+
return result.slice(0, maxLocators);
|
|
613
|
+
}
|
|
614
|
+
catch (error) {
|
|
615
|
+
console.error("Error in getUniqueLocators:", error);
|
|
616
|
+
return [];
|
|
617
|
+
}
|
|
618
|
+
}
|
|
473
619
|
getElementLocators(element, options = {}) {
|
|
474
620
|
|
|
475
621
|
const { excludeText = false } = options;
|
|
@@ -60,7 +60,6 @@ async function loadUserData(user) {
|
|
|
60
60
|
async function verifyTextExistsInPage(text) {
|
|
61
61
|
await context.web.verifyTextExistInPage(text, null, this);
|
|
62
62
|
}
|
|
63
|
-
|
|
64
63
|
Then("Verify the text {string} can be found in the page", verifyTextExistsInPage);
|
|
65
64
|
|
|
66
65
|
/**
|
|
@@ -87,6 +86,7 @@ async function fillElement(elementDescription, value) {
|
|
|
87
86
|
}
|
|
88
87
|
When("fill {string} with {string}", fillElement);
|
|
89
88
|
When("Fill {string} with {string}", fillElement);
|
|
89
|
+
|
|
90
90
|
/**
|
|
91
91
|
* Verify text does not exist in page
|
|
92
92
|
* @param {string} text the text to verify does not exist in page
|
|
@@ -107,6 +107,24 @@ async function navigateTo(url) {
|
|
|
107
107
|
}
|
|
108
108
|
When("Navigate to {string}", navigateTo);
|
|
109
109
|
|
|
110
|
+
/**
|
|
111
|
+
* Navigate to the current page
|
|
112
|
+
* @protect
|
|
113
|
+
*/
|
|
114
|
+
async function browserNavigateBack() {
|
|
115
|
+
await context.web.goBack({}, this);
|
|
116
|
+
}
|
|
117
|
+
Then("Browser navigate back", browserNavigateBack);
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Navigate forward in browser history
|
|
121
|
+
* @protect
|
|
122
|
+
*/
|
|
123
|
+
async function browserNavigateForward() {
|
|
124
|
+
await context.web.goForward({}, this);
|
|
125
|
+
}
|
|
126
|
+
Then("Browser navigate forward", browserNavigateForward);
|
|
127
|
+
|
|
110
128
|
/**
|
|
111
129
|
* Store browser session "<path>"
|
|
112
130
|
* @param {string} filePath the file path or empty to store in the test data file
|
|
@@ -141,6 +159,7 @@ Then(
|
|
|
141
159
|
"Identify the text {string}, climb {string} levels in the page, validate text {string} can be found in the context",
|
|
142
160
|
verifyTextRelatedToText
|
|
143
161
|
);
|
|
162
|
+
|
|
144
163
|
/**
|
|
145
164
|
* execute bruno single request given the bruno project is placed in a folder called bruno under the root of the cucumber project
|
|
146
165
|
* @requestName the name of the bruno request file
|
|
@@ -149,7 +168,6 @@ Then(
|
|
|
149
168
|
async function runBrunoRequest(requestName) {
|
|
150
169
|
await executeBrunoRequest(requestName, {}, context, this);
|
|
151
170
|
}
|
|
152
|
-
|
|
153
171
|
When("Bruno - {string}", runBrunoRequest);
|
|
154
172
|
When("bruno - {string}", runBrunoRequest);
|
|
155
173
|
|
|
@@ -163,41 +181,41 @@ async function verify_the_downloaded_file_exists(fileName) {
|
|
|
163
181
|
const downloadFile = path.join(downloadFolder, fileName);
|
|
164
182
|
await verifyFileExists(downloadFile, {}, context, this);
|
|
165
183
|
}
|
|
166
|
-
|
|
167
184
|
Then("Verify the file {string} exists", { timeout: 60000 }, verify_the_downloaded_file_exists);
|
|
168
185
|
|
|
169
|
-
When("Noop", async function(){})
|
|
170
|
-
|
|
171
186
|
/**
|
|
172
|
-
*
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
*/
|
|
187
|
+
* Noop step for running only hooks
|
|
188
|
+
*/
|
|
189
|
+
When("Noop", async function () {});
|
|
176
190
|
|
|
177
|
-
|
|
178
|
-
|
|
191
|
+
/**
|
|
192
|
+
* Verify the page url is "<url>"
|
|
193
|
+
* @param {string} url URL to be verified against current URL
|
|
194
|
+
* @protect
|
|
195
|
+
*/
|
|
196
|
+
async function verify_page_url(url) {
|
|
197
|
+
await context.web.verifyPagePath(url, {}, this);
|
|
179
198
|
}
|
|
180
199
|
Then("Verify the page url is {string}", verify_page_url);
|
|
181
200
|
|
|
182
201
|
/**
|
|
183
|
-
* Verify the page title is "<title>"
|
|
184
|
-
* @param {string} title Title to be verified against current Title
|
|
185
|
-
* @protect
|
|
186
|
-
*/
|
|
187
|
-
async function verify_page_title(title){
|
|
202
|
+
* Verify the page title is "<title>"
|
|
203
|
+
* @param {string} title Title to be verified against current Title
|
|
204
|
+
* @protect
|
|
205
|
+
*/
|
|
206
|
+
async function verify_page_title(title) {
|
|
188
207
|
await context.web.verifyPageTitle(title, {}, this);
|
|
189
208
|
}
|
|
190
|
-
Then("Verify the page title is {string}",verify_page_title)
|
|
209
|
+
Then("Verify the page title is {string}", verify_page_title);
|
|
191
210
|
|
|
192
211
|
/**
|
|
193
|
-
* Explicit wait/sleep function that pauses execution for a specified duration
|
|
194
|
-
* @param {duration} - Duration to sleep in milliseconds (default: 1000ms)
|
|
195
|
-
* @param {options} - Optional configuration object
|
|
196
|
-
* @param {world} - Optional world context
|
|
197
|
-
* @returns Promise that resolves after the specified duration
|
|
198
|
-
*/
|
|
212
|
+
* Explicit wait/sleep function that pauses execution for a specified duration
|
|
213
|
+
* @param {duration} - Duration to sleep in milliseconds (default: 1000ms)
|
|
214
|
+
* @param {options} - Optional configuration object
|
|
215
|
+
* @param {world} - Optional world context
|
|
216
|
+
* @returns Promise that resolves after the specified duration
|
|
217
|
+
*/
|
|
199
218
|
async function sleep(duration) {
|
|
200
219
|
await context.web.sleep(duration, {}, null);
|
|
201
220
|
}
|
|
202
|
-
|
|
203
|
-
Then("Sleep for {string} ms", {timeout: -1}, sleep);
|
|
221
|
+
Then("Sleep for {string} ms", { timeout: -1 }, sleep);
|
|
@@ -310,6 +310,14 @@ const invertStableCommand = (call, elements, stepParams) => {
|
|
|
310
310
|
break;
|
|
311
311
|
}
|
|
312
312
|
|
|
313
|
+
case "goBack":
|
|
314
|
+
step.type = Types.GO_BACK;
|
|
315
|
+
break;
|
|
316
|
+
|
|
317
|
+
case "goForward":
|
|
318
|
+
step.type = Types.GO_FORWARD;
|
|
319
|
+
break;
|
|
320
|
+
|
|
313
321
|
case "reloadPage":
|
|
314
322
|
step.type = Types.RELOAD;
|
|
315
323
|
break;
|
|
@@ -24,11 +24,11 @@ export function getInitScript(config, options) {
|
|
|
24
24
|
window.__bvt_Recorder_config = ${JSON.stringify(config ?? null)};
|
|
25
25
|
window.__PW_options = ${JSON.stringify(options ?? null)};
|
|
26
26
|
`;
|
|
27
|
-
const recorderScript = readFileSync(
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
recorderScript
|
|
27
|
+
const recorderScript = readFileSync(
|
|
28
|
+
path.join(__dirname, "..", "..", "assets", "bundled_scripts", "recorder.js"),
|
|
29
|
+
"utf8"
|
|
31
30
|
);
|
|
31
|
+
return preScript + recorderScript;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
async function evaluate(frame, script) {
|
|
@@ -190,7 +190,7 @@ export class BVTRecorder {
|
|
|
190
190
|
|
|
191
191
|
this.lastKnownUrlPath = "";
|
|
192
192
|
// TODO: what is world?
|
|
193
|
-
this.world = { attach: () => {
|
|
193
|
+
this.world = { attach: () => {} };
|
|
194
194
|
this.shouldTakeScreenshot = true;
|
|
195
195
|
this.watcher = null;
|
|
196
196
|
}
|
|
@@ -294,7 +294,7 @@ export class BVTRecorder {
|
|
|
294
294
|
process.env.CDP_LISTEN_PORT = this.#remoteDebuggerPort;
|
|
295
295
|
|
|
296
296
|
this.stepRunner.setRemoteDebugPort(this.#remoteDebuggerPort);
|
|
297
|
-
this.world = { attach: () => {
|
|
297
|
+
this.world = { attach: () => {} };
|
|
298
298
|
|
|
299
299
|
const ai_config_file = path.join(this.projectDir, "ai_config.json");
|
|
300
300
|
let ai_config = {};
|
|
@@ -433,6 +433,75 @@ export class BVTRecorder {
|
|
|
433
433
|
}
|
|
434
434
|
});
|
|
435
435
|
}
|
|
436
|
+
|
|
437
|
+
hasHistoryReplacementAtIndex(previousEntries, currentEntries, index) {
|
|
438
|
+
if (!previousEntries || !currentEntries) return false;
|
|
439
|
+
if (index >= previousEntries.length || index >= currentEntries.length) return false;
|
|
440
|
+
|
|
441
|
+
const prevEntry = previousEntries[index];
|
|
442
|
+
// console.log("prevEntry", prevEntry);
|
|
443
|
+
const currEntry = currentEntries[index];
|
|
444
|
+
// console.log("currEntry", currEntry);
|
|
445
|
+
|
|
446
|
+
// Check if the entry at this index has been replaced
|
|
447
|
+
return prevEntry.id !== currEntry.id;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// Even simpler approach for your specific case
|
|
451
|
+
analyzeTransitionType(entries, currentIndex, currentEntry) {
|
|
452
|
+
// console.log("Analyzing transition type");
|
|
453
|
+
// console.log("===========================");
|
|
454
|
+
// console.log("Current Index:", currentIndex);
|
|
455
|
+
// console.log("Current Entry:", currentEntry);
|
|
456
|
+
// console.log("Current Entries:", entries);
|
|
457
|
+
// console.log("Current entries length:", entries.length);
|
|
458
|
+
// console.log("===========================");
|
|
459
|
+
// console.log("Previous Index:", this.previousIndex);
|
|
460
|
+
// // console.log("Previous Entry:", this.previousEntries[this.previousIndex]);
|
|
461
|
+
// console.log("Previous Entries:", this.previousEntries);
|
|
462
|
+
// console.log("Previous entries length:", this.previousHistoryLength);
|
|
463
|
+
|
|
464
|
+
if (this.previousIndex === null || this.previousHistoryLength === null || !this.previousEntries) {
|
|
465
|
+
return {
|
|
466
|
+
action: "initial",
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
const indexDiff = currentIndex - this.previousIndex;
|
|
471
|
+
const lengthDiff = entries.length - this.previousHistoryLength;
|
|
472
|
+
|
|
473
|
+
// Backward navigation
|
|
474
|
+
if (indexDiff < 0) {
|
|
475
|
+
return { action: "back" };
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// Forward navigation
|
|
479
|
+
if (indexDiff > 0 && lengthDiff === 0) {
|
|
480
|
+
// Check if the entry at current index is the same as before
|
|
481
|
+
const entryReplaced = this.hasHistoryReplacementAtIndex(this.previousEntries, entries, currentIndex);
|
|
482
|
+
|
|
483
|
+
if (entryReplaced) {
|
|
484
|
+
return { action: "navigate" }; // New navigation that replaced forward history
|
|
485
|
+
} else {
|
|
486
|
+
return { action: "forward" }; // True forward navigation
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// New navigation (history grew)
|
|
491
|
+
if (lengthDiff > 0) {
|
|
492
|
+
return { action: "navigate" };
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// Same position, same length
|
|
496
|
+
if (lengthDiff <= 0) {
|
|
497
|
+
const entryReplaced = this.hasHistoryReplacementAtIndex(this.previousEntries, entries, currentIndex);
|
|
498
|
+
|
|
499
|
+
return entryReplaced ? { action: "navigate" } : { action: "reload" };
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
return { action: "unknown" };
|
|
503
|
+
}
|
|
504
|
+
|
|
436
505
|
async getCurrentTransition() {
|
|
437
506
|
if (this?.web?.browser?._name !== "chromium") {
|
|
438
507
|
return;
|
|
@@ -441,35 +510,70 @@ export class BVTRecorder {
|
|
|
441
510
|
|
|
442
511
|
try {
|
|
443
512
|
const result = await client.send("Page.getNavigationHistory");
|
|
444
|
-
// console.log("
|
|
513
|
+
// console.log("Navigation History:", result);
|
|
445
514
|
const entries = result.entries;
|
|
446
515
|
const currentIndex = result.currentIndex;
|
|
447
516
|
|
|
448
517
|
// ignore if currentIndex is not the last entry
|
|
449
|
-
if (currentIndex !== entries.length - 1) return;
|
|
518
|
+
// if (currentIndex !== entries.length - 1) return;
|
|
450
519
|
|
|
451
520
|
const currentEntry = entries[currentIndex];
|
|
452
|
-
|
|
521
|
+
const transitionInfo = this.analyzeTransitionType(entries, currentIndex, currentEntry);
|
|
522
|
+
this.previousIndex = currentIndex;
|
|
523
|
+
this.previousHistoryLength = entries.length;
|
|
524
|
+
this.previousUrl = currentEntry.url;
|
|
525
|
+
this.previousEntries = [...entries]; // Store a copy of current entries
|
|
526
|
+
|
|
527
|
+
return {
|
|
528
|
+
currentEntry,
|
|
529
|
+
navigationAction: transitionInfo.action,
|
|
530
|
+
};
|
|
453
531
|
} catch (error) {
|
|
454
|
-
console.error("Error in getTransistionType event");
|
|
532
|
+
console.error("Error in getTransistionType event", error);
|
|
455
533
|
} finally {
|
|
456
534
|
await client.detach();
|
|
457
535
|
}
|
|
458
536
|
}
|
|
459
|
-
|
|
537
|
+
userInitiatedTransitionTypes = ["typed", "address_bar"];
|
|
460
538
|
async handlePageTransition() {
|
|
461
539
|
const transition = await this.getCurrentTransition();
|
|
462
540
|
if (!transition) return;
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
541
|
+
|
|
542
|
+
const { currentEntry, navigationAction } = transition;
|
|
543
|
+
|
|
544
|
+
switch (navigationAction) {
|
|
545
|
+
case "initial":
|
|
546
|
+
// console.log("Initial navigation, no action taken");
|
|
547
|
+
return;
|
|
548
|
+
case "navigate":
|
|
549
|
+
// console.log("transitionType", transition.transitionType);
|
|
550
|
+
// console.log("sending onGoto event", { url: currentEntry.url,
|
|
551
|
+
// type: "navigate", });
|
|
552
|
+
if (this.userInitiatedTransitionTypes.includes(currentEntry.transitionType)) {
|
|
553
|
+
const env = JSON.parse(readFileSync(this.envName), "utf8");
|
|
554
|
+
const baseUrl = env.baseUrl;
|
|
555
|
+
let url = currentEntry.userTypedURL;
|
|
556
|
+
if (baseUrl && url.startsWith(baseUrl)) {
|
|
557
|
+
url = url.replace(baseUrl, "{{env.baseUrl}}");
|
|
558
|
+
}
|
|
559
|
+
// console.log("User initiated transition");
|
|
560
|
+
this.sendEvent(this.events.onGoto, { url, type: "navigate" });
|
|
561
|
+
}
|
|
562
|
+
return;
|
|
563
|
+
case "back":
|
|
564
|
+
// console.log("User navigated back");
|
|
565
|
+
// console.log("sending onGoto event", {
|
|
566
|
+
// type: "back",
|
|
567
|
+
// });
|
|
568
|
+
this.sendEvent(this.events.onGoto, { type: "back" });
|
|
569
|
+
return;
|
|
570
|
+
case "forward":
|
|
571
|
+
// console.log("User navigated forward"); console.log("sending onGoto event", { type: "forward", });
|
|
572
|
+
this.sendEvent(this.events.onGoto, { type: "forward" });
|
|
573
|
+
return;
|
|
574
|
+
default:
|
|
575
|
+
this.sendEvent(this.events.onGoto, { type: "unknown" });
|
|
576
|
+
return;
|
|
473
577
|
}
|
|
474
578
|
}
|
|
475
579
|
|
|
@@ -596,8 +700,14 @@ export class BVTRecorder {
|
|
|
596
700
|
}
|
|
597
701
|
async closeBrowser() {
|
|
598
702
|
delete process.env.TEMP_RUN;
|
|
599
|
-
await this.watcher.close().then(() => {
|
|
703
|
+
await this.watcher.close().then(() => {});
|
|
704
|
+
|
|
600
705
|
this.watcher = null;
|
|
706
|
+
this.previousIndex = null;
|
|
707
|
+
this.previousHistoryLength = null;
|
|
708
|
+
this.previousUrl = null;
|
|
709
|
+
this.previousEntries = null;
|
|
710
|
+
|
|
601
711
|
await closeContext();
|
|
602
712
|
this.pageSet.clear();
|
|
603
713
|
}
|
package/bin/client/recording.js
CHANGED
|
@@ -11,6 +11,8 @@ const Types = {
|
|
|
11
11
|
PARAMETERIZED_CLICK: "parameterized_click",
|
|
12
12
|
CONTEXT_CLICK: "context_click",
|
|
13
13
|
NAVIGATE: "navigate",
|
|
14
|
+
GO_BACK: "browser_go_back",
|
|
15
|
+
GO_FORWARD: "browser_go_forward",
|
|
14
16
|
FILL: "fill_element",
|
|
15
17
|
FILL_SIMPLE: "fill_simple",
|
|
16
18
|
EXECUTE: "execute_page_method",
|