@dev-blinq/cucumber_client 1.0.1224-dev → 1.0.1224-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 +68 -5
  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 +841 -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 +48 -63
  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 +42 -14
  20. package/bin/client/code_gen/code_inversion.js +68 -10
  21. package/bin/client/code_gen/index.js +3 -0
  22. package/bin/client/code_gen/page_reflection.js +37 -20
  23. package/bin/client/code_gen/playwright_codeget.js +170 -33
  24. package/bin/client/cucumber/feature.js +85 -27
  25. package/bin/client/cucumber/steps_definitions.js +84 -76
  26. package/bin/client/local_agent.js +7 -3
  27. package/bin/client/project.js +7 -1
  28. package/bin/client/recorderv3/bvt_recorder.js +267 -80
  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 +157 -6
  34. package/bin/client/recorderv3/update_feature.js +58 -30
  35. package/bin/client/recording.js +7 -0
  36. package/bin/client/run_cucumber.js +15 -2
  37. package/bin/client/scenario_report.js +4 -8
  38. package/bin/client/test_scenario.js +0 -1
  39. package/bin/index.js +1 -0
  40. package/package.json +15 -8
@@ -82,6 +82,12 @@ class Project {
82
82
  const utilsContent = readFileSync(utilsPath, "utf8");
83
83
  const utilsFileName = this.stepsFolder + "/utils.mjs";
84
84
  writeFileSync(utilsFileName, utilsContent, "utf8");
85
+ const hooksTemplateFilePath = path.join(__dirname, "../assets", "templates", "_hooks_template.txt");
86
+ if (existsSync(hooksTemplateFilePath)) {
87
+ const hooksFilePath = path.join(this.stepsFolder, "_hooks.mjs");
88
+ const hooksContent = readFileSync(hooksTemplateFilePath, "utf8");
89
+ writeFileSync(hooksFilePath, hooksContent, "utf8");
90
+ }
85
91
  } catch (e) {
86
92
  logger.error("Error loading utils_template");
87
93
  }
@@ -104,7 +110,7 @@ class Project {
104
110
  this.pages.push(page);
105
111
  }
106
112
  }
107
- return envFile;
113
+ return this.environment.name;
108
114
  }
109
115
  getPagesNames() {
110
116
  let result = [];
@@ -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,41 +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";
18
+ import { Step } from "../cucumber/feature.js";
19
+
19
20
  const __dirname = path.dirname(url.fileURLToPath(import.meta.url));
20
- const { source: injectedScriptSource } = pkg;
21
+
21
22
  const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
22
- export function getInitScript(config) {
23
- const popupHandlers = config?.popupHandlers ?? [];
24
-
25
- const disableHighlight = config?.disableHighlight ?? false;
26
-
27
- const disableMultipleLocators = config?.disableMultipleLocators ?? false;
28
-
29
- const popupScript = `
30
- window.__bvt_Recorder.setPopupHandlers(${JSON.stringify(popupHandlers)});
31
- window.__bvt_Recorder.setDisableHighlight(${JSON.stringify(disableHighlight)});
32
- window.__bvt_Recorder.setImproviseLocators(${JSON.stringify(!disableMultipleLocators)});`;
33
- return (
34
- [
35
- path.join(__dirname, "..", "..", "assets", "preload", "accessibility.js"),
36
- path.join(__dirname, "..", "..", "assets", "preload", "dom-utils.js"),
37
- path.join(__dirname, "..", "..", "assets", "preload", "generateSelector.js"),
38
- path.join(__dirname, "..", "..", "assets", "preload", "locators.js"),
39
- path.join(__dirname, "..", "..", "assets", "preload", "unique_locators.js"),
40
- // path.join(__dirname, "..", "..", "assets", "preload", "pw_locators.js"),
41
- path.join(__dirname, "..", "..", "assets", "preload", "climb.js"),
42
- path.join(__dirname, "..", "..", "assets", "preload", "text-locator.js"),
43
- path.join(__dirname, "..", "..", "assets", "preload", "toolbar.js"),
44
- path.join(__dirname, "..", "..", "assets", "preload", "recording-tool.js"),
45
- path.join(__dirname, "..", "..", "assets", "preload", "find_context.js"),
46
- path.join(__dirname, "..", "..", "assets", "preload", "recorderv3.js"),
47
- path.join(__dirname, "..", "..", "assets", "preload", "findElementText.js"),
48
- path.join(__dirname, "..", "..", "assets", "preload", "css_gen.js"),
49
- path.join(__dirname, "..", "..", "assets", "preload", "yaml.js"),
50
- ]
51
- .map((filePath) => readFileSync(filePath, "utf8"))
52
- .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"
53
31
  );
32
+ return preScript + recorderScript;
54
33
  }
55
34
 
56
35
  async function evaluate(frame, script) {
@@ -71,7 +50,7 @@ async function findNestedFrameSelector(frame, obj) {
71
50
  const frameElement = await frame.frameElement();
72
51
  if (!frameElement) return;
73
52
  const selectors = await parent.evaluate((element) => {
74
- return window.getPWLocators(element);
53
+ return window.__bvt_Recorder.locatorGenerator.getElementLocators(element, { excludeText: true }).locators;
75
54
  }, frameElement);
76
55
  return findNestedFrameSelector(parent, { children: obj, selectors });
77
56
  } catch (e) {
@@ -205,11 +184,8 @@ export class BVTRecorder {
205
184
  projectDir: this.projectDir,
206
185
  logger: this.logger,
207
186
  });
208
- this.stepRunner = new BVTStepRunner({
209
- projectDir: this.projectDir,
210
- });
211
187
  this.pageSet = new Set();
212
-
188
+ this.pageMetaDataSet = new Set();
213
189
  this.lastKnownUrlPath = "";
214
190
  // TODO: what is world?
215
191
  this.world = { attach: () => {} };
@@ -225,6 +201,10 @@ export class BVTRecorder {
225
201
  onStepDetails: "BVTRecorder.onStepDetails",
226
202
  getTestData: "BVTRecorder.getTestData",
227
203
  onGoto: "BVTRecorder.onGoto",
204
+ cmdExecutionStart: "BVTRecorder.cmdExecutionStart",
205
+ cmdExecutionSuccess: "BVTRecorder.cmdExecutionSuccess",
206
+ cmdExecutionError: "BVTRecorder.cmdExecutionError",
207
+ interceptResults: "BVTRecorder.interceptResults",
228
208
  };
229
209
  bindings = {
230
210
  __bvt_recordCommand: async ({ frame, page, context }, event) => {
@@ -300,29 +280,22 @@ export class BVTRecorder {
300
280
  return result;
301
281
  }
302
282
  getInitScripts(config) {
303
- return getInitScript(config);
304
- // const scripts = []
305
- // scripts.push(...this.getPWScript());
306
- // scripts.push(...this.getRecorderScripts());
307
-
308
- // const popupHandlers = config?.popupHandlers ?? [];
309
- // const disableHighlight = config?.disableHighlight ?? false;
310
- // const disableMultipleLocators = config?.disableMultipleLocators ?? false;
311
-
312
- // const inlineScript = `
313
- // window.__bvt_Recorder.setPopupHandlers(${JSON.stringify(popupHandlers)});
314
- // window.__bvt_Recorder.setDisableHighlight(${JSON.stringify(disableHighlight)});
315
- // window.__bvt_Recorder.setImproviseLocators(${JSON.stringify(!disableMultipleLocators)});
316
- // `
317
- // scripts.push(inlineScript);
318
- // 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
+ });
319
292
  }
320
293
 
321
294
  async _initBrowser({ url }) {
322
295
  this.#remoteDebuggerPort = await findAvailablePort();
323
296
  process.env.CDP_LISTEN_PORT = this.#remoteDebuggerPort;
324
297
 
325
- this.stepRunner.setRemoteDebugPort(this.#remoteDebuggerPort);
298
+ // this.stepRunner.setRemoteDebugPort(this.#remoteDebuggerPort);
326
299
  this.world = { attach: () => {} };
327
300
 
328
301
  const ai_config_file = path.join(this.projectDir, "ai_config.json");
@@ -334,8 +307,9 @@ export class BVTRecorder {
334
307
  console.error("Error reading ai_config.json", error);
335
308
  }
336
309
  }
310
+ this.config = ai_config;
337
311
  const initScripts = {
338
- recorderCjs: injectedScriptSource,
312
+ // recorderCjs: injectedScriptSource,
339
313
  scripts: [
340
314
  this.getInitScripts(ai_config),
341
315
  `\ndelete Object.getPrototypeOf(navigator).webdriver;${process.env.WINDOW_DEBUGGER ? "window.debug=true;\n" : ""}`,
@@ -347,11 +321,40 @@ export class BVTRecorder {
347
321
  let stopTime = Date.now();
348
322
  this.logger.info(`Browser launched in ${(stopTime - startTime) / 1000} s`);
349
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
+ });
350
353
  const context = bvtContext.playContext;
351
354
  this.context = context;
352
355
  this.web = bvtContext.stable || bvtContext.web;
356
+ this.web.tryAllStrategies = true;
353
357
  this.page = bvtContext.page;
354
-
355
358
  this.pageSet.add(this.page);
356
359
  this.lastKnownUrlPath = this._updateUrlPath();
357
360
  const browser = await this.context.browser();
@@ -462,6 +465,75 @@ export class BVTRecorder {
462
465
  }
463
466
  });
464
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
+
465
537
  async getCurrentTransition() {
466
538
  if (this?.web?.browser?._name !== "chromium") {
467
539
  return;
@@ -470,29 +542,70 @@ export class BVTRecorder {
470
542
 
471
543
  try {
472
544
  const result = await client.send("Page.getNavigationHistory");
473
- // console.log("result", result);
545
+ // console.log("Navigation History:", result);
474
546
  const entries = result.entries;
475
547
  const currentIndex = result.currentIndex;
476
548
 
477
549
  // ignore if currentIndex is not the last entry
478
- if (currentIndex !== entries.length - 1) return;
550
+ // if (currentIndex !== entries.length - 1) return;
479
551
 
480
552
  const currentEntry = entries[currentIndex];
481
- 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
+ };
482
563
  } catch (error) {
483
- console.error("Error in getTransistionType event");
564
+ console.error("Error in getTransistionType event", error);
484
565
  } finally {
485
566
  await client.detach();
486
567
  }
487
568
  }
488
- userInitiatedTranistionTypes = ["typed", "address_bar"];
569
+ userInitiatedTransitionTypes = ["typed", "address_bar"];
489
570
  async handlePageTransition() {
490
571
  const transition = await this.getCurrentTransition();
491
572
  if (!transition) return;
492
- // console.log("transitionType", transition.transitionType);
493
- if (this.userInitiatedTranistionTypes.includes(transition.transitionType)) {
494
- // console.log("User initiated transition");
495
- this.sendEvent(this.events.onGoto, { url: transition.userTypedURL });
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;
496
609
  }
497
610
  }
498
611
 
@@ -621,6 +734,11 @@ export class BVTRecorder {
621
734
  delete process.env.TEMP_RUN;
622
735
  await this.watcher.close().then(() => {});
623
736
  this.watcher = null;
737
+ this.previousIndex = null;
738
+ this.previousHistoryLength = null;
739
+ this.previousUrl = null;
740
+ this.previousEntries = null;
741
+
624
742
  await closeContext();
625
743
  this.pageSet.clear();
626
744
  }
@@ -697,40 +815,61 @@ export class BVTRecorder {
697
815
  async abortExecution() {
698
816
  await this.stepRunner.abortExecution();
699
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
+
700
827
  async dealyedRevertMode() {
701
828
  const timerId = setTimeout(async () => {
702
829
  await this.revertMode();
703
830
  }, 100);
704
831
  this.timerId = timerId;
705
832
  }
706
- async runStep({ step, parametersMap }, options) {
833
+ async runStep({ step, parametersMap, tags, isFirstStep, listenNetwork }, options) {
834
+ const { skipAfter = true, skipBefore = !isFirstStep } = options || {};
707
835
  const _env = {
708
836
  TOKEN: this.TOKEN,
709
837
  TEMP_RUN: true,
710
838
  REPORT_FOLDER: this.bvtContext.reportFolder,
711
839
  BLINQ_ENV: this.envName,
840
+ STORE_DETAILED_NETWORK_DATA: listenNetwork ? "true" : "false",
841
+ CURRENT_STEP_ID: step.id,
712
842
  };
713
843
 
714
844
  this.bvtContext.navigate = true;
845
+ this.bvtContext.loadedRoutes = null;
715
846
  for (const [key, value] of Object.entries(_env)) {
716
847
  process.env[key] = value;
717
848
  }
849
+
718
850
  if (this.timerId) {
719
851
  clearTimeout(this.timerId);
720
852
  this.timerId = null;
721
853
  }
722
854
  await this.setMode("running");
855
+
723
856
  try {
724
- await this.stepRunner.runStep(
857
+ const { result, info } = await this.stepRunner.runStep(
725
858
  {
726
859
  step,
727
860
  parametersMap,
728
861
  envPath: this.envName,
862
+ tags,
863
+ config: this.config,
729
864
  },
730
865
  this.bvtContext,
731
- options
866
+ {
867
+ skipAfter,
868
+ skipBefore,
869
+ }
732
870
  );
733
871
  await this.revertMode();
872
+ return { info };
734
873
  } catch (error) {
735
874
  await this.revertMode();
736
875
  throw error;
@@ -741,15 +880,10 @@ export class BVTRecorder {
741
880
  this.bvtContext.navigate = false;
742
881
  }
743
882
  }
744
- async runScenario({ steps, parametersMap }) {
745
- for (const step of steps) {
746
- await this.runStep({ step, parametersMap });
747
- }
748
- }
749
883
  async saveScenario({ scenario, featureName, override, isSingleStep }) {
750
884
  await updateStepDefinitions({ scenario, featureName, projectDir: this.projectDir }); // updates mjs files
751
885
  if (!isSingleStep) await updateFeatureFile({ featureName, scenario, override, projectDir: this.projectDir }); // updates gherkin files
752
- await this.cleanup();
886
+ await this.cleanup({ tags: scenario.tags });
753
887
  }
754
888
  async getImplementedSteps() {
755
889
  const stepsAndScenarios = await getImplementedSteps(this.projectDir);
@@ -765,7 +899,15 @@ export class BVTRecorder {
765
899
  };
766
900
  }
767
901
  async getStepsAndCommandsForScenario({ name, featureName }) {
768
- 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;
769
911
  // return getStepsAndCommandsForScenario({
770
912
  // name,
771
913
  // featureName,
@@ -773,6 +915,7 @@ export class BVTRecorder {
773
915
  // map: this.scenariosStepsMap,
774
916
  // });
775
917
  }
918
+
776
919
  async generateStepName({ commands, stepsNames, parameters, map }) {
777
920
  return await this.namesService.generateStepName({ commands, stepsNames, parameters, map });
778
921
  }
@@ -855,9 +998,9 @@ export class BVTRecorder {
855
998
  }
856
999
  }
857
1000
 
858
- async discardTestData() {
1001
+ async discardTestData({ tags }) {
859
1002
  resetTestData(this.envName, this.world);
860
- await this.cleanup();
1003
+ await this.cleanup({ tags });
861
1004
  }
862
1005
  async addToTestData(obj) {
863
1006
  if (!existsSync(_getDataFile(this.world, this.bvtContext, this.web))) {
@@ -896,6 +1039,7 @@ export class BVTRecorder {
896
1039
  const stepParams = parseStepTextParameters(stepName);
897
1040
  return getCommandsForImplementedStep(stepName, step_definitions, stepParams).commands;
898
1041
  }
1042
+
899
1043
  loadExistingScenario({ featureName, scenarioName }) {
900
1044
  const step_definitions = loadStepDefinitions(this.projectDir);
901
1045
  const featureFilePath = path.join(this.projectDir, "features", featureName);
@@ -924,6 +1068,7 @@ export class BVTRecorder {
924
1068
  ..._s,
925
1069
  keyword: step.keyword.trim(),
926
1070
  };
1071
+ parseRouteFiles(this.projectDir, _step);
927
1072
  steps.push(_step);
928
1073
  }
929
1074
  return {
@@ -963,7 +1108,7 @@ export class BVTRecorder {
963
1108
  }
964
1109
  return result;
965
1110
  }
966
- async cleanup() {
1111
+ async cleanup({ tags }) {
967
1112
  const noopStep = {
968
1113
  text: "Noop",
969
1114
  isImplemented: true,
@@ -977,6 +1122,7 @@ export class BVTRecorder {
977
1122
  {
978
1123
  step: noopStep,
979
1124
  parametersMap: {},
1125
+ tags: tags || [],
980
1126
  },
981
1127
  {
982
1128
  skipAfter: false,
@@ -1015,6 +1161,47 @@ export class BVTRecorder {
1015
1161
  return false;
1016
1162
  }
1017
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
+ }
1018
1205
  }
1019
1206
 
1020
1207
  const parseFeatureFile = (featureFilePath) => {