@dev-blinq/cucumber_client 1.0.1267-dev → 1.0.1267-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 (38) hide show
  1. package/bin/assets/bundled_scripts/recorder.js +159 -14224
  2. package/bin/assets/preload/recorderv3.js +3 -1
  3. package/bin/assets/scripts/dom_parent.js +4 -0
  4. package/bin/assets/scripts/recorder.js +8 -4
  5. package/bin/assets/scripts/unique_locators.js +847 -693
  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 +1 -46
  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 +53 -4
  13. package/bin/client/code_gen/page_reflection.js +838 -899
  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 +17 -1
  19. package/bin/client/local_agent.js +5 -4
  20. package/bin/client/project.js +186 -196
  21. package/bin/client/recorderv3/bvt_recorder.js +160 -61
  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 +331 -67
  27. package/bin/client/recorderv3/step_utils.js +155 -5
  28. package/bin/client/recorderv3/update_feature.js +32 -30
  29. package/bin/client/recording.js +1 -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/bin/logger.js +3 -2
  36. package/bin/min/consoleApi.min.cjs +2 -3
  37. package/bin/min/injectedScript.min.cjs +16 -16
  38. package/package.json +24 -14
@@ -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));
@@ -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,13 +183,9 @@ 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
189
  this.world = { attach: () => {} };
194
190
  this.shouldTakeScreenshot = true;
195
191
  this.watcher = null;
@@ -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,7 +292,7 @@ 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);
295
+ // this.stepRunner.setRemoteDebugPort(this.#remoteDebuggerPort);
297
296
  this.world = { attach: () => {} };
298
297
 
299
298
  const ai_config_file = path.join(this.projectDir, "ai_config.json");
@@ -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,6 +455,8 @@ 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
  }
@@ -510,13 +539,9 @@ export class BVTRecorder {
510
539
 
511
540
  try {
512
541
  const result = await client.send("Page.getNavigationHistory");
513
- // console.log("Navigation History:", result);
514
542
  const entries = result.entries;
515
543
  const currentIndex = result.currentIndex;
516
544
 
517
- // ignore if currentIndex is not the last entry
518
- // if (currentIndex !== entries.length - 1) return;
519
-
520
545
  const currentEntry = entries[currentIndex];
521
546
  const transitionInfo = this.analyzeTransitionType(entries, currentIndex, currentEntry);
522
547
  this.previousIndex = currentIndex;
@@ -529,6 +554,8 @@ export class BVTRecorder {
529
554
  navigationAction: transitionInfo.action,
530
555
  };
531
556
  } catch (error) {
557
+ this.logger.error("Error in getCurrentTransition event");
558
+ this.logger.error(error);
532
559
  console.error("Error in getTransistionType event", error);
533
560
  } finally {
534
561
  await client.detach();
@@ -597,6 +624,8 @@ export class BVTRecorder {
597
624
  // add listener for frame navigation on new tab
598
625
  this._addFrameNavigateListener(page);
599
626
  } catch (error) {
627
+ this.logger.error("Error in page event");
628
+ this.logger.error(error);
600
629
  console.error("Error in page event");
601
630
  console.error(error);
602
631
  }
@@ -638,6 +667,7 @@ export class BVTRecorder {
638
667
  const { data } = await client.send("Page.captureScreenshot", { format: "png" });
639
668
  return data;
640
669
  } catch (error) {
670
+ this.logger.error("Error in taking browser screenshot");
641
671
  console.error("Error in taking browser screenshot", error);
642
672
  } finally {
643
673
  await client.detach();
@@ -701,7 +731,6 @@ export class BVTRecorder {
701
731
  async closeBrowser() {
702
732
  delete process.env.TEMP_RUN;
703
733
  await this.watcher.close().then(() => {});
704
-
705
734
  this.watcher = null;
706
735
  this.previousIndex = null;
707
736
  this.previousHistoryLength = null;
@@ -784,40 +813,65 @@ export class BVTRecorder {
784
813
  async abortExecution() {
785
814
  await this.stepRunner.abortExecution();
786
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
+
787
825
  async dealyedRevertMode() {
788
826
  const timerId = setTimeout(async () => {
789
827
  await this.revertMode();
790
828
  }, 100);
791
829
  this.timerId = timerId;
792
830
  }
793
- async runStep({ step, parametersMap }, options) {
831
+ async runStep({ step, parametersMap, tags, isFirstStep, listenNetwork }, options) {
832
+ const { skipAfter = true, skipBefore = !isFirstStep } = options || {};
794
833
  const _env = {
795
834
  TOKEN: this.TOKEN,
796
835
  TEMP_RUN: true,
797
836
  REPORT_FOLDER: this.bvtContext.reportFolder,
798
837
  BLINQ_ENV: this.envName,
838
+ DEBUG: "blinq:route",
799
839
  };
800
840
 
801
841
  this.bvtContext.navigate = true;
842
+ this.bvtContext.loadedRoutes = null;
843
+ if (listenNetwork) {
844
+ process.env.STORE_DETAILED_NETWORK_DATA = "true";
845
+ } else {
846
+ process.env.STORE_DETAILED_NETWORK_DATA = "false";
847
+ }
802
848
  for (const [key, value] of Object.entries(_env)) {
803
849
  process.env[key] = value;
804
850
  }
851
+
805
852
  if (this.timerId) {
806
853
  clearTimeout(this.timerId);
807
854
  this.timerId = null;
808
855
  }
809
856
  await this.setMode("running");
857
+
810
858
  try {
811
- await this.stepRunner.runStep(
859
+ const { result, info } = await this.stepRunner.runStep(
812
860
  {
813
861
  step,
814
862
  parametersMap,
815
863
  envPath: this.envName,
864
+ tags,
865
+ config: this.config,
816
866
  },
817
867
  this.bvtContext,
818
- options
868
+ {
869
+ skipAfter,
870
+ skipBefore,
871
+ }
819
872
  );
820
873
  await this.revertMode();
874
+ return { info };
821
875
  } catch (error) {
822
876
  await this.revertMode();
823
877
  throw error;
@@ -828,15 +882,10 @@ export class BVTRecorder {
828
882
  this.bvtContext.navigate = false;
829
883
  }
830
884
  }
831
- async runScenario({ steps, parametersMap }) {
832
- for (const step of steps) {
833
- await this.runStep({ step, parametersMap });
834
- }
835
- }
836
885
  async saveScenario({ scenario, featureName, override, isSingleStep }) {
837
886
  await updateStepDefinitions({ scenario, featureName, projectDir: this.projectDir }); // updates mjs files
838
887
  if (!isSingleStep) await updateFeatureFile({ featureName, scenario, override, projectDir: this.projectDir }); // updates gherkin files
839
- await this.cleanup();
888
+ await this.cleanup({ tags: scenario.tags });
840
889
  }
841
890
  async getImplementedSteps() {
842
891
  const stepsAndScenarios = await getImplementedSteps(this.projectDir);
@@ -860,6 +909,7 @@ export class BVTRecorder {
860
909
  step.commands = [];
861
910
  }
862
911
  }
912
+ return steps;
863
913
  // return getStepsAndCommandsForScenario({
864
914
  // name,
865
915
  // featureName,
@@ -867,6 +917,7 @@ export class BVTRecorder {
867
917
  // map: this.scenariosStepsMap,
868
918
  // });
869
919
  }
920
+
870
921
  async generateStepName({ commands, stepsNames, parameters, map }) {
871
922
  return await this.namesService.generateStepName({ commands, stepsNames, parameters, map });
872
923
  }
@@ -918,10 +969,11 @@ export class BVTRecorder {
918
969
  if (existsSync(_getDataFile(this.world, this.bvtContext, this.web))) {
919
970
  try {
920
971
  const testData = JSON.parse(readFileSync(_getDataFile(this.world, this.bvtContext, this.web), "utf8"));
921
- this.logger.info("Test data", testData);
972
+ // this.logger.info("Test data", testData);
922
973
  this.sendEvent(this.events.getTestData, testData);
923
974
  } catch (e) {
924
- this.logger.error("Error reading test data file", e);
975
+ // this.logger.error("Error reading test data file", e);
976
+ console.log("Error reading test data file", e);
925
977
  }
926
978
  }
927
979
 
@@ -930,10 +982,12 @@ export class BVTRecorder {
930
982
  this.watcher.on("all", async (event, path) => {
931
983
  try {
932
984
  const testData = JSON.parse(await readFile(_getDataFile(this.world, this.bvtContext, this.web), "utf8"));
933
- this.logger.info("Test data", testData);
985
+ // this.logger.info("Test data", testData);
986
+ console.log("Test data changed", testData);
934
987
  this.sendEvent(this.events.getTestData, testData);
935
988
  } catch (e) {
936
- this.logger.error("Error reading test data file", e);
989
+ // this.logger.error("Error reading test data file", e);
990
+ console.log("Error reading test data file", e);
937
991
  }
938
992
  });
939
993
  }
@@ -949,9 +1003,9 @@ export class BVTRecorder {
949
1003
  }
950
1004
  }
951
1005
 
952
- async discardTestData() {
1006
+ async discardTestData({ tags }) {
953
1007
  resetTestData(this.envName, this.world);
954
- await this.cleanup();
1008
+ await this.cleanup({ tags });
955
1009
  }
956
1010
  async addToTestData(obj) {
957
1011
  if (!existsSync(_getDataFile(this.world, this.bvtContext, this.web))) {
@@ -966,7 +1020,7 @@ export class BVTRecorder {
966
1020
  .filter((file) => file.endsWith(".feature"))
967
1021
  .map((file) => path.join(this.projectDir, "features", file));
968
1022
  try {
969
- const parsedFiles = featureFiles.map((file) => parseFeatureFile(file));
1023
+ const parsedFiles = featureFiles.map((file) => this.parseFeatureFile(file));
970
1024
  const output = {};
971
1025
  parsedFiles.forEach((file) => {
972
1026
  if (!file.feature) return;
@@ -990,10 +1044,11 @@ export class BVTRecorder {
990
1044
  const stepParams = parseStepTextParameters(stepName);
991
1045
  return getCommandsForImplementedStep(stepName, step_definitions, stepParams).commands;
992
1046
  }
1047
+
993
1048
  loadExistingScenario({ featureName, scenarioName }) {
994
1049
  const step_definitions = loadStepDefinitions(this.projectDir);
995
1050
  const featureFilePath = path.join(this.projectDir, "features", featureName);
996
- const gherkinDoc = parseFeatureFile(featureFilePath);
1051
+ const gherkinDoc = this.parseFeatureFile(featureFilePath);
997
1052
  const scenario = gherkinDoc.feature.children.find((child) => child.scenario.name === scenarioName)?.scenario;
998
1053
 
999
1054
  const steps = [];
@@ -1018,6 +1073,7 @@ export class BVTRecorder {
1018
1073
  ..._s,
1019
1074
  keyword: step.keyword.trim(),
1020
1075
  };
1076
+ parseRouteFiles(this.projectDir, _step);
1021
1077
  steps.push(_step);
1022
1078
  }
1023
1079
  return {
@@ -1057,7 +1113,7 @@ export class BVTRecorder {
1057
1113
  }
1058
1114
  return result;
1059
1115
  }
1060
- async cleanup() {
1116
+ async cleanup({ tags }) {
1061
1117
  const noopStep = {
1062
1118
  text: "Noop",
1063
1119
  isImplemented: true,
@@ -1071,6 +1127,7 @@ export class BVTRecorder {
1071
1127
  {
1072
1128
  step: noopStep,
1073
1129
  parametersMap: {},
1130
+ tags: tags || [],
1074
1131
  },
1075
1132
  {
1076
1133
  skipAfter: false,
@@ -1109,20 +1166,62 @@ export class BVTRecorder {
1109
1166
  return false;
1110
1167
  }
1111
1168
  }
1112
- }
1169
+ async initExecution({ tags = [] }) {
1170
+ // run before hooks
1171
+ const noopStep = {
1172
+ text: "Noop",
1173
+ isImplemented: true,
1174
+ };
1175
+ await this.runStep(
1176
+ {
1177
+ step: noopStep,
1178
+ parametersMap: {},
1179
+ tags,
1180
+ },
1181
+ {
1182
+ skipBefore: false,
1183
+ skipAfter: true,
1184
+ }
1185
+ );
1186
+ }
1187
+ async cleanupExecution({ tags = [] }) {
1188
+ // run after hooks
1189
+ const noopStep = {
1190
+ text: "Noop",
1191
+ isImplemented: true,
1192
+ };
1193
+ await this.runStep(
1194
+ {
1195
+ step: noopStep,
1196
+ parametersMap: {},
1197
+ tags,
1198
+ },
1199
+ {
1200
+ skipBefore: true,
1201
+ skipAfter: false,
1202
+ }
1203
+ );
1204
+ }
1205
+ async resetExecution({ tags = [] }) {
1206
+ // run after hooks followed by before hooks
1207
+ await this.cleanupExecution({ tags });
1208
+ await this.initExecution({ tags });
1209
+ }
1113
1210
 
1114
- const parseFeatureFile = (featureFilePath) => {
1115
- try {
1116
- let id = 0;
1117
- const uuidFn = () => (++id).toString(16);
1118
- const builder = new AstBuilder(uuidFn);
1119
- const matcher = new GherkinClassicTokenMatcher();
1120
- const parser = new Parser(builder, matcher);
1121
- const source = readFileSync(featureFilePath, "utf8");
1122
- const gherkinDocument = parser.parse(source);
1123
- return gherkinDocument;
1124
- } catch (e) {
1125
- console.log(e);
1211
+ parseFeatureFile(featureFilePath) {
1212
+ try {
1213
+ let id = 0;
1214
+ const uuidFn = () => (++id).toString(16);
1215
+ const builder = new AstBuilder(uuidFn);
1216
+ const matcher = new GherkinClassicTokenMatcher();
1217
+ const parser = new Parser(builder, matcher);
1218
+ const source = readFileSync(featureFilePath, "utf8");
1219
+ const gherkinDocument = parser.parse(source);
1220
+ return gherkinDocument;
1221
+ } catch (e) {
1222
+ this.logger.error(`Error parsing feature file: ${featureFilePath}`);
1223
+ console.log(e);
1224
+ }
1225
+ return {};
1126
1226
  }
1127
- return {};
1128
- };
1227
+ }
@@ -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,17 +294,17 @@ export const getImplementedSteps = async (projectDir) => {
236
294
  for (const tag of scenario.tags) {
237
295
  delete tag.location;
238
296
  }
239
- // for (const scenario of scenarios) {
240
- // for (const step of scenario.steps) {
241
- // if (step.templateIndex === undefined) {
242
- // const cleanStepName = stepsDefinitions._stepNameToTemplate(step.text);
243
- // const index = implementedSteps.findIndex((istep) => {
244
- // return cleanStepName === istep.pattern;
245
- // });
246
- // step.templateIndex = index;
247
- // }
248
- // }
249
- // }
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
+ }
250
308
  }
251
309
  if (foundErrors.length > 0) {
252
310
  console.log("foundErrors", foundErrors);