@dev-blinq/cucumber_client 1.0.1237-dev → 1.0.1237-stage

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.
Files changed (40) hide show
  1. package/bin/assets/bundled_scripts/recorder.js +220 -0
  2. package/bin/assets/preload/recorderv3.js +5 -3
  3. package/bin/assets/preload/unique_locators.js +1 -1
  4. package/bin/assets/scripts/aria_snapshot.js +235 -0
  5. package/bin/assets/scripts/dom_attr.js +372 -0
  6. package/bin/assets/scripts/dom_element.js +0 -0
  7. package/bin/assets/scripts/dom_parent.js +185 -0
  8. package/bin/assets/scripts/event_utils.js +105 -0
  9. package/bin/assets/scripts/pw.js +7886 -0
  10. package/bin/assets/scripts/recorder.js +1147 -0
  11. package/bin/assets/scripts/snapshot_capturer.js +155 -0
  12. package/bin/assets/scripts/unique_locators.js +852 -0
  13. package/bin/assets/scripts/yaml.js +4770 -0
  14. package/bin/assets/templates/_hooks_template.txt +37 -0
  15. package/bin/assets/templates/page_template.txt +2 -16
  16. package/bin/assets/templates/utils_template.txt +44 -71
  17. package/bin/client/apiTest/apiTest.js +6 -0
  18. package/bin/client/cli_helpers.js +11 -13
  19. package/bin/client/code_cleanup/utils.js +36 -13
  20. package/bin/client/code_gen/code_inversion.js +68 -10
  21. package/bin/client/code_gen/page_reflection.js +12 -15
  22. package/bin/client/code_gen/playwright_codeget.js +127 -34
  23. package/bin/client/cucumber/feature.js +85 -27
  24. package/bin/client/cucumber/steps_definitions.js +84 -76
  25. package/bin/client/cucumber_selector.js +13 -1
  26. package/bin/client/local_agent.js +3 -3
  27. package/bin/client/project.js +7 -1
  28. package/bin/client/recorderv3/bvt_recorder.js +267 -87
  29. package/bin/client/recorderv3/implemented_steps.js +74 -12
  30. package/bin/client/recorderv3/index.js +58 -8
  31. package/bin/client/recorderv3/network.js +299 -0
  32. package/bin/client/recorderv3/step_runner.js +319 -67
  33. package/bin/client/recorderv3/step_utils.js +152 -5
  34. package/bin/client/recorderv3/update_feature.js +58 -30
  35. package/bin/client/recording.js +5 -0
  36. package/bin/client/run_cucumber.js +5 -1
  37. package/bin/client/scenario_report.js +0 -5
  38. package/bin/client/test_scenario.js +0 -1
  39. package/bin/index.js +1 -0
  40. package/package.json +17 -9
@@ -3,8 +3,7 @@ import { closeContext, initContext, _getDataFile, resetTestData } from "automati
3
3
  import { existsSync, readdirSync, readFileSync, rmSync } from "fs";
4
4
  import path from "path";
5
5
  import url from "url";
6
- import pkg from "../../min/injectedScript.min.cjs";
7
- import { getImplementedSteps, getStepsAndCommandsForScenario } from "./implemented_steps.js";
6
+ import { getImplementedSteps, parseRouteFiles } from "./implemented_steps.js";
8
7
  import { NamesService } from "./services.js";
9
8
  import { BVTStepRunner } from "./step_runner.js";
10
9
  import { readFile, writeFile } from "fs/promises";
@@ -16,42 +15,21 @@ import chokidar from "chokidar";
16
15
  import logger from "../../logger.js";
17
16
  import { unEscapeNonPrintables } from "../cucumber/utils.js";
18
17
  import { findAvailablePort } from "../utils/index.js";
19
- import { getAiConfig } from "../code_gen/page_reflection.js";
18
+ import { Step } from "../cucumber/feature.js";
19
+
20
20
  const __dirname = path.dirname(url.fileURLToPath(import.meta.url));
21
- const { source: injectedScriptSource } = pkg;
21
+
22
22
  const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
23
- export function getInitScript(config) {
24
- const popupHandlers = config?.popupHandlers ?? [];
25
-
26
- const disableHighlight = config?.disableHighlight ?? false;
27
-
28
- const disableMultipleLocators = config?.disableMultipleLocators ?? false;
29
-
30
- const popupScript = `
31
- window.__bvt_Recorder.setPopupHandlers(${JSON.stringify(popupHandlers)});
32
- window.__bvt_Recorder.setDisableHighlight(${JSON.stringify(disableHighlight)});
33
- window.__bvt_Recorder.setImproviseLocators(${JSON.stringify(!disableMultipleLocators)});`;
34
- return (
35
- [
36
- path.join(__dirname, "..", "..", "assets", "preload", "accessibility.js"),
37
- path.join(__dirname, "..", "..", "assets", "preload", "dom-utils.js"),
38
- path.join(__dirname, "..", "..", "assets", "preload", "generateSelector.js"),
39
- path.join(__dirname, "..", "..", "assets", "preload", "locators.js"),
40
- path.join(__dirname, "..", "..", "assets", "preload", "unique_locators.js"),
41
- // path.join(__dirname, "..", "..", "assets", "preload", "pw_locators.js"),
42
- path.join(__dirname, "..", "..", "assets", "preload", "climb.js"),
43
- path.join(__dirname, "..", "..", "assets", "preload", "text-locator.js"),
44
- path.join(__dirname, "..", "..", "assets", "preload", "toolbar.js"),
45
- path.join(__dirname, "..", "..", "assets", "preload", "recording-tool.js"),
46
- path.join(__dirname, "..", "..", "assets", "preload", "find_context.js"),
47
- path.join(__dirname, "..", "..", "assets", "preload", "recorderv3.js"),
48
- path.join(__dirname, "..", "..", "assets", "preload", "findElementText.js"),
49
- path.join(__dirname, "..", "..", "assets", "preload", "css_gen.js"),
50
- path.join(__dirname, "..", "..", "assets", "preload", "yaml.js"),
51
- ]
52
- .map((filePath) => readFileSync(filePath, "utf8"))
53
- .join("\n") + popupScript
23
+ export function getInitScript(config, options) {
24
+ const preScript = `
25
+ window.__bvt_Recorder_config = ${JSON.stringify(config ?? null)};
26
+ window.__PW_options = ${JSON.stringify(options ?? null)};
27
+ `;
28
+ const recorderScript = readFileSync(
29
+ path.join(__dirname, "..", "..", "assets", "bundled_scripts", "recorder.js"),
30
+ "utf8"
54
31
  );
32
+ return preScript + recorderScript;
55
33
  }
56
34
 
57
35
  async function evaluate(frame, script) {
@@ -72,7 +50,7 @@ async function findNestedFrameSelector(frame, obj) {
72
50
  const frameElement = await frame.frameElement();
73
51
  if (!frameElement) return;
74
52
  const selectors = await parent.evaluate((element) => {
75
- return window.getPWLocators(element);
53
+ return window.__bvt_Recorder.locatorGenerator.getElementLocators(element, { excludeText: true }).locators;
76
54
  }, frameElement);
77
55
  return findNestedFrameSelector(parent, { children: obj, selectors });
78
56
  } catch (e) {
@@ -206,11 +184,8 @@ export class BVTRecorder {
206
184
  projectDir: this.projectDir,
207
185
  logger: this.logger,
208
186
  });
209
- this.stepRunner = new BVTStepRunner({
210
- projectDir: this.projectDir,
211
- });
212
187
  this.pageSet = new Set();
213
-
188
+ this.pageMetaDataSet = new Set();
214
189
  this.lastKnownUrlPath = "";
215
190
  // TODO: what is world?
216
191
  this.world = { attach: () => {} };
@@ -226,6 +201,10 @@ export class BVTRecorder {
226
201
  onStepDetails: "BVTRecorder.onStepDetails",
227
202
  getTestData: "BVTRecorder.getTestData",
228
203
  onGoto: "BVTRecorder.onGoto",
204
+ cmdExecutionStart: "BVTRecorder.cmdExecutionStart",
205
+ cmdExecutionSuccess: "BVTRecorder.cmdExecutionSuccess",
206
+ cmdExecutionError: "BVTRecorder.cmdExecutionError",
207
+ interceptResults: "BVTRecorder.interceptResults",
229
208
  };
230
209
  bindings = {
231
210
  __bvt_recordCommand: async ({ frame, page, context }, event) => {
@@ -301,29 +280,22 @@ export class BVTRecorder {
301
280
  return result;
302
281
  }
303
282
  getInitScripts(config) {
304
- return getInitScript(config);
305
- // const scripts = []
306
- // scripts.push(...this.getPWScript());
307
- // scripts.push(...this.getRecorderScripts());
308
-
309
- // const popupHandlers = config?.popupHandlers ?? [];
310
- // const disableHighlight = config?.disableHighlight ?? false;
311
- // const disableMultipleLocators = config?.disableMultipleLocators ?? false;
312
-
313
- // const inlineScript = `
314
- // window.__bvt_Recorder.setPopupHandlers(${JSON.stringify(popupHandlers)});
315
- // window.__bvt_Recorder.setDisableHighlight(${JSON.stringify(disableHighlight)});
316
- // window.__bvt_Recorder.setImproviseLocators(${JSON.stringify(!disableMultipleLocators)});
317
- // `
318
- // scripts.push(inlineScript);
319
- // return scripts;
283
+ return getInitScript(config, {
284
+ sdkLanguage: "javascript",
285
+ testIdAttributeName: "blinq-test-id",
286
+ stableRafCount: 0,
287
+ browserName: this.browser?.browserType().name(),
288
+ inputFileRoleTextbox: false,
289
+ customEngines: [],
290
+ isUnderTest: true,
291
+ });
320
292
  }
321
293
 
322
294
  async _initBrowser({ url }) {
323
295
  this.#remoteDebuggerPort = await findAvailablePort();
324
296
  process.env.CDP_LISTEN_PORT = this.#remoteDebuggerPort;
325
297
 
326
- this.stepRunner.setRemoteDebugPort(this.#remoteDebuggerPort);
298
+ // this.stepRunner.setRemoteDebugPort(this.#remoteDebuggerPort);
327
299
  this.world = { attach: () => {} };
328
300
 
329
301
  const ai_config_file = path.join(this.projectDir, "ai_config.json");
@@ -335,8 +307,9 @@ export class BVTRecorder {
335
307
  console.error("Error reading ai_config.json", error);
336
308
  }
337
309
  }
310
+ this.config = ai_config;
338
311
  const initScripts = {
339
- recorderCjs: injectedScriptSource,
312
+ // recorderCjs: injectedScriptSource,
340
313
  scripts: [
341
314
  this.getInitScripts(ai_config),
342
315
  `\ndelete Object.getPrototypeOf(navigator).webdriver;${process.env.WINDOW_DEBUGGER ? "window.debug=true;\n" : ""}`,
@@ -348,11 +321,40 @@ export class BVTRecorder {
348
321
  let stopTime = Date.now();
349
322
  this.logger.info(`Browser launched in ${(stopTime - startTime) / 1000} s`);
350
323
  this.bvtContext = bvtContext;
324
+ this.stepRunner = new BVTStepRunner({
325
+ projectDir: this.projectDir,
326
+ sendExecutionStatus: (data) => {
327
+ if (data && data.type) {
328
+ switch (data.type) {
329
+ case "cmdExecutionStart":
330
+ console.log("Sending cmdExecutionStart event for cmdId:", data);
331
+ this.sendEvent(this.events.cmdExecutionStart, data);
332
+ break;
333
+ case "cmdExecutionSuccess":
334
+ console.log("Sending cmdExecutionSuccess event for cmdId:", data);
335
+ this.sendEvent(this.events.cmdExecutionSuccess, data);
336
+ break;
337
+ case "cmdExecutionError":
338
+ console.log("Sending cmdExecutionError event for cmdId:", data);
339
+ this.sendEvent(this.events.cmdExecutionError, data);
340
+ break;
341
+ case "interceptResults":
342
+ console.log("Sending interceptResults event");
343
+ this.sendEvent(this.events.interceptResults, data);
344
+ break;
345
+ default:
346
+ console.warn("Unknown command execution status type:", data.type);
347
+ break;
348
+ }
349
+ }
350
+ },
351
+ bvtContext: this.bvtContext,
352
+ });
351
353
  const context = bvtContext.playContext;
352
354
  this.context = context;
353
355
  this.web = bvtContext.stable || bvtContext.web;
356
+ this.web.tryAllStrategies = true;
354
357
  this.page = bvtContext.page;
355
-
356
358
  this.pageSet.add(this.page);
357
359
  this.lastKnownUrlPath = this._updateUrlPath();
358
360
  const browser = await this.context.browser();
@@ -463,6 +465,75 @@ export class BVTRecorder {
463
465
  }
464
466
  });
465
467
  }
468
+
469
+ hasHistoryReplacementAtIndex(previousEntries, currentEntries, index) {
470
+ if (!previousEntries || !currentEntries) return false;
471
+ if (index >= previousEntries.length || index >= currentEntries.length) return false;
472
+
473
+ const prevEntry = previousEntries[index];
474
+ // console.log("prevEntry", prevEntry);
475
+ const currEntry = currentEntries[index];
476
+ // console.log("currEntry", currEntry);
477
+
478
+ // Check if the entry at this index has been replaced
479
+ return prevEntry.id !== currEntry.id;
480
+ }
481
+
482
+ // Even simpler approach for your specific case
483
+ analyzeTransitionType(entries, currentIndex, currentEntry) {
484
+ // console.log("Analyzing transition type");
485
+ // console.log("===========================");
486
+ // console.log("Current Index:", currentIndex);
487
+ // console.log("Current Entry:", currentEntry);
488
+ // console.log("Current Entries:", entries);
489
+ // console.log("Current entries length:", entries.length);
490
+ // console.log("===========================");
491
+ // console.log("Previous Index:", this.previousIndex);
492
+ // // console.log("Previous Entry:", this.previousEntries[this.previousIndex]);
493
+ // console.log("Previous Entries:", this.previousEntries);
494
+ // console.log("Previous entries length:", this.previousHistoryLength);
495
+
496
+ if (this.previousIndex === null || this.previousHistoryLength === null || !this.previousEntries) {
497
+ return {
498
+ action: "initial",
499
+ };
500
+ }
501
+
502
+ const indexDiff = currentIndex - this.previousIndex;
503
+ const lengthDiff = entries.length - this.previousHistoryLength;
504
+
505
+ // Backward navigation
506
+ if (indexDiff < 0) {
507
+ return { action: "back" };
508
+ }
509
+
510
+ // Forward navigation
511
+ if (indexDiff > 0 && lengthDiff === 0) {
512
+ // Check if the entry at current index is the same as before
513
+ const entryReplaced = this.hasHistoryReplacementAtIndex(this.previousEntries, entries, currentIndex);
514
+
515
+ if (entryReplaced) {
516
+ return { action: "navigate" }; // New navigation that replaced forward history
517
+ } else {
518
+ return { action: "forward" }; // True forward navigation
519
+ }
520
+ }
521
+
522
+ // New navigation (history grew)
523
+ if (lengthDiff > 0) {
524
+ return { action: "navigate" };
525
+ }
526
+
527
+ // Same position, same length
528
+ if (lengthDiff <= 0) {
529
+ const entryReplaced = this.hasHistoryReplacementAtIndex(this.previousEntries, entries, currentIndex);
530
+
531
+ return entryReplaced ? { action: "navigate" } : { action: "reload" };
532
+ }
533
+
534
+ return { action: "unknown" };
535
+ }
536
+
466
537
  async getCurrentTransition() {
467
538
  if (this?.web?.browser?._name !== "chromium") {
468
539
  return;
@@ -471,35 +542,70 @@ export class BVTRecorder {
471
542
 
472
543
  try {
473
544
  const result = await client.send("Page.getNavigationHistory");
474
- // console.log("result", result);
545
+ // console.log("Navigation History:", result);
475
546
  const entries = result.entries;
476
547
  const currentIndex = result.currentIndex;
477
548
 
478
549
  // ignore if currentIndex is not the last entry
479
- if (currentIndex !== entries.length - 1) return;
550
+ // if (currentIndex !== entries.length - 1) return;
480
551
 
481
552
  const currentEntry = entries[currentIndex];
482
- return currentEntry;
553
+ const transitionInfo = this.analyzeTransitionType(entries, currentIndex, currentEntry);
554
+ this.previousIndex = currentIndex;
555
+ this.previousHistoryLength = entries.length;
556
+ this.previousUrl = currentEntry.url;
557
+ this.previousEntries = [...entries]; // Store a copy of current entries
558
+
559
+ return {
560
+ currentEntry,
561
+ navigationAction: transitionInfo.action,
562
+ };
483
563
  } catch (error) {
484
- console.error("Error in getTransistionType event");
564
+ console.error("Error in getTransistionType event", error);
485
565
  } finally {
486
566
  await client.detach();
487
567
  }
488
568
  }
489
- userInitiatedTranistionTypes = ["typed", "address_bar"];
569
+ userInitiatedTransitionTypes = ["typed", "address_bar"];
490
570
  async handlePageTransition() {
491
571
  const transition = await this.getCurrentTransition();
492
572
  if (!transition) return;
493
- // console.log("transitionType", transition.transitionType);
494
- if (this.userInitiatedTranistionTypes.includes(transition.transitionType)) {
495
- const env = JSON.parse(readFileSync(this.envName), "utf8");
496
- const baseUrl = env.baseUrl;
497
- let url = transition.userTypedURL;
498
- if (baseUrl && url.startsWith(baseUrl)) {
499
- url = url.replace(baseUrl, "{{env.baseUrl}}");
500
- }
501
- // console.log("User initiated transition");
502
- this.sendEvent(this.events.onGoto, { url });
573
+
574
+ const { currentEntry, navigationAction } = transition;
575
+
576
+ switch (navigationAction) {
577
+ case "initial":
578
+ // console.log("Initial navigation, no action taken");
579
+ return;
580
+ case "navigate":
581
+ // console.log("transitionType", transition.transitionType);
582
+ // console.log("sending onGoto event", { url: currentEntry.url,
583
+ // type: "navigate", });
584
+ if (this.userInitiatedTransitionTypes.includes(currentEntry.transitionType)) {
585
+ const env = JSON.parse(readFileSync(this.envName), "utf8");
586
+ const baseUrl = env.baseUrl;
587
+ let url = currentEntry.userTypedURL;
588
+ if (baseUrl && url.startsWith(baseUrl)) {
589
+ url = url.replace(baseUrl, "{{env.baseUrl}}");
590
+ }
591
+ // console.log("User initiated transition");
592
+ this.sendEvent(this.events.onGoto, { url, type: "navigate" });
593
+ }
594
+ return;
595
+ case "back":
596
+ // console.log("User navigated back");
597
+ // console.log("sending onGoto event", {
598
+ // type: "back",
599
+ // });
600
+ this.sendEvent(this.events.onGoto, { type: "back" });
601
+ return;
602
+ case "forward":
603
+ // console.log("User navigated forward"); console.log("sending onGoto event", { type: "forward", });
604
+ this.sendEvent(this.events.onGoto, { type: "forward" });
605
+ return;
606
+ default:
607
+ this.sendEvent(this.events.onGoto, { type: "unknown" });
608
+ return;
503
609
  }
504
610
  }
505
611
 
@@ -628,6 +734,11 @@ export class BVTRecorder {
628
734
  delete process.env.TEMP_RUN;
629
735
  await this.watcher.close().then(() => {});
630
736
  this.watcher = null;
737
+ this.previousIndex = null;
738
+ this.previousHistoryLength = null;
739
+ this.previousUrl = null;
740
+ this.previousEntries = null;
741
+
631
742
  await closeContext();
632
743
  this.pageSet.clear();
633
744
  }
@@ -704,40 +815,61 @@ export class BVTRecorder {
704
815
  async abortExecution() {
705
816
  await this.stepRunner.abortExecution();
706
817
  }
818
+
819
+ async pauseExecution({ cmdId }) {
820
+ await this.stepRunner.pauseExecution(cmdId);
821
+ }
822
+
823
+ async resumeExecution({ cmdId }) {
824
+ await this.stepRunner.resumeExecution(cmdId);
825
+ }
826
+
707
827
  async dealyedRevertMode() {
708
828
  const timerId = setTimeout(async () => {
709
829
  await this.revertMode();
710
830
  }, 100);
711
831
  this.timerId = timerId;
712
832
  }
713
- async runStep({ step, parametersMap }, options) {
833
+ async runStep({ step, parametersMap, tags, isFirstStep, listenNetwork }, options) {
834
+ const { skipAfter = true, skipBefore = !isFirstStep } = options || {};
714
835
  const _env = {
715
836
  TOKEN: this.TOKEN,
716
837
  TEMP_RUN: true,
717
838
  REPORT_FOLDER: this.bvtContext.reportFolder,
718
839
  BLINQ_ENV: this.envName,
840
+ STORE_DETAILED_NETWORK_DATA: listenNetwork ? "true" : "false",
841
+ CURRENT_STEP_ID: step.id,
719
842
  };
720
843
 
721
844
  this.bvtContext.navigate = true;
845
+ this.bvtContext.loadedRoutes = null;
722
846
  for (const [key, value] of Object.entries(_env)) {
723
847
  process.env[key] = value;
724
848
  }
849
+
725
850
  if (this.timerId) {
726
851
  clearTimeout(this.timerId);
727
852
  this.timerId = null;
728
853
  }
729
854
  await this.setMode("running");
855
+
730
856
  try {
731
- await this.stepRunner.runStep(
857
+ const { result, info } = await this.stepRunner.runStep(
732
858
  {
733
859
  step,
734
860
  parametersMap,
735
861
  envPath: this.envName,
862
+ tags,
863
+ config: this.config,
736
864
  },
737
865
  this.bvtContext,
738
- options
866
+ {
867
+ skipAfter,
868
+ skipBefore,
869
+ }
739
870
  );
740
871
  await this.revertMode();
872
+ return { info };
741
873
  } catch (error) {
742
874
  await this.revertMode();
743
875
  throw error;
@@ -748,15 +880,10 @@ export class BVTRecorder {
748
880
  this.bvtContext.navigate = false;
749
881
  }
750
882
  }
751
- async runScenario({ steps, parametersMap }) {
752
- for (const step of steps) {
753
- await this.runStep({ step, parametersMap });
754
- }
755
- }
756
883
  async saveScenario({ scenario, featureName, override, isSingleStep }) {
757
884
  await updateStepDefinitions({ scenario, featureName, projectDir: this.projectDir }); // updates mjs files
758
885
  if (!isSingleStep) await updateFeatureFile({ featureName, scenario, override, projectDir: this.projectDir }); // updates gherkin files
759
- await this.cleanup();
886
+ await this.cleanup({ tags: scenario.tags });
760
887
  }
761
888
  async getImplementedSteps() {
762
889
  const stepsAndScenarios = await getImplementedSteps(this.projectDir);
@@ -772,7 +899,15 @@ export class BVTRecorder {
772
899
  };
773
900
  }
774
901
  async getStepsAndCommandsForScenario({ name, featureName }) {
775
- return this.scenariosStepsMap.get(name) || [];
902
+ const steps = this.scenariosStepsMap.get(name) || [];
903
+ for (const step of steps) {
904
+ if (step.isImplemented) {
905
+ step.commands = this.getCommandsForImplementedStep({ stepName: step.text });
906
+ } else {
907
+ step.commands = [];
908
+ }
909
+ }
910
+ return steps;
776
911
  // return getStepsAndCommandsForScenario({
777
912
  // name,
778
913
  // featureName,
@@ -780,6 +915,7 @@ export class BVTRecorder {
780
915
  // map: this.scenariosStepsMap,
781
916
  // });
782
917
  }
918
+
783
919
  async generateStepName({ commands, stepsNames, parameters, map }) {
784
920
  return await this.namesService.generateStepName({ commands, stepsNames, parameters, map });
785
921
  }
@@ -862,9 +998,9 @@ export class BVTRecorder {
862
998
  }
863
999
  }
864
1000
 
865
- async discardTestData() {
1001
+ async discardTestData({ tags }) {
866
1002
  resetTestData(this.envName, this.world);
867
- await this.cleanup();
1003
+ await this.cleanup({ tags });
868
1004
  }
869
1005
  async addToTestData(obj) {
870
1006
  if (!existsSync(_getDataFile(this.world, this.bvtContext, this.web))) {
@@ -903,6 +1039,7 @@ export class BVTRecorder {
903
1039
  const stepParams = parseStepTextParameters(stepName);
904
1040
  return getCommandsForImplementedStep(stepName, step_definitions, stepParams).commands;
905
1041
  }
1042
+
906
1043
  loadExistingScenario({ featureName, scenarioName }) {
907
1044
  const step_definitions = loadStepDefinitions(this.projectDir);
908
1045
  const featureFilePath = path.join(this.projectDir, "features", featureName);
@@ -931,6 +1068,7 @@ export class BVTRecorder {
931
1068
  ..._s,
932
1069
  keyword: step.keyword.trim(),
933
1070
  };
1071
+ parseRouteFiles(this.projectDir, _step);
934
1072
  steps.push(_step);
935
1073
  }
936
1074
  return {
@@ -970,7 +1108,7 @@ export class BVTRecorder {
970
1108
  }
971
1109
  return result;
972
1110
  }
973
- async cleanup() {
1111
+ async cleanup({ tags }) {
974
1112
  const noopStep = {
975
1113
  text: "Noop",
976
1114
  isImplemented: true,
@@ -984,6 +1122,7 @@ export class BVTRecorder {
984
1122
  {
985
1123
  step: noopStep,
986
1124
  parametersMap: {},
1125
+ tags: tags || [],
987
1126
  },
988
1127
  {
989
1128
  skipAfter: false,
@@ -1022,6 +1161,47 @@ export class BVTRecorder {
1022
1161
  return false;
1023
1162
  }
1024
1163
  }
1164
+ async initExecution({ tags = [] }) {
1165
+ // run before hooks
1166
+ const noopStep = {
1167
+ text: "Noop",
1168
+ isImplemented: true,
1169
+ };
1170
+ await this.runStep(
1171
+ {
1172
+ step: noopStep,
1173
+ parametersMap: {},
1174
+ tags,
1175
+ },
1176
+ {
1177
+ skipBefore: false,
1178
+ skipAfter: true,
1179
+ }
1180
+ );
1181
+ }
1182
+ async cleanupExecution({ tags = [] }) {
1183
+ // run after hooks
1184
+ const noopStep = {
1185
+ text: "Noop",
1186
+ isImplemented: true,
1187
+ };
1188
+ await this.runStep(
1189
+ {
1190
+ step: noopStep,
1191
+ parametersMap: {},
1192
+ tags,
1193
+ },
1194
+ {
1195
+ skipBefore: true,
1196
+ skipAfter: false,
1197
+ }
1198
+ );
1199
+ }
1200
+ async resetExecution({ tags = [] }) {
1201
+ // run after hooks followed by before hooks
1202
+ await this.cleanupExecution({ tags });
1203
+ await this.initExecution({ tags });
1204
+ }
1025
1205
  }
1026
1206
 
1027
1207
  const parseFeatureFile = (featureFilePath) => {
@@ -1,9 +1,9 @@
1
1
  import { AstBuilder, GherkinClassicTokenMatcher, Parser } from "@cucumber/gherkin";
2
- import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
2
+ import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync } from "fs";
3
3
  import path from "path";
4
4
  import url from "url";
5
5
  import { findFilesWithExtension, StepsDefinitions } from "../cucumber/steps_definitions.js";
6
- import { Feature } from "../cucumber/feature.js";
6
+ import { Feature, Step } from "../cucumber/feature.js";
7
7
  import { CodePage } from "../code_gen/page_reflection.js";
8
8
  import { getCommandsForImplementedStep, loadStepDefinitions } from "./step_utils.js";
9
9
  import { parseStepTextParameters } from "../cucumber/utils.js";
@@ -13,7 +13,6 @@ const uuidFn = () => (++id).toString(16);
13
13
  const builder = new AstBuilder(uuidFn);
14
14
  const matcher = new GherkinClassicTokenMatcher();
15
15
  const parser = new Parser(builder, matcher);
16
-
17
16
  let i = 0;
18
17
  const getImplId = () => {
19
18
  return `I-${i++}`;
@@ -57,6 +56,53 @@ function memorySizeOf(obj) {
57
56
  return sizeOf(obj);
58
57
  }
59
58
 
59
+ export function parseRouteFiles(projectDir, step) {
60
+ const routeFolder = path.join(projectDir, "data", "routes");
61
+ const templateRouteMap = new Map();
62
+
63
+ if (!existsSync(routeFolder)) {
64
+ step.routeItems = null;
65
+ return;
66
+ }
67
+
68
+ // Go over all the files in the route folder and parse them
69
+ const routeFiles = readdirSync(routeFolder).filter((file) => file.endsWith(".json"));
70
+ for (const file of routeFiles) {
71
+ const filePath = path.join(routeFolder, file);
72
+ const routeData = JSON.parse(readFileSync(filePath, "utf8"));
73
+ if (routeData && routeData.template) {
74
+ const template = routeData.template;
75
+ const routes = routeData.routes;
76
+
77
+ templateRouteMap.set(template, routes);
78
+ }
79
+ }
80
+
81
+ if (!existsSync(routeFolder)) {
82
+ return null;
83
+ } else if (step && step.text) {
84
+ // Convert the step text to cucumber template
85
+ const cucumberStep = new Step();
86
+ cucumberStep.text = step.text;
87
+ const template = cucumberStep.getTemplate();
88
+ if (templateRouteMap.has(template)) {
89
+ const routeItems = templateRouteMap.get(template);
90
+ routeItems.forEach((item) => {
91
+ const filters = item.filters || {};
92
+ const queryParams = filters?.queryParams || {};
93
+ const queryParamsArray = Object.keys(queryParams).map((key) => ({
94
+ key: key,
95
+ value: queryParams[key],
96
+ }));
97
+ filters.queryParams = queryParamsArray || [];
98
+ });
99
+ step.routeItems = routeItems;
100
+ } else {
101
+ step.routeItems = null;
102
+ }
103
+ }
104
+ }
105
+
60
106
  export const getImplementedSteps = async (projectDir) => {
61
107
  const foundErrors = [];
62
108
  try {
@@ -70,6 +116,12 @@ export const getImplementedSteps = async (projectDir) => {
70
116
  const utilsContent = readFileSync(utilsTemplateFilePath, "utf8");
71
117
  //console.log({ utilsContent });
72
118
  writeFileSync(utilsFilePath, utilsContent, "utf8");
119
+ const hooksTemplateFilePath = path.join(__dirname, "../../assets", "templates", "_hooks_template.txt");
120
+ if (existsSync(hooksTemplateFilePath)) {
121
+ const hooksFilePath = path.join(stepDefinitionFolderPath, "_hooks.mjs");
122
+ const hooksContent = readFileSync(hooksTemplateFilePath, "utf8");
123
+ writeFileSync(hooksFilePath, hooksContent, "utf8");
124
+ }
73
125
  } catch (error) {
74
126
  foundErrors.push({ error });
75
127
  }
@@ -168,7 +220,10 @@ export const getImplementedSteps = async (projectDir) => {
168
220
  }
169
221
  stepLineSet.add(stepLine);
170
222
  step.templateIndex = implementedSteps.length;
171
- implementedSteps.push({
223
+
224
+ parseRouteFiles(projectDir, step);
225
+
226
+ const implementedStep = {
172
227
  keyword: step.keyword.trim(),
173
228
  keywordAlias: step.keywordAlias?.trim(),
174
229
  text: updateStepText(template.pattern, step.parameters),
@@ -179,7 +234,10 @@ export const getImplementedSteps = async (projectDir) => {
179
234
  templateIndex: step.templateIndex,
180
235
  pattern: template.pattern,
181
236
  paths: template.paths,
182
- });
237
+ routeItems: step.routeItems,
238
+ };
239
+
240
+ implementedSteps.push(implementedStep);
183
241
  }
184
242
  }
185
243
 
@@ -236,13 +294,17 @@ export const getImplementedSteps = async (projectDir) => {
236
294
  for (const tag of scenario.tags) {
237
295
  delete tag.location;
238
296
  }
239
- // const steps = scenario.steps;
240
- // for (const step of steps) {
241
- // const index = implementedSteps.findIndex((istep) => {
242
- // return stepsDefinitions._stepNameToTemplate(step.text) === istep.pattern;
243
- // });
244
- // step.templateIndex = index;
245
- // }
297
+ for (const scenario of scenarios) {
298
+ for (const step of scenario.steps) {
299
+ if (step.templateIndex === undefined) {
300
+ const cleanStepName = stepsDefinitions._stepNameToTemplate(step.text);
301
+ const index = implementedSteps.findIndex((istep) => {
302
+ return cleanStepName === istep.pattern;
303
+ });
304
+ step.templateIndex = index;
305
+ }
306
+ }
307
+ }
246
308
  }
247
309
  if (foundErrors.length > 0) {
248
310
  console.log("foundErrors", foundErrors);