@dev-blinq/cucumber_client 1.0.1258-dev → 1.0.1258-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 (35) hide show
  1. package/bin/assets/bundled_scripts/recorder.js +159 -14068
  2. package/bin/assets/preload/recorderv3.js +3 -1
  3. package/bin/assets/scripts/dom_parent.js +26 -0
  4. package/bin/assets/scripts/recorder.js +8 -4
  5. package/bin/assets/scripts/unique_locators.js +847 -547
  6. package/bin/assets/templates/_hooks_template.txt +37 -0
  7. package/bin/assets/templates/page_template.txt +2 -16
  8. package/bin/assets/templates/utils_template.txt +44 -71
  9. package/bin/client/apiTest/apiTest.js +6 -0
  10. package/bin/client/cli_helpers.js +11 -13
  11. package/bin/client/code_cleanup/utils.js +5 -1
  12. package/bin/client/code_gen/code_inversion.js +61 -4
  13. package/bin/client/code_gen/page_reflection.js +10 -3
  14. package/bin/client/code_gen/playwright_codeget.js +52 -13
  15. package/bin/client/cucumber/feature.js +89 -27
  16. package/bin/client/cucumber/project_to_document.js +1 -1
  17. package/bin/client/cucumber/steps_definitions.js +84 -76
  18. package/bin/client/cucumber_selector.js +13 -1
  19. package/bin/client/local_agent.js +3 -3
  20. package/bin/client/project.js +7 -1
  21. package/bin/client/recorderv3/bvt_recorder.js +285 -80
  22. package/bin/client/recorderv3/implemented_steps.js +74 -16
  23. package/bin/client/recorderv3/index.js +47 -25
  24. package/bin/client/recorderv3/network.js +299 -0
  25. package/bin/client/recorderv3/services.js +4 -16
  26. package/bin/client/recorderv3/step_runner.js +326 -67
  27. package/bin/client/recorderv3/step_utils.js +152 -5
  28. package/bin/client/recorderv3/update_feature.js +66 -34
  29. package/bin/client/recording.js +3 -0
  30. package/bin/client/run_cucumber.js +5 -1
  31. package/bin/client/scenario_report.js +0 -5
  32. package/bin/client/test_scenario.js +0 -1
  33. package/bin/client/utils/socket_logger.js +132 -0
  34. package/bin/index.js +1 -0
  35. package/package.json +15 -12
@@ -3,7 +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 { getImplementedSteps, getStepsAndCommandsForScenario } from "./implemented_steps.js";
6
+ import { getImplementedSteps, parseRouteFiles } from "./implemented_steps.js";
7
7
  import { NamesService } from "./services.js";
8
8
  import { BVTStepRunner } from "./step_runner.js";
9
9
  import { readFile, writeFile } from "fs/promises";
@@ -12,10 +12,9 @@ import { updateFeatureFile } from "./update_feature.js";
12
12
  import { parseStepTextParameters } from "../cucumber/utils.js";
13
13
  import { AstBuilder, GherkinClassicTokenMatcher, Parser } from "@cucumber/gherkin";
14
14
  import chokidar from "chokidar";
15
- import logger from "../../logger.js";
16
15
  import { unEscapeNonPrintables } from "../cucumber/utils.js";
17
16
  import { findAvailablePort } from "../utils/index.js";
18
-
17
+ import socketLogger from "../utils/socket_logger.js";
19
18
  const __dirname = path.dirname(url.fileURLToPath(import.meta.url));
20
19
 
21
20
  const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
@@ -24,11 +23,11 @@ export function getInitScript(config, options) {
24
23
  window.__bvt_Recorder_config = ${JSON.stringify(config ?? null)};
25
24
  window.__PW_options = ${JSON.stringify(options ?? null)};
26
25
  `;
27
- const recorderScript = readFileSync(path.join(__dirname, "..", "..", "assets", "bundled_scripts", "recorder.js"), "utf8")
28
- return (
29
- preScript +
30
- recorderScript
26
+ const recorderScript = readFileSync(
27
+ path.join(__dirname, "..", "..", "assets", "bundled_scripts", "recorder.js"),
28
+ "utf8"
31
29
  );
30
+ return preScript + recorderScript;
32
31
  }
33
32
 
34
33
  async function evaluate(frame, script) {
@@ -44,15 +43,15 @@ async function evaluate(frame, script) {
44
43
  async function findNestedFrameSelector(frame, obj) {
45
44
  try {
46
45
  const parent = frame.parentFrame();
47
- if (parent) console.log(`Parent frame: ${JSON.stringify(parent)}`);
48
46
  if (!parent) return { children: obj };
49
47
  const frameElement = await frame.frameElement();
50
48
  if (!frameElement) return;
51
49
  const selectors = await parent.evaluate((element) => {
52
- return window.getPWLocators(element);
50
+ return window.__bvt_Recorder.locatorGenerator.getElementLocators(element, { excludeText: true }).locators;
53
51
  }, frameElement);
54
52
  return findNestedFrameSelector(parent, { children: obj, selectors });
55
53
  } catch (e) {
54
+ socketLogger.error(`Error in findNestedFrameSelector: ${e}`);
56
55
  console.error(e);
57
56
  }
58
57
  }
@@ -149,6 +148,7 @@ const transformAction = (action, el, isVerify, isPopupCloseClick, isInHoverMode,
149
148
  };
150
149
  }
151
150
  default: {
151
+ socketLogger.error(`Action not supported: ${action.name}`);
152
152
  console.log("action not supported", action);
153
153
  throw new Error("action not supported");
154
154
  }
@@ -160,6 +160,7 @@ const transformAction = (action, el, isVerify, isPopupCloseClick, isInHoverMode,
160
160
  * @property {string} projectDir
161
161
  * @property {string} TOKEN
162
162
  * @property {(name:string, data:any)=> void} sendEvent
163
+ * @property {Object} logger
163
164
  */
164
165
  export class BVTRecorder {
165
166
  #currentURL = "";
@@ -173,7 +174,6 @@ export class BVTRecorder {
173
174
  */
174
175
  constructor(initialState) {
175
176
  Object.assign(this, initialState);
176
- this.logger = logger;
177
177
  this.screenshotMap = new Map();
178
178
  this.snapshotMap = new Map();
179
179
  this.scenariosStepsMap = new Map();
@@ -183,14 +183,10 @@ export class BVTRecorder {
183
183
  projectDir: this.projectDir,
184
184
  logger: this.logger,
185
185
  });
186
- this.stepRunner = new BVTStepRunner({
187
- projectDir: this.projectDir,
188
- });
189
186
  this.pageSet = new Set();
190
-
187
+ this.pageMetaDataSet = new Set();
191
188
  this.lastKnownUrlPath = "";
192
- // TODO: what is world?
193
- this.world = { attach: () => { } };
189
+ this.world = { attach: () => {} };
194
190
  this.shouldTakeScreenshot = true;
195
191
  this.watcher = null;
196
192
  }
@@ -203,12 +199,16 @@ export class BVTRecorder {
203
199
  onStepDetails: "BVTRecorder.onStepDetails",
204
200
  getTestData: "BVTRecorder.getTestData",
205
201
  onGoto: "BVTRecorder.onGoto",
202
+ cmdExecutionStart: "BVTRecorder.cmdExecutionStart",
203
+ cmdExecutionSuccess: "BVTRecorder.cmdExecutionSuccess",
204
+ cmdExecutionError: "BVTRecorder.cmdExecutionError",
205
+ interceptResults: "BVTRecorder.interceptResults",
206
206
  };
207
207
  bindings = {
208
208
  __bvt_recordCommand: async ({ frame, page, context }, event) => {
209
209
  this.#activeFrame = frame;
210
210
  const nestFrmLoc = await findNestedFrameSelector(frame);
211
- console.log(`Time taken for action: ${event.statistics.time}`);
211
+ this.logger.info(`Time taken for action: ${event.statistics.time}`);
212
212
  await this.onAction({ ...event, nestFrmLoc });
213
213
  },
214
214
  __bvt_getMode: async () => {
@@ -227,8 +227,7 @@ export class BVTRecorder {
227
227
  await this.onClosePopup();
228
228
  },
229
229
  __bvt_log: async (src, message) => {
230
- // this.logger.info(message);
231
- console.log(`Inside Browser: ${message}`);
230
+ this.logger.info(`Inside Browser: ${message}`);
232
231
  },
233
232
  __bvt_getObject: (_src, obj) => {
234
233
  this.processObject(obj);
@@ -293,8 +292,8 @@ export class BVTRecorder {
293
292
  this.#remoteDebuggerPort = await findAvailablePort();
294
293
  process.env.CDP_LISTEN_PORT = this.#remoteDebuggerPort;
295
294
 
296
- this.stepRunner.setRemoteDebugPort(this.#remoteDebuggerPort);
297
- this.world = { attach: () => { } };
295
+ // this.stepRunner.setRemoteDebugPort(this.#remoteDebuggerPort);
296
+ this.world = { attach: () => {} };
298
297
 
299
298
  const ai_config_file = path.join(this.projectDir, "ai_config.json");
300
299
  let ai_config = {};
@@ -302,9 +301,10 @@ export class BVTRecorder {
302
301
  try {
303
302
  ai_config = JSON.parse(readFileSync(ai_config_file, "utf8"));
304
303
  } catch (error) {
305
- console.error("Error reading ai_config.json", error);
304
+ this.logger.error("Error reading ai_config.json", error);
306
305
  }
307
306
  }
307
+ this.config = ai_config;
308
308
  const initScripts = {
309
309
  // recorderCjs: injectedScriptSource,
310
310
  scripts: [
@@ -313,16 +313,37 @@ export class BVTRecorder {
313
313
  ],
314
314
  };
315
315
 
316
- let startTime = Date.now();
317
316
  const bvtContext = await initContext(url, false, false, this.world, 450, initScripts, this.envName);
318
- let stopTime = Date.now();
319
- this.logger.info(`Browser launched in ${(stopTime - startTime) / 1000} s`);
320
317
  this.bvtContext = bvtContext;
318
+ this.stepRunner = new BVTStepRunner({
319
+ projectDir: this.projectDir,
320
+ sendExecutionStatus: (data) => {
321
+ if (data && data.type) {
322
+ switch (data.type) {
323
+ case "cmdExecutionStart":
324
+ this.sendEvent(this.events.cmdExecutionStart, data);
325
+ break;
326
+ case "cmdExecutionSuccess":
327
+ this.sendEvent(this.events.cmdExecutionSuccess, data);
328
+ break;
329
+ case "cmdExecutionError":
330
+ this.sendEvent(this.events.cmdExecutionError, data);
331
+ break;
332
+ case "interceptResults":
333
+ this.sendEvent(this.events.interceptResults, data);
334
+ break;
335
+ default:
336
+ break;
337
+ }
338
+ }
339
+ },
340
+ bvtContext: this.bvtContext,
341
+ });
321
342
  const context = bvtContext.playContext;
322
343
  this.context = context;
323
344
  this.web = bvtContext.stable || bvtContext.web;
345
+ this.web.tryAllStrategies = true;
324
346
  this.page = bvtContext.page;
325
-
326
347
  this.pageSet.add(this.page);
327
348
  this.lastKnownUrlPath = this._updateUrlPath();
328
349
  const browser = await this.context.browser();
@@ -350,13 +371,15 @@ export class BVTRecorder {
350
371
  }
351
372
  return;
352
373
  } catch (error) {
353
- console.error("Error evaluting in context:", error);
374
+ // console.error("Error evaluting in context:", error);
375
+ this.logger.error("Error evaluating in context:", error);
354
376
  }
355
377
  }
356
378
  }
357
379
 
358
380
  getMode() {
359
- console.log("getMode", this.#mode);
381
+ // console.log("getMode", this.#mode);
382
+ this.logger.info("Current mode:", this.#mode);
360
383
  return this.#mode;
361
384
  }
362
385
 
@@ -398,6 +421,8 @@ export class BVTRecorder {
398
421
  this.sendEvent(this.events.onBrowserClose);
399
422
  }
400
423
  } catch (error) {
424
+ this.logger.error("Error in page close event");
425
+ this.logger.error(error);
401
426
  console.error("Error in page close event");
402
427
  console.error(error);
403
428
  }
@@ -408,8 +433,10 @@ export class BVTRecorder {
408
433
  if (frame !== page.mainFrame()) return;
409
434
  this.handlePageTransition();
410
435
  } catch (error) {
436
+ this.logger.error("Error in handlePageTransition event");
437
+ this.logger.error(error);
411
438
  console.error("Error in handlePageTransition event");
412
- // console.error(error);
439
+ console.error(error);
413
440
  }
414
441
  try {
415
442
  if (frame !== this.#activeFrame) return;
@@ -428,11 +455,82 @@ export class BVTRecorder {
428
455
  // await this._setRecordingMode(frame);
429
456
  // await this._initPage(page);
430
457
  } catch (error) {
458
+ this.logger.error("Error in frame navigate event");
459
+ this.logger.error(error);
431
460
  console.error("Error in frame navigate event");
432
461
  // console.error(error);
433
462
  }
434
463
  });
435
464
  }
465
+
466
+ hasHistoryReplacementAtIndex(previousEntries, currentEntries, index) {
467
+ if (!previousEntries || !currentEntries) return false;
468
+ if (index >= previousEntries.length || index >= currentEntries.length) return false;
469
+
470
+ const prevEntry = previousEntries[index];
471
+ // console.log("prevEntry", prevEntry);
472
+ const currEntry = currentEntries[index];
473
+ // console.log("currEntry", currEntry);
474
+
475
+ // Check if the entry at this index has been replaced
476
+ return prevEntry.id !== currEntry.id;
477
+ }
478
+
479
+ // Even simpler approach for your specific case
480
+ analyzeTransitionType(entries, currentIndex, currentEntry) {
481
+ // console.log("Analyzing transition type");
482
+ // console.log("===========================");
483
+ // console.log("Current Index:", currentIndex);
484
+ // console.log("Current Entry:", currentEntry);
485
+ // console.log("Current Entries:", entries);
486
+ // console.log("Current entries length:", entries.length);
487
+ // console.log("===========================");
488
+ // console.log("Previous Index:", this.previousIndex);
489
+ // // console.log("Previous Entry:", this.previousEntries[this.previousIndex]);
490
+ // console.log("Previous Entries:", this.previousEntries);
491
+ // console.log("Previous entries length:", this.previousHistoryLength);
492
+
493
+ if (this.previousIndex === null || this.previousHistoryLength === null || !this.previousEntries) {
494
+ return {
495
+ action: "initial",
496
+ };
497
+ }
498
+
499
+ const indexDiff = currentIndex - this.previousIndex;
500
+ const lengthDiff = entries.length - this.previousHistoryLength;
501
+
502
+ // Backward navigation
503
+ if (indexDiff < 0) {
504
+ return { action: "back" };
505
+ }
506
+
507
+ // Forward navigation
508
+ if (indexDiff > 0 && lengthDiff === 0) {
509
+ // Check if the entry at current index is the same as before
510
+ const entryReplaced = this.hasHistoryReplacementAtIndex(this.previousEntries, entries, currentIndex);
511
+
512
+ if (entryReplaced) {
513
+ return { action: "navigate" }; // New navigation that replaced forward history
514
+ } else {
515
+ return { action: "forward" }; // True forward navigation
516
+ }
517
+ }
518
+
519
+ // New navigation (history grew)
520
+ if (lengthDiff > 0) {
521
+ return { action: "navigate" };
522
+ }
523
+
524
+ // Same position, same length
525
+ if (lengthDiff <= 0) {
526
+ const entryReplaced = this.hasHistoryReplacementAtIndex(this.previousEntries, entries, currentIndex);
527
+
528
+ return entryReplaced ? { action: "navigate" } : { action: "reload" };
529
+ }
530
+
531
+ return { action: "unknown" };
532
+ }
533
+
436
534
  async getCurrentTransition() {
437
535
  if (this?.web?.browser?._name !== "chromium") {
438
536
  return;
@@ -441,35 +539,68 @@ export class BVTRecorder {
441
539
 
442
540
  try {
443
541
  const result = await client.send("Page.getNavigationHistory");
444
- // console.log("result", result);
445
542
  const entries = result.entries;
446
543
  const currentIndex = result.currentIndex;
447
544
 
448
- // ignore if currentIndex is not the last entry
449
- if (currentIndex !== entries.length - 1) return;
450
-
451
545
  const currentEntry = entries[currentIndex];
452
- return currentEntry;
546
+ const transitionInfo = this.analyzeTransitionType(entries, currentIndex, currentEntry);
547
+ this.previousIndex = currentIndex;
548
+ this.previousHistoryLength = entries.length;
549
+ this.previousUrl = currentEntry.url;
550
+ this.previousEntries = [...entries]; // Store a copy of current entries
551
+
552
+ return {
553
+ currentEntry,
554
+ navigationAction: transitionInfo.action,
555
+ };
453
556
  } catch (error) {
454
- console.error("Error in getTransistionType event");
557
+ this.logger.error("Error in getCurrentTransition event");
558
+ this.logger.error(error);
559
+ console.error("Error in getTransistionType event", error);
455
560
  } finally {
456
561
  await client.detach();
457
562
  }
458
563
  }
459
- userInitiatedTranistionTypes = ["typed", "address_bar"];
564
+ userInitiatedTransitionTypes = ["typed", "address_bar"];
460
565
  async handlePageTransition() {
461
566
  const transition = await this.getCurrentTransition();
462
567
  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 });
568
+
569
+ const { currentEntry, navigationAction } = transition;
570
+
571
+ switch (navigationAction) {
572
+ case "initial":
573
+ // console.log("Initial navigation, no action taken");
574
+ return;
575
+ case "navigate":
576
+ // console.log("transitionType", transition.transitionType);
577
+ // console.log("sending onGoto event", { url: currentEntry.url,
578
+ // type: "navigate", });
579
+ if (this.userInitiatedTransitionTypes.includes(currentEntry.transitionType)) {
580
+ const env = JSON.parse(readFileSync(this.envName), "utf8");
581
+ const baseUrl = env.baseUrl;
582
+ let url = currentEntry.userTypedURL;
583
+ if (baseUrl && url.startsWith(baseUrl)) {
584
+ url = url.replace(baseUrl, "{{env.baseUrl}}");
585
+ }
586
+ // console.log("User initiated transition");
587
+ this.sendEvent(this.events.onGoto, { url, type: "navigate" });
588
+ }
589
+ return;
590
+ case "back":
591
+ // console.log("User navigated back");
592
+ // console.log("sending onGoto event", {
593
+ // type: "back",
594
+ // });
595
+ this.sendEvent(this.events.onGoto, { type: "back" });
596
+ return;
597
+ case "forward":
598
+ // console.log("User navigated forward"); console.log("sending onGoto event", { type: "forward", });
599
+ this.sendEvent(this.events.onGoto, { type: "forward" });
600
+ return;
601
+ default:
602
+ this.sendEvent(this.events.onGoto, { type: "unknown" });
603
+ return;
473
604
  }
474
605
  }
475
606
 
@@ -493,6 +624,8 @@ export class BVTRecorder {
493
624
  // add listener for frame navigation on new tab
494
625
  this._addFrameNavigateListener(page);
495
626
  } catch (error) {
627
+ this.logger.error("Error in page event");
628
+ this.logger.error(error);
496
629
  console.error("Error in page event");
497
630
  console.error(error);
498
631
  }
@@ -534,6 +667,7 @@ export class BVTRecorder {
534
667
  const { data } = await client.send("Page.captureScreenshot", { format: "png" });
535
668
  return data;
536
669
  } catch (error) {
670
+ this.logger.error("Error in taking browser screenshot");
537
671
  console.error("Error in taking browser screenshot", error);
538
672
  } finally {
539
673
  await client.detach();
@@ -596,8 +730,13 @@ export class BVTRecorder {
596
730
  }
597
731
  async closeBrowser() {
598
732
  delete process.env.TEMP_RUN;
599
- await this.watcher.close().then(() => { });
733
+ await this.watcher.close().then(() => {});
600
734
  this.watcher = null;
735
+ this.previousIndex = null;
736
+ this.previousHistoryLength = null;
737
+ this.previousUrl = null;
738
+ this.previousEntries = null;
739
+
601
740
  await closeContext();
602
741
  this.pageSet.clear();
603
742
  }
@@ -674,40 +813,61 @@ export class BVTRecorder {
674
813
  async abortExecution() {
675
814
  await this.stepRunner.abortExecution();
676
815
  }
816
+
817
+ async pauseExecution({ cmdId }) {
818
+ await this.stepRunner.pauseExecution(cmdId);
819
+ }
820
+
821
+ async resumeExecution({ cmdId }) {
822
+ await this.stepRunner.resumeExecution(cmdId);
823
+ }
824
+
677
825
  async dealyedRevertMode() {
678
826
  const timerId = setTimeout(async () => {
679
827
  await this.revertMode();
680
828
  }, 100);
681
829
  this.timerId = timerId;
682
830
  }
683
- async runStep({ step, parametersMap }, options) {
831
+ async runStep({ step, parametersMap, tags, isFirstStep, listenNetwork }, options) {
832
+ const { skipAfter = true, skipBefore = !isFirstStep } = options || {};
684
833
  const _env = {
685
834
  TOKEN: this.TOKEN,
686
835
  TEMP_RUN: true,
687
836
  REPORT_FOLDER: this.bvtContext.reportFolder,
688
837
  BLINQ_ENV: this.envName,
838
+ STORE_DETAILED_NETWORK_DATA: listenNetwork ? "true" : "false",
839
+ DEBUG: "blinq:route",
689
840
  };
690
841
 
691
842
  this.bvtContext.navigate = true;
843
+ this.bvtContext.loadedRoutes = null;
692
844
  for (const [key, value] of Object.entries(_env)) {
693
845
  process.env[key] = value;
694
846
  }
847
+
695
848
  if (this.timerId) {
696
849
  clearTimeout(this.timerId);
697
850
  this.timerId = null;
698
851
  }
699
852
  await this.setMode("running");
853
+
700
854
  try {
701
- await this.stepRunner.runStep(
855
+ const { result, info } = await this.stepRunner.runStep(
702
856
  {
703
857
  step,
704
858
  parametersMap,
705
859
  envPath: this.envName,
860
+ tags,
861
+ config: this.config,
706
862
  },
707
863
  this.bvtContext,
708
- options
864
+ {
865
+ skipAfter,
866
+ skipBefore,
867
+ }
709
868
  );
710
869
  await this.revertMode();
870
+ return { info };
711
871
  } catch (error) {
712
872
  await this.revertMode();
713
873
  throw error;
@@ -718,15 +878,10 @@ export class BVTRecorder {
718
878
  this.bvtContext.navigate = false;
719
879
  }
720
880
  }
721
- async runScenario({ steps, parametersMap }) {
722
- for (const step of steps) {
723
- await this.runStep({ step, parametersMap });
724
- }
725
- }
726
881
  async saveScenario({ scenario, featureName, override, isSingleStep }) {
727
882
  await updateStepDefinitions({ scenario, featureName, projectDir: this.projectDir }); // updates mjs files
728
883
  if (!isSingleStep) await updateFeatureFile({ featureName, scenario, override, projectDir: this.projectDir }); // updates gherkin files
729
- await this.cleanup();
884
+ await this.cleanup({ tags: scenario.tags });
730
885
  }
731
886
  async getImplementedSteps() {
732
887
  const stepsAndScenarios = await getImplementedSteps(this.projectDir);
@@ -750,6 +905,7 @@ export class BVTRecorder {
750
905
  step.commands = [];
751
906
  }
752
907
  }
908
+ return steps;
753
909
  // return getStepsAndCommandsForScenario({
754
910
  // name,
755
911
  // featureName,
@@ -757,6 +913,7 @@ export class BVTRecorder {
757
913
  // map: this.scenariosStepsMap,
758
914
  // });
759
915
  }
916
+
760
917
  async generateStepName({ commands, stepsNames, parameters, map }) {
761
918
  return await this.namesService.generateStepName({ commands, stepsNames, parameters, map });
762
919
  }
@@ -808,10 +965,11 @@ export class BVTRecorder {
808
965
  if (existsSync(_getDataFile(this.world, this.bvtContext, this.web))) {
809
966
  try {
810
967
  const testData = JSON.parse(readFileSync(_getDataFile(this.world, this.bvtContext, this.web), "utf8"));
811
- this.logger.info("Test data", testData);
968
+ // this.logger.info("Test data", testData);
812
969
  this.sendEvent(this.events.getTestData, testData);
813
970
  } catch (e) {
814
- this.logger.error("Error reading test data file", e);
971
+ // this.logger.error("Error reading test data file", e);
972
+ console.log("Error reading test data file", e);
815
973
  }
816
974
  }
817
975
 
@@ -820,10 +978,12 @@ export class BVTRecorder {
820
978
  this.watcher.on("all", async (event, path) => {
821
979
  try {
822
980
  const testData = JSON.parse(await readFile(_getDataFile(this.world, this.bvtContext, this.web), "utf8"));
823
- this.logger.info("Test data", testData);
981
+ // this.logger.info("Test data", testData);
982
+ console.log("Test data changed", testData);
824
983
  this.sendEvent(this.events.getTestData, testData);
825
984
  } catch (e) {
826
- this.logger.error("Error reading test data file", e);
985
+ // this.logger.error("Error reading test data file", e);
986
+ console.log("Error reading test data file", e);
827
987
  }
828
988
  });
829
989
  }
@@ -839,9 +999,9 @@ export class BVTRecorder {
839
999
  }
840
1000
  }
841
1001
 
842
- async discardTestData() {
1002
+ async discardTestData({ tags }) {
843
1003
  resetTestData(this.envName, this.world);
844
- await this.cleanup();
1004
+ await this.cleanup({ tags });
845
1005
  }
846
1006
  async addToTestData(obj) {
847
1007
  if (!existsSync(_getDataFile(this.world, this.bvtContext, this.web))) {
@@ -856,7 +1016,7 @@ export class BVTRecorder {
856
1016
  .filter((file) => file.endsWith(".feature"))
857
1017
  .map((file) => path.join(this.projectDir, "features", file));
858
1018
  try {
859
- const parsedFiles = featureFiles.map((file) => parseFeatureFile(file));
1019
+ const parsedFiles = featureFiles.map((file) => this.parseFeatureFile(file));
860
1020
  const output = {};
861
1021
  parsedFiles.forEach((file) => {
862
1022
  if (!file.feature) return;
@@ -880,10 +1040,11 @@ export class BVTRecorder {
880
1040
  const stepParams = parseStepTextParameters(stepName);
881
1041
  return getCommandsForImplementedStep(stepName, step_definitions, stepParams).commands;
882
1042
  }
1043
+
883
1044
  loadExistingScenario({ featureName, scenarioName }) {
884
1045
  const step_definitions = loadStepDefinitions(this.projectDir);
885
1046
  const featureFilePath = path.join(this.projectDir, "features", featureName);
886
- const gherkinDoc = parseFeatureFile(featureFilePath);
1047
+ const gherkinDoc = this.parseFeatureFile(featureFilePath);
887
1048
  const scenario = gherkinDoc.feature.children.find((child) => child.scenario.name === scenarioName)?.scenario;
888
1049
 
889
1050
  const steps = [];
@@ -908,6 +1069,7 @@ export class BVTRecorder {
908
1069
  ..._s,
909
1070
  keyword: step.keyword.trim(),
910
1071
  };
1072
+ parseRouteFiles(this.projectDir, _step);
911
1073
  steps.push(_step);
912
1074
  }
913
1075
  return {
@@ -947,7 +1109,7 @@ export class BVTRecorder {
947
1109
  }
948
1110
  return result;
949
1111
  }
950
- async cleanup() {
1112
+ async cleanup({ tags }) {
951
1113
  const noopStep = {
952
1114
  text: "Noop",
953
1115
  isImplemented: true,
@@ -961,6 +1123,7 @@ export class BVTRecorder {
961
1123
  {
962
1124
  step: noopStep,
963
1125
  parametersMap: {},
1126
+ tags: tags || [],
964
1127
  },
965
1128
  {
966
1129
  skipAfter: false,
@@ -999,20 +1162,62 @@ export class BVTRecorder {
999
1162
  return false;
1000
1163
  }
1001
1164
  }
1002
- }
1165
+ async initExecution({ tags = [] }) {
1166
+ // run before hooks
1167
+ const noopStep = {
1168
+ text: "Noop",
1169
+ isImplemented: true,
1170
+ };
1171
+ await this.runStep(
1172
+ {
1173
+ step: noopStep,
1174
+ parametersMap: {},
1175
+ tags,
1176
+ },
1177
+ {
1178
+ skipBefore: false,
1179
+ skipAfter: true,
1180
+ }
1181
+ );
1182
+ }
1183
+ async cleanupExecution({ tags = [] }) {
1184
+ // run after hooks
1185
+ const noopStep = {
1186
+ text: "Noop",
1187
+ isImplemented: true,
1188
+ };
1189
+ await this.runStep(
1190
+ {
1191
+ step: noopStep,
1192
+ parametersMap: {},
1193
+ tags,
1194
+ },
1195
+ {
1196
+ skipBefore: true,
1197
+ skipAfter: false,
1198
+ }
1199
+ );
1200
+ }
1201
+ async resetExecution({ tags = [] }) {
1202
+ // run after hooks followed by before hooks
1203
+ await this.cleanupExecution({ tags });
1204
+ await this.initExecution({ tags });
1205
+ }
1003
1206
 
1004
- const parseFeatureFile = (featureFilePath) => {
1005
- try {
1006
- let id = 0;
1007
- const uuidFn = () => (++id).toString(16);
1008
- const builder = new AstBuilder(uuidFn);
1009
- const matcher = new GherkinClassicTokenMatcher();
1010
- const parser = new Parser(builder, matcher);
1011
- const source = readFileSync(featureFilePath, "utf8");
1012
- const gherkinDocument = parser.parse(source);
1013
- return gherkinDocument;
1014
- } catch (e) {
1015
- console.log(e);
1207
+ parseFeatureFile(featureFilePath) {
1208
+ try {
1209
+ let id = 0;
1210
+ const uuidFn = () => (++id).toString(16);
1211
+ const builder = new AstBuilder(uuidFn);
1212
+ const matcher = new GherkinClassicTokenMatcher();
1213
+ const parser = new Parser(builder, matcher);
1214
+ const source = readFileSync(featureFilePath, "utf8");
1215
+ const gherkinDocument = parser.parse(source);
1216
+ return gherkinDocument;
1217
+ } catch (e) {
1218
+ this.logger.error(`Error parsing feature file: ${featureFilePath}`);
1219
+ console.log(e);
1220
+ }
1221
+ return {};
1016
1222
  }
1017
- return {};
1018
- };
1223
+ }