@dev-blinq/cucumber_client 1.0.1266-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.
@@ -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.1266-dev",
3
+ "version": "1.0.1267-dev",
4
4
  "description": "",
5
5
  "main": "bin/index.js",
6
6
  "types": "bin/index.d.ts",