@dev-blinq/cucumber_client 1.0.1168-dev → 1.0.1168-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 (47) hide show
  1. package/bin/assets/bundled_scripts/recorder.js +220 -0
  2. package/bin/assets/preload/accessibility.js +1 -1
  3. package/bin/assets/preload/find_context.js +1 -1
  4. package/bin/assets/preload/generateSelector.js +24 -0
  5. package/bin/assets/preload/locators.js +18 -0
  6. package/bin/assets/preload/recorderv3.js +85 -11
  7. package/bin/assets/preload/unique_locators.js +24 -3
  8. package/bin/assets/scripts/aria_snapshot.js +235 -0
  9. package/bin/assets/scripts/dom_attr.js +372 -0
  10. package/bin/assets/scripts/dom_element.js +0 -0
  11. package/bin/assets/scripts/dom_parent.js +185 -0
  12. package/bin/assets/scripts/event_utils.js +105 -0
  13. package/bin/assets/scripts/pw.js +7886 -0
  14. package/bin/assets/scripts/recorder.js +1147 -0
  15. package/bin/assets/scripts/snapshot_capturer.js +155 -0
  16. package/bin/assets/scripts/unique_locators.js +844 -0
  17. package/bin/assets/scripts/yaml.js +4770 -0
  18. package/bin/assets/templates/page_template.txt +2 -16
  19. package/bin/assets/templates/utils_template.txt +65 -12
  20. package/bin/client/cli_helpers.js +0 -1
  21. package/bin/client/code_cleanup/utils.js +43 -14
  22. package/bin/client/code_gen/code_inversion.js +112 -18
  23. package/bin/client/code_gen/index.js +3 -0
  24. package/bin/client/code_gen/page_reflection.js +37 -20
  25. package/bin/client/code_gen/playwright_codeget.js +152 -48
  26. package/bin/client/cucumber/feature.js +96 -42
  27. package/bin/client/cucumber/project_to_document.js +8 -7
  28. package/bin/client/cucumber/steps_definitions.js +59 -16
  29. package/bin/client/local_agent.js +9 -7
  30. package/bin/client/operations/dump_tree.js +159 -5
  31. package/bin/client/playground/playground.js +1 -1
  32. package/bin/client/project.js +6 -2
  33. package/bin/client/recorderv3/bvt_recorder.js +228 -79
  34. package/bin/client/recorderv3/cli.js +1 -0
  35. package/bin/client/recorderv3/implemented_steps.js +111 -11
  36. package/bin/client/recorderv3/index.js +45 -4
  37. package/bin/client/recorderv3/network.js +299 -0
  38. package/bin/client/recorderv3/step_runner.js +179 -13
  39. package/bin/client/recorderv3/step_utils.js +159 -14
  40. package/bin/client/recorderv3/update_feature.js +55 -30
  41. package/bin/client/recording.js +8 -0
  42. package/bin/client/run_cucumber.js +112 -4
  43. package/bin/client/scenario_report.js +112 -50
  44. package/bin/client/test_scenario.js +0 -1
  45. package/bin/index.js +1 -0
  46. package/package.json +14 -7
  47. package/bin/client/code_gen/get_implemented_steps.js +0 -27
@@ -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 } 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,22 @@ 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 NetworkMonitor from "./network.js";
19
+ import { Step } from "../cucumber/feature.js";
20
+
19
21
  const __dirname = path.dirname(url.fileURLToPath(import.meta.url));
20
- const { source: injectedScriptSource } = pkg;
22
+
21
23
  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
24
+ export function getInitScript(config, options) {
25
+ const preScript = `
26
+ window.__bvt_Recorder_config = ${JSON.stringify(config ?? null)};
27
+ window.__PW_options = ${JSON.stringify(options ?? null)};
28
+ `;
29
+ const recorderScript = readFileSync(
30
+ path.join(__dirname, "..", "..", "assets", "bundled_scripts", "recorder.js"),
31
+ "utf8"
53
32
  );
33
+ return preScript + recorderScript;
54
34
  }
55
35
 
56
36
  async function evaluate(frame, script) {
@@ -71,7 +51,7 @@ async function findNestedFrameSelector(frame, obj) {
71
51
  const frameElement = await frame.frameElement();
72
52
  if (!frameElement) return;
73
53
  const selectors = await parent.evaluate((element) => {
74
- return window.getPWLocators(element);
54
+ return window.__bvt_Recorder.locatorGenerator.getElementLocators(element, { excludeText: true }).locators;
75
55
  }, frameElement);
76
56
  return findNestedFrameSelector(parent, { children: obj, selectors });
77
57
  } catch (e) {
@@ -198,6 +178,7 @@ export class BVTRecorder {
198
178
  this.logger = logger;
199
179
  this.screenshotMap = new Map();
200
180
  this.snapshotMap = new Map();
181
+ this.scenariosStepsMap = new Map();
201
182
  this.namesService = new NamesService({
202
183
  screenshotMap: this.screenshotMap,
203
184
  TOKEN: this.TOKEN,
@@ -206,15 +187,38 @@ export class BVTRecorder {
206
187
  });
207
188
  this.stepRunner = new BVTStepRunner({
208
189
  projectDir: this.projectDir,
190
+ sendExecutionStatus: (data) => {
191
+ if (data && data.type) {
192
+ switch (data.type) {
193
+ case "cmdExecutionStart":
194
+ // console.log("Sending cmdExecutionStart event for cmdId:", data);
195
+ this.sendEvent(this.events.cmdExecutionStart, data);
196
+ break;
197
+ case "cmdExecutionSuccess":
198
+ // console.log("Sending cmdExecutionSuccess event for cmdId:", data);
199
+ this.sendEvent(this.events.cmdExecutionSuccess, data);
200
+ break;
201
+ case "cmdExecutionError":
202
+ // console.log("Sending cmdExecutionError event for cmdId:", data);
203
+ this.sendEvent(this.events.cmdExecutionError, data);
204
+ break;
205
+ case "interceptResults":
206
+ // console.log("Sending interceptResults event");
207
+ this.sendEvent(this.events.interceptResults, data);
208
+ break;
209
+ default:
210
+ console.warn("Unknown command execution status type:", data.type);
211
+ break;
212
+ }
213
+ }
214
+ },
209
215
  });
210
216
  this.pageSet = new Set();
211
-
212
217
  this.lastKnownUrlPath = "";
213
218
  // TODO: what is world?
214
219
  this.world = { attach: () => {} };
215
220
  this.shouldTakeScreenshot = true;
216
221
  this.watcher = null;
217
- this.sendEvent("BVTRecorder.getTestData", {});
218
222
  }
219
223
  events = {
220
224
  onFrameNavigate: "BVTRecorder.onFrameNavigate",
@@ -225,6 +229,10 @@ export class BVTRecorder {
225
229
  onStepDetails: "BVTRecorder.onStepDetails",
226
230
  getTestData: "BVTRecorder.getTestData",
227
231
  onGoto: "BVTRecorder.onGoto",
232
+ cmdExecutionStart: "BVTRecorder.cmdExecutionStart",
233
+ cmdExecutionSuccess: "BVTRecorder.cmdExecutionSuccess",
234
+ cmdExecutionError: "BVTRecorder.cmdExecutionError",
235
+ interceptResults: "BVTRecorder.interceptResults",
228
236
  };
229
237
  bindings = {
230
238
  __bvt_recordCommand: async ({ frame, page, context }, event) => {
@@ -300,22 +308,15 @@ export class BVTRecorder {
300
308
  return result;
301
309
  }
302
310
  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;
311
+ return getInitScript(config, {
312
+ sdkLanguage: "javascript",
313
+ testIdAttributeName: "blinq-test-id",
314
+ stableRafCount: 0,
315
+ browserName: this.browser?.browserType().name(),
316
+ inputFileRoleTextbox: false,
317
+ customEngines: [],
318
+ isUnderTest: true,
319
+ });
319
320
  }
320
321
 
321
322
  async _initBrowser({ url }) {
@@ -335,7 +336,7 @@ export class BVTRecorder {
335
336
  }
336
337
  }
337
338
  const initScripts = {
338
- recorderCjs: injectedScriptSource,
339
+ // recorderCjs: injectedScriptSource,
339
340
  scripts: [
340
341
  this.getInitScripts(ai_config),
341
342
  `\ndelete Object.getPrototypeOf(navigator).webdriver;${process.env.WINDOW_DEBUGGER ? "window.debug=true;\n" : ""}`,
@@ -462,6 +463,75 @@ export class BVTRecorder {
462
463
  }
463
464
  });
464
465
  }
466
+
467
+ hasHistoryReplacementAtIndex(previousEntries, currentEntries, index) {
468
+ if (!previousEntries || !currentEntries) return false;
469
+ if (index >= previousEntries.length || index >= currentEntries.length) return false;
470
+
471
+ const prevEntry = previousEntries[index];
472
+ // console.log("prevEntry", prevEntry);
473
+ const currEntry = currentEntries[index];
474
+ // console.log("currEntry", currEntry);
475
+
476
+ // Check if the entry at this index has been replaced
477
+ return prevEntry.id !== currEntry.id;
478
+ }
479
+
480
+ // Even simpler approach for your specific case
481
+ analyzeTransitionType(entries, currentIndex, currentEntry) {
482
+ // console.log("Analyzing transition type");
483
+ // console.log("===========================");
484
+ // console.log("Current Index:", currentIndex);
485
+ // console.log("Current Entry:", currentEntry);
486
+ // console.log("Current Entries:", entries);
487
+ // console.log("Current entries length:", entries.length);
488
+ // console.log("===========================");
489
+ // console.log("Previous Index:", this.previousIndex);
490
+ // // console.log("Previous Entry:", this.previousEntries[this.previousIndex]);
491
+ // console.log("Previous Entries:", this.previousEntries);
492
+ // console.log("Previous entries length:", this.previousHistoryLength);
493
+
494
+ if (this.previousIndex === null || this.previousHistoryLength === null || !this.previousEntries) {
495
+ return {
496
+ action: "initial",
497
+ };
498
+ }
499
+
500
+ const indexDiff = currentIndex - this.previousIndex;
501
+ const lengthDiff = entries.length - this.previousHistoryLength;
502
+
503
+ // Backward navigation
504
+ if (indexDiff < 0) {
505
+ return { action: "back" };
506
+ }
507
+
508
+ // Forward navigation
509
+ if (indexDiff > 0 && lengthDiff === 0) {
510
+ // Check if the entry at current index is the same as before
511
+ const entryReplaced = this.hasHistoryReplacementAtIndex(this.previousEntries, entries, currentIndex);
512
+
513
+ if (entryReplaced) {
514
+ return { action: "navigate" }; // New navigation that replaced forward history
515
+ } else {
516
+ return { action: "forward" }; // True forward navigation
517
+ }
518
+ }
519
+
520
+ // New navigation (history grew)
521
+ if (lengthDiff > 0) {
522
+ return { action: "navigate" };
523
+ }
524
+
525
+ // Same position, same length
526
+ if (lengthDiff <= 0) {
527
+ const entryReplaced = this.hasHistoryReplacementAtIndex(this.previousEntries, entries, currentIndex);
528
+
529
+ return entryReplaced ? { action: "navigate" } : { action: "reload" };
530
+ }
531
+
532
+ return { action: "unknown" };
533
+ }
534
+
465
535
  async getCurrentTransition() {
466
536
  if (this?.web?.browser?._name !== "chromium") {
467
537
  return;
@@ -470,31 +540,82 @@ export class BVTRecorder {
470
540
 
471
541
  try {
472
542
  const result = await client.send("Page.getNavigationHistory");
473
- // console.log("result", result);
543
+ // console.log("Navigation History:", result);
474
544
  const entries = result.entries;
475
545
  const currentIndex = result.currentIndex;
476
546
 
477
547
  // ignore if currentIndex is not the last entry
478
- if (currentIndex !== entries.length - 1) return;
548
+ // if (currentIndex !== entries.length - 1) return;
479
549
 
480
550
  const currentEntry = entries[currentIndex];
481
- return currentEntry;
551
+ const transitionInfo = this.analyzeTransitionType(entries, currentIndex, currentEntry);
552
+ this.previousIndex = currentIndex;
553
+ this.previousHistoryLength = entries.length;
554
+ this.previousUrl = currentEntry.url;
555
+ this.previousEntries = [...entries]; // Store a copy of current entries
556
+
557
+ return {
558
+ currentEntry,
559
+ navigationAction: transitionInfo.action,
560
+ };
482
561
  } catch (error) {
483
- console.error("Error in getTransistionType event");
562
+ console.error("Error in getTransistionType event", error);
484
563
  } finally {
485
564
  await client.detach();
486
565
  }
487
566
  }
488
- userInitiatedTranistionTypes = ["typed", "address_bar"];
567
+ userInitiatedTransitionTypes = ["typed", "address_bar"];
489
568
  async handlePageTransition() {
490
569
  const transition = await this.getCurrentTransition();
491
570
  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 });
571
+
572
+ const { currentEntry, navigationAction } = transition;
573
+
574
+ switch (navigationAction) {
575
+ case "initial":
576
+ // console.log("Initial navigation, no action taken");
577
+ return;
578
+ case "navigate":
579
+ // console.log("transitionType", transition.transitionType);
580
+ // console.log("sending onGoto event", { url: currentEntry.url,
581
+ // type: "navigate", });
582
+ if (this.userInitiatedTransitionTypes.includes(currentEntry.transitionType)) {
583
+ const env = JSON.parse(readFileSync(this.envName), "utf8");
584
+ const baseUrl = env.baseUrl;
585
+ let url = currentEntry.userTypedURL;
586
+ if (baseUrl && url.startsWith(baseUrl)) {
587
+ url = url.replace(baseUrl, "{{env.baseUrl}}");
588
+ }
589
+ // console.log("User initiated transition");
590
+ this.sendEvent(this.events.onGoto, { url, type: "navigate" });
591
+ }
592
+ return;
593
+ case "back":
594
+ // console.log("User navigated back");
595
+ // console.log("sending onGoto event", {
596
+ // type: "back",
597
+ // });
598
+ this.sendEvent(this.events.onGoto, { type: "back" });
599
+ return;
600
+ case "forward":
601
+ // console.log("User navigated forward"); console.log("sending onGoto event", { type: "forward", });
602
+ this.sendEvent(this.events.onGoto, { type: "forward" });
603
+ return;
604
+ default:
605
+ this.sendEvent(this.events.onGoto, { type: "unknown" });
606
+ return;
496
607
  }
497
608
  }
609
+
610
+ async getCurrentPageTitle() {
611
+ const title = await this.page.title();
612
+ return title;
613
+ }
614
+ async getCurrentPageUrl() {
615
+ const url = await this.page.url();
616
+ return url;
617
+ }
618
+
498
619
  _addPagelisteners(context) {
499
620
  context.on("page", async (page) => {
500
621
  try {
@@ -609,10 +730,13 @@ export class BVTRecorder {
609
730
  }
610
731
  async closeBrowser() {
611
732
  delete process.env.TEMP_RUN;
612
- await this.watcher.close().then(() => {
613
- this.logger.info(`Closed current testData file`);
614
- });
733
+ await this.watcher.close().then(() => {});
615
734
  this.watcher = null;
735
+ this.previousIndex = null;
736
+ this.previousHistoryLength = null;
737
+ this.previousUrl = null;
738
+ this.previousEntries = null;
739
+
616
740
  await closeContext();
617
741
  this.pageSet.clear();
618
742
  }
@@ -695,34 +819,41 @@ export class BVTRecorder {
695
819
  }, 100);
696
820
  this.timerId = timerId;
697
821
  }
698
- async runStep({ step, parametersMap }, options) {
822
+ async runStep({ step, parametersMap, tags, isFirstStep, listenNetwork }, options) {
699
823
  const _env = {
700
824
  TOKEN: this.TOKEN,
701
825
  TEMP_RUN: true,
702
826
  REPORT_FOLDER: this.bvtContext.reportFolder,
703
827
  BLINQ_ENV: this.envName,
828
+ STORE_DETAILED_NETWORK_DATA: listenNetwork ? "true" : "false",
829
+ CURRENT_STEP_ID: step.id,
704
830
  };
705
831
 
706
832
  this.bvtContext.navigate = true;
833
+ this.bvtContext.loadedRoutes = null;
707
834
  for (const [key, value] of Object.entries(_env)) {
708
835
  process.env[key] = value;
709
836
  }
837
+
710
838
  if (this.timerId) {
711
839
  clearTimeout(this.timerId);
712
840
  this.timerId = null;
713
841
  }
714
842
  await this.setMode("running");
843
+
715
844
  try {
716
- await this.stepRunner.runStep(
845
+ const { result, info } = await this.stepRunner.runStep(
717
846
  {
718
847
  step,
719
848
  parametersMap,
720
849
  envPath: this.envName,
850
+ tags,
721
851
  },
722
852
  this.bvtContext,
723
- options
853
+ options ? { ...options, skipBefore: !isFirstStep } : { skipBefore: !isFirstStep }
724
854
  );
725
855
  await this.revertMode();
856
+ return { info };
726
857
  } catch (error) {
727
858
  await this.revertMode();
728
859
  throw error;
@@ -733,19 +864,34 @@ export class BVTRecorder {
733
864
  this.bvtContext.navigate = false;
734
865
  }
735
866
  }
736
- async runScenario({ steps, parametersMap }) {
737
- for (const step of steps) {
738
- await this.runStep({ step, parametersMap });
739
- }
740
- }
741
867
  async saveScenario({ scenario, featureName, override, isSingleStep }) {
742
868
  await updateStepDefinitions({ scenario, featureName, projectDir: this.projectDir }); // updates mjs files
743
869
  if (!isSingleStep) await updateFeatureFile({ featureName, scenario, override, projectDir: this.projectDir }); // updates gherkin files
744
- await this.cleanup();
870
+ await this.cleanup({ tags: scenario.tags });
745
871
  }
746
872
  async getImplementedSteps() {
747
- return getImplementedSteps(this.projectDir);
873
+ const stepsAndScenarios = await getImplementedSteps(this.projectDir);
874
+ const implementedSteps = stepsAndScenarios.implementedSteps;
875
+ const scenarios = stepsAndScenarios.scenarios;
876
+ for (const scenario of scenarios) {
877
+ this.scenariosStepsMap.set(scenario.name, scenario.steps);
878
+ delete scenario.steps;
879
+ }
880
+ return {
881
+ implementedSteps,
882
+ scenarios,
883
+ };
748
884
  }
885
+ async getStepsAndCommandsForScenario({ name, featureName }) {
886
+ return this.scenariosStepsMap.get(name) || [];
887
+ // return getStepsAndCommandsForScenario({
888
+ // name,
889
+ // featureName,
890
+ // projectDir: this.projectDir,
891
+ // map: this.scenariosStepsMap,
892
+ // });
893
+ }
894
+
749
895
  async generateStepName({ commands, stepsNames, parameters, map }) {
750
896
  return await this.namesService.generateStepName({ commands, stepsNames, parameters, map });
751
897
  }
@@ -828,9 +974,9 @@ export class BVTRecorder {
828
974
  }
829
975
  }
830
976
 
831
- async discardTestData() {
977
+ async discardTestData({ tags }) {
832
978
  resetTestData(this.envName, this.world);
833
- await this.cleanup();
979
+ await this.cleanup({ tags });
834
980
  }
835
981
  async addToTestData(obj) {
836
982
  if (!existsSync(_getDataFile(this.world, this.bvtContext, this.web))) {
@@ -869,6 +1015,7 @@ export class BVTRecorder {
869
1015
  const stepParams = parseStepTextParameters(stepName);
870
1016
  return getCommandsForImplementedStep(stepName, step_definitions, stepParams).commands;
871
1017
  }
1018
+
872
1019
  loadExistingScenario({ featureName, scenarioName }) {
873
1020
  const step_definitions = loadStepDefinitions(this.projectDir);
874
1021
  const featureFilePath = path.join(this.projectDir, "features", featureName);
@@ -897,6 +1044,7 @@ export class BVTRecorder {
897
1044
  ..._s,
898
1045
  keyword: step.keyword.trim(),
899
1046
  };
1047
+ parseRouteFiles(this.projectDir, _step);
900
1048
  steps.push(_step);
901
1049
  }
902
1050
  return {
@@ -936,7 +1084,7 @@ export class BVTRecorder {
936
1084
  }
937
1085
  return result;
938
1086
  }
939
- async cleanup() {
1087
+ async cleanup({ tags }) {
940
1088
  const noopStep = {
941
1089
  text: "Noop",
942
1090
  isImplemented: true,
@@ -950,6 +1098,7 @@ export class BVTRecorder {
950
1098
  {
951
1099
  step: noopStep,
952
1100
  parametersMap: {},
1101
+ tags: tags || [],
953
1102
  },
954
1103
  {
955
1104
  skipAfter: false,
@@ -34,6 +34,7 @@ let recorder = null;
34
34
  const init = async ({ envName, projectDir, roomId }) => {
35
35
  console.log("connecting to " + WS_URL);
36
36
  const socket = io(WS_URL);
37
+ // Disconnect from the server when the process exits
37
38
  socket.on("connect", () => {
38
39
  // console.log('connected to server')
39
40
  });
@@ -1,17 +1,18 @@
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
+ import { getCommandsForImplementedStep, loadStepDefinitions } from "./step_utils.js";
9
+ import { parseStepTextParameters } from "../cucumber/utils.js";
8
10
  const __dirname = path.dirname(url.fileURLToPath(import.meta.url));
9
11
  let id = 0;
10
12
  const uuidFn = () => (++id).toString(16);
11
13
  const builder = new AstBuilder(uuidFn);
12
14
  const matcher = new GherkinClassicTokenMatcher();
13
15
  const parser = new Parser(builder, matcher);
14
-
15
16
  let i = 0;
16
17
  const getImplId = () => {
17
18
  return `I-${i++}`;
@@ -55,6 +56,53 @@ function memorySizeOf(obj) {
55
56
  return sizeOf(obj);
56
57
  }
57
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
+ paramKey: key,
95
+ paramValue: queryParams[key],
96
+ }));
97
+ filters.queryParams = queryParamsArray || [];
98
+ });
99
+ step.routeItems = routeItems;
100
+ } else {
101
+ step.routeItems = null;
102
+ }
103
+ }
104
+ }
105
+
58
106
  export const getImplementedSteps = async (projectDir) => {
59
107
  const foundErrors = [];
60
108
  try {
@@ -166,7 +214,10 @@ export const getImplementedSteps = async (projectDir) => {
166
214
  }
167
215
  stepLineSet.add(stepLine);
168
216
  step.templateIndex = implementedSteps.length;
169
- implementedSteps.push({
217
+
218
+ parseRouteFiles(projectDir, step);
219
+
220
+ const implementedStep = {
170
221
  keyword: step.keyword.trim(),
171
222
  keywordAlias: step.keywordAlias?.trim(),
172
223
  text: updateStepText(template.pattern, step.parameters),
@@ -177,7 +228,10 @@ export const getImplementedSteps = async (projectDir) => {
177
228
  templateIndex: step.templateIndex,
178
229
  pattern: template.pattern,
179
230
  paths: template.paths,
180
- });
231
+ routeItems: step.routeItems,
232
+ };
233
+
234
+ implementedSteps.push(implementedStep);
181
235
  }
182
236
  }
183
237
 
@@ -231,12 +285,19 @@ export const getImplementedSteps = async (projectDir) => {
231
285
  delete scenario.featureText;
232
286
  delete scenario.scenarioDocument;
233
287
  delete scenario.examples;
234
- const steps = scenario.steps;
235
- for (const step of steps) {
236
- const index = implementedSteps.findIndex((istep) => {
237
- return stepsDefinitions._stepNameToTemplate(step.text) === istep.pattern;
238
- });
239
- step.templateIndex = index;
288
+ for (const tag of scenario.tags) {
289
+ delete tag.location;
290
+ }
291
+ for (const scenario of scenarios) {
292
+ for (const step of scenario.steps) {
293
+ if (step.templateIndex === undefined) {
294
+ const cleanStepName = stepsDefinitions._stepNameToTemplate(step.text);
295
+ const index = implementedSteps.findIndex((istep) => {
296
+ return cleanStepName === istep.pattern;
297
+ });
298
+ step.templateIndex = index;
299
+ }
300
+ }
240
301
  }
241
302
  }
242
303
  if (foundErrors.length > 0) {
@@ -246,3 +307,42 @@ export const getImplementedSteps = async (projectDir) => {
246
307
  console.log("Size of scenarios", memorySizeOf(scenarios));
247
308
  return { implementedSteps, scenarios };
248
309
  };
310
+
311
+ export const getStepsAndCommandsForScenario = ({ name, featureName, projectDir, map }) => {
312
+ const stepsDefinitions = new StepsDefinitions(projectDir);
313
+ const step_definitions = loadStepDefinitions(projectDir);
314
+ stepsDefinitions.load();
315
+ const featureFilePath = path.join(projectDir, "features", featureName.trim() + ".feature");
316
+ if (!existsSync(featureFilePath)) {
317
+ throw new Error(`Feature file ${featureFilePath} not found`);
318
+ }
319
+ const content = readFileSync(featureFilePath, "utf8");
320
+ const doc = parser.parse(content);
321
+ const feature = new Feature(doc, content);
322
+ const scenario = feature.getScenario(name);
323
+ if (!scenario) {
324
+ throw new Error(`Scenario ${name} not found in feature ${featureName}`);
325
+ }
326
+
327
+ for (const step of scenario.steps) {
328
+ const stepName = step.text;
329
+ const stepParams = parseStepTextParameters(stepName);
330
+ step.commands = [];
331
+ try {
332
+ const stepDefinition = stepsDefinitions.findMatchingStep(stepName);
333
+ if (stepDefinition) {
334
+ step.isImplemented = true;
335
+ const commands = getCommandsForImplementedStep(stepName, step_definitions, stepParams).commands;
336
+ step.commands = commands;
337
+ } else {
338
+ step.isImplemented = false;
339
+ }
340
+ } catch (error) {
341
+ console.error(`Error getting step definition for ${stepName}`, error);
342
+ step.isImplemented = false;
343
+ }
344
+ }
345
+ console.log("Scenario steps size", memorySizeOf(scenario.steps));
346
+
347
+ return { steps: scenario.steps };
348
+ };