@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.
@@ -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
- result.push({
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 === 1) {
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 === 0) {
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
- result.push({
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 === 1) {
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 === 0) {
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
- * Verify the page url is "<url>"
173
- * @param {string} url URL to be verified against current URL
174
- * @protect
175
- */
187
+ * Noop step for running only hooks
188
+ */
189
+ When("Noop", async function () {});
176
190
 
177
- async function verify_page_url(url){
178
- await context.web.verifyPagePath(url,{}, this);
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(path.join(__dirname, "..", "..", "assets", "bundled_scripts", "recorder.js"), "utf8")
28
- return (
29
- preScript +
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("result", result);
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
- return currentEntry;
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
- userInitiatedTranistionTypes = ["typed", "address_bar"];
537
+ userInitiatedTransitionTypes = ["typed", "address_bar"];
460
538
  async handlePageTransition() {
461
539
  const transition = await this.getCurrentTransition();
462
540
  if (!transition) return;
463
- // console.log("transitionType", transition.transitionType);
464
- if (this.userInitiatedTranistionTypes.includes(transition.transitionType)) {
465
- const env = JSON.parse(readFileSync(this.envName), "utf8");
466
- const baseUrl = env.baseUrl;
467
- let url = transition.userTypedURL;
468
- if (baseUrl && url.startsWith(baseUrl)) {
469
- url = url.replace(baseUrl, "{{env.baseUrl}}");
470
- }
471
- // console.log("User initiated transition");
472
- this.sendEvent(this.events.onGoto, { url });
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
  }
@@ -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",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dev-blinq/cucumber_client",
3
- "version": "1.0.1265-dev",
3
+ "version": "1.0.1267-dev",
4
4
  "description": "",
5
5
  "main": "bin/index.js",
6
6
  "types": "bin/index.d.ts",