@dev-blinq/cucumber_client 1.0.1276-dev → 1.0.1276-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 (37) hide show
  1. package/bin/assets/bundled_scripts/recorder.js +121 -121
  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 +11 -4
  5. package/bin/assets/scripts/unique_locators.js +837 -815
  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 -902
  14. package/bin/client/code_gen/playwright_codeget.js +43 -12
  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 -81
  18. package/bin/client/cucumber_selector.js +17 -1
  19. package/bin/client/local_agent.js +7 -6
  20. package/bin/client/project.js +186 -196
  21. package/bin/client/recorderv3/bvt_recorder.js +170 -60
  22. package/bin/client/recorderv3/implemented_steps.js +74 -16
  23. package/bin/client/recorderv3/index.js +50 -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 +332 -69
  27. package/bin/client/recorderv3/step_utils.js +579 -7
  28. package/bin/client/recorderv3/update_feature.js +32 -30
  29. package/bin/client/run_cucumber.js +5 -1
  30. package/bin/client/scenario_report.js +0 -5
  31. package/bin/client/test_scenario.js +0 -1
  32. package/bin/client/utils/socket_logger.js +132 -0
  33. package/bin/index.js +1 -0
  34. package/bin/logger.js +3 -2
  35. package/bin/min/consoleApi.min.cjs +2 -3
  36. package/bin/min/injectedScript.min.cjs +16 -16
  37. 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,10 @@ 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";
18
+ import { tmpdir } from "os";
19
19
  const __dirname = path.dirname(url.fileURLToPath(import.meta.url));
20
20
 
21
21
  const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
@@ -44,15 +44,15 @@ async function evaluate(frame, script) {
44
44
  async function findNestedFrameSelector(frame, obj) {
45
45
  try {
46
46
  const parent = frame.parentFrame();
47
- if (parent) console.log(`Parent frame: ${JSON.stringify(parent)}`);
48
47
  if (!parent) return { children: obj };
49
48
  const frameElement = await frame.frameElement();
50
49
  if (!frameElement) return;
51
50
  const selectors = await parent.evaluate((element) => {
52
- return window.getPWLocators(element);
51
+ return window.__bvt_Recorder.locatorGenerator.getElementLocators(element, { excludeText: true }).locators;
53
52
  }, frameElement);
54
53
  return findNestedFrameSelector(parent, { children: obj, selectors });
55
54
  } catch (e) {
55
+ socketLogger.error(`Error in findNestedFrameSelector: ${e}`);
56
56
  console.error(e);
57
57
  }
58
58
  }
@@ -149,6 +149,7 @@ const transformAction = (action, el, isVerify, isPopupCloseClick, isInHoverMode,
149
149
  };
150
150
  }
151
151
  default: {
152
+ socketLogger.error(`Action not supported: ${action.name}`);
152
153
  console.log("action not supported", action);
153
154
  throw new Error("action not supported");
154
155
  }
@@ -160,6 +161,7 @@ const transformAction = (action, el, isVerify, isPopupCloseClick, isInHoverMode,
160
161
  * @property {string} projectDir
161
162
  * @property {string} TOKEN
162
163
  * @property {(name:string, data:any)=> void} sendEvent
164
+ * @property {Object} logger
163
165
  */
164
166
  export class BVTRecorder {
165
167
  #currentURL = "";
@@ -173,7 +175,6 @@ export class BVTRecorder {
173
175
  */
174
176
  constructor(initialState) {
175
177
  Object.assign(this, initialState);
176
- this.logger = logger;
177
178
  this.screenshotMap = new Map();
178
179
  this.snapshotMap = new Map();
179
180
  this.scenariosStepsMap = new Map();
@@ -183,16 +184,16 @@ export class BVTRecorder {
183
184
  projectDir: this.projectDir,
184
185
  logger: this.logger,
185
186
  });
186
- this.stepRunner = new BVTStepRunner({
187
- projectDir: this.projectDir,
188
- });
189
187
  this.pageSet = new Set();
190
-
188
+ this.pageMetaDataSet = new Set();
191
189
  this.lastKnownUrlPath = "";
192
- // TODO: what is world?
193
190
  this.world = { attach: () => {} };
194
191
  this.shouldTakeScreenshot = true;
195
192
  this.watcher = null;
193
+ this.networkEventsFolder = path.join(tmpdir(), "blinq_network_events");
194
+ if (existsSync(this.networkEventsFolder)) {
195
+ rmSync(this.networkEventsFolder, { recursive: true, force: true });
196
+ }
196
197
  }
197
198
  events = {
198
199
  onFrameNavigate: "BVTRecorder.onFrameNavigate",
@@ -203,12 +204,16 @@ export class BVTRecorder {
203
204
  onStepDetails: "BVTRecorder.onStepDetails",
204
205
  getTestData: "BVTRecorder.getTestData",
205
206
  onGoto: "BVTRecorder.onGoto",
207
+ cmdExecutionStart: "BVTRecorder.cmdExecutionStart",
208
+ cmdExecutionSuccess: "BVTRecorder.cmdExecutionSuccess",
209
+ cmdExecutionError: "BVTRecorder.cmdExecutionError",
210
+ interceptResults: "BVTRecorder.interceptResults",
206
211
  };
207
212
  bindings = {
208
213
  __bvt_recordCommand: async ({ frame, page, context }, event) => {
209
214
  this.#activeFrame = frame;
210
215
  const nestFrmLoc = await findNestedFrameSelector(frame);
211
- console.log(`Time taken for action: ${event.statistics.time}`);
216
+ this.logger.info(`Time taken for action: ${event.statistics.time}`);
212
217
  await this.onAction({ ...event, nestFrmLoc });
213
218
  },
214
219
  __bvt_getMode: async () => {
@@ -227,8 +232,7 @@ export class BVTRecorder {
227
232
  await this.onClosePopup();
228
233
  },
229
234
  __bvt_log: async (src, message) => {
230
- // this.logger.info(message);
231
- console.log(`Inside Browser: ${message}`);
235
+ this.logger.info(`Inside Browser: ${message}`);
232
236
  },
233
237
  __bvt_getObject: (_src, obj) => {
234
238
  this.processObject(obj);
@@ -293,7 +297,7 @@ export class BVTRecorder {
293
297
  this.#remoteDebuggerPort = await findAvailablePort();
294
298
  process.env.CDP_LISTEN_PORT = this.#remoteDebuggerPort;
295
299
 
296
- this.stepRunner.setRemoteDebugPort(this.#remoteDebuggerPort);
300
+ // this.stepRunner.setRemoteDebugPort(this.#remoteDebuggerPort);
297
301
  this.world = { attach: () => {} };
298
302
 
299
303
  const ai_config_file = path.join(this.projectDir, "ai_config.json");
@@ -302,9 +306,10 @@ export class BVTRecorder {
302
306
  try {
303
307
  ai_config = JSON.parse(readFileSync(ai_config_file, "utf8"));
304
308
  } catch (error) {
305
- console.error("Error reading ai_config.json", error);
309
+ this.logger.error("Error reading ai_config.json", error);
306
310
  }
307
311
  }
312
+ this.config = ai_config;
308
313
  const initScripts = {
309
314
  // recorderCjs: injectedScriptSource,
310
315
  scripts: [
@@ -313,16 +318,37 @@ export class BVTRecorder {
313
318
  ],
314
319
  };
315
320
 
316
- let startTime = Date.now();
317
321
  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
322
  this.bvtContext = bvtContext;
323
+ this.stepRunner = new BVTStepRunner({
324
+ projectDir: this.projectDir,
325
+ sendExecutionStatus: (data) => {
326
+ if (data && data.type) {
327
+ switch (data.type) {
328
+ case "cmdExecutionStart":
329
+ this.sendEvent(this.events.cmdExecutionStart, data);
330
+ break;
331
+ case "cmdExecutionSuccess":
332
+ this.sendEvent(this.events.cmdExecutionSuccess, data);
333
+ break;
334
+ case "cmdExecutionError":
335
+ this.sendEvent(this.events.cmdExecutionError, data);
336
+ break;
337
+ case "interceptResults":
338
+ this.sendEvent(this.events.interceptResults, data);
339
+ break;
340
+ default:
341
+ break;
342
+ }
343
+ }
344
+ },
345
+ bvtContext: this.bvtContext,
346
+ });
321
347
  const context = bvtContext.playContext;
322
348
  this.context = context;
323
349
  this.web = bvtContext.stable || bvtContext.web;
350
+ this.web.tryAllStrategies = true;
324
351
  this.page = bvtContext.page;
325
-
326
352
  this.pageSet.add(this.page);
327
353
  this.lastKnownUrlPath = this._updateUrlPath();
328
354
  const browser = await this.context.browser();
@@ -350,13 +376,15 @@ export class BVTRecorder {
350
376
  }
351
377
  return;
352
378
  } catch (error) {
353
- console.error("Error evaluting in context:", error);
379
+ // console.error("Error evaluting in context:", error);
380
+ this.logger.error("Error evaluating in context:", error);
354
381
  }
355
382
  }
356
383
  }
357
384
 
358
385
  getMode() {
359
- console.log("getMode", this.#mode);
386
+ // console.log("getMode", this.#mode);
387
+ this.logger.info("Current mode:", this.#mode);
360
388
  return this.#mode;
361
389
  }
362
390
 
@@ -398,6 +426,8 @@ export class BVTRecorder {
398
426
  this.sendEvent(this.events.onBrowserClose);
399
427
  }
400
428
  } catch (error) {
429
+ this.logger.error("Error in page close event");
430
+ this.logger.error(error);
401
431
  console.error("Error in page close event");
402
432
  console.error(error);
403
433
  }
@@ -408,8 +438,10 @@ export class BVTRecorder {
408
438
  if (frame !== page.mainFrame()) return;
409
439
  this.handlePageTransition();
410
440
  } catch (error) {
441
+ this.logger.error("Error in handlePageTransition event");
442
+ this.logger.error(error);
411
443
  console.error("Error in handlePageTransition event");
412
- // console.error(error);
444
+ console.error(error);
413
445
  }
414
446
  try {
415
447
  if (frame !== this.#activeFrame) return;
@@ -428,6 +460,8 @@ export class BVTRecorder {
428
460
  // await this._setRecordingMode(frame);
429
461
  // await this._initPage(page);
430
462
  } catch (error) {
463
+ this.logger.error("Error in frame navigate event");
464
+ this.logger.error(error);
431
465
  console.error("Error in frame navigate event");
432
466
  // console.error(error);
433
467
  }
@@ -510,13 +544,9 @@ export class BVTRecorder {
510
544
 
511
545
  try {
512
546
  const result = await client.send("Page.getNavigationHistory");
513
- // console.log("Navigation History:", result);
514
547
  const entries = result.entries;
515
548
  const currentIndex = result.currentIndex;
516
549
 
517
- // ignore if currentIndex is not the last entry
518
- // if (currentIndex !== entries.length - 1) return;
519
-
520
550
  const currentEntry = entries[currentIndex];
521
551
  const transitionInfo = this.analyzeTransitionType(entries, currentIndex, currentEntry);
522
552
  this.previousIndex = currentIndex;
@@ -529,6 +559,8 @@ export class BVTRecorder {
529
559
  navigationAction: transitionInfo.action,
530
560
  };
531
561
  } catch (error) {
562
+ this.logger.error("Error in getCurrentTransition event");
563
+ this.logger.error(error);
532
564
  console.error("Error in getTransistionType event", error);
533
565
  } finally {
534
566
  await client.detach();
@@ -597,6 +629,8 @@ export class BVTRecorder {
597
629
  // add listener for frame navigation on new tab
598
630
  this._addFrameNavigateListener(page);
599
631
  } catch (error) {
632
+ this.logger.error("Error in page event");
633
+ this.logger.error(error);
600
634
  console.error("Error in page event");
601
635
  console.error(error);
602
636
  }
@@ -638,6 +672,7 @@ export class BVTRecorder {
638
672
  const { data } = await client.send("Page.captureScreenshot", { format: "png" });
639
673
  return data;
640
674
  } catch (error) {
675
+ this.logger.error("Error in taking browser screenshot");
641
676
  console.error("Error in taking browser screenshot", error);
642
677
  } finally {
643
678
  await client.detach();
@@ -783,41 +818,65 @@ export class BVTRecorder {
783
818
  async abortExecution() {
784
819
  await this.stepRunner.abortExecution();
785
820
  }
821
+
822
+ async pauseExecution({ cmdId }) {
823
+ await this.stepRunner.pauseExecution(cmdId);
824
+ }
825
+
826
+ async resumeExecution({ cmdId }) {
827
+ await this.stepRunner.resumeExecution(cmdId);
828
+ }
829
+
786
830
  async dealyedRevertMode() {
787
831
  const timerId = setTimeout(async () => {
788
832
  await this.revertMode();
789
833
  }, 100);
790
834
  this.timerId = timerId;
791
835
  }
792
- async runStep({ step, parametersMap }, options) {
836
+ async runStep({ step, parametersMap, tags, isFirstStep, listenNetwork }, options) {
837
+ const { skipAfter = true, skipBefore = !isFirstStep } = options || {};
793
838
  const _env = {
794
839
  TOKEN: this.TOKEN,
795
840
  TEMP_RUN: true,
796
841
  REPORT_FOLDER: this.bvtContext.reportFolder,
797
842
  BLINQ_ENV: this.envName,
843
+ DEBUG: "blinq:route",
798
844
  };
799
845
 
800
846
  this.bvtContext.navigate = true;
847
+ this.bvtContext.loadedRoutes = null;
848
+ if (listenNetwork) {
849
+ this.bvtContext.STORE_DETAILED_NETWORK_DATA = true;
850
+ } else {
851
+ this.bvtContext.STORE_DETAILED_NETWORK_DATA = false;
852
+ }
801
853
  for (const [key, value] of Object.entries(_env)) {
802
854
  process.env[key] = value;
803
855
  }
856
+
804
857
  if (this.timerId) {
805
858
  clearTimeout(this.timerId);
806
859
  this.timerId = null;
807
860
  }
808
861
  await this.setMode("running");
862
+
809
863
  try {
810
864
  const { result, info } = await this.stepRunner.runStep(
811
865
  {
812
866
  step,
813
867
  parametersMap,
814
868
  envPath: this.envName,
869
+ tags,
870
+ config: this.config,
815
871
  },
816
872
  this.bvtContext,
817
- options
873
+ {
874
+ skipAfter,
875
+ skipBefore,
876
+ }
818
877
  );
819
878
  await this.revertMode();
820
- return { result, info };
879
+ return { info };
821
880
  } catch (error) {
822
881
  await this.revertMode();
823
882
  throw error;
@@ -828,15 +887,10 @@ export class BVTRecorder {
828
887
  this.bvtContext.navigate = false;
829
888
  }
830
889
  }
831
- async runScenario({ steps, parametersMap }) {
832
- for (const step of steps) {
833
- await this.runStep({ step, parametersMap });
834
- }
835
- }
836
890
  async saveScenario({ scenario, featureName, override, isSingleStep }) {
837
891
  await updateStepDefinitions({ scenario, featureName, projectDir: this.projectDir }); // updates mjs files
838
892
  if (!isSingleStep) await updateFeatureFile({ featureName, scenario, override, projectDir: this.projectDir }); // updates gherkin files
839
- await this.cleanup();
893
+ await this.cleanup({ tags: scenario.tags });
840
894
  }
841
895
  async getImplementedSteps() {
842
896
  const stepsAndScenarios = await getImplementedSteps(this.projectDir);
@@ -860,6 +914,7 @@ export class BVTRecorder {
860
914
  step.commands = [];
861
915
  }
862
916
  }
917
+ return steps;
863
918
  // return getStepsAndCommandsForScenario({
864
919
  // name,
865
920
  // featureName,
@@ -867,6 +922,7 @@ export class BVTRecorder {
867
922
  // map: this.scenariosStepsMap,
868
923
  // });
869
924
  }
925
+
870
926
  async generateStepName({ commands, stepsNames, parameters, map }) {
871
927
  return await this.namesService.generateStepName({ commands, stepsNames, parameters, map });
872
928
  }
@@ -918,10 +974,11 @@ export class BVTRecorder {
918
974
  if (existsSync(_getDataFile(this.world, this.bvtContext, this.web))) {
919
975
  try {
920
976
  const testData = JSON.parse(readFileSync(_getDataFile(this.world, this.bvtContext, this.web), "utf8"));
921
- this.logger.info("Test data", testData);
977
+ // this.logger.info("Test data", testData);
922
978
  this.sendEvent(this.events.getTestData, testData);
923
979
  } catch (e) {
924
- this.logger.error("Error reading test data file", e);
980
+ // this.logger.error("Error reading test data file", e);
981
+ console.log("Error reading test data file", e);
925
982
  }
926
983
  }
927
984
 
@@ -930,10 +987,12 @@ export class BVTRecorder {
930
987
  this.watcher.on("all", async (event, path) => {
931
988
  try {
932
989
  const testData = JSON.parse(await readFile(_getDataFile(this.world, this.bvtContext, this.web), "utf8"));
933
- this.logger.info("Test data", testData);
990
+ // this.logger.info("Test data", testData);
991
+ console.log("Test data changed", testData);
934
992
  this.sendEvent(this.events.getTestData, testData);
935
993
  } catch (e) {
936
- this.logger.error("Error reading test data file", e);
994
+ // this.logger.error("Error reading test data file", e);
995
+ console.log("Error reading test data file", e);
937
996
  }
938
997
  });
939
998
  }
@@ -949,9 +1008,9 @@ export class BVTRecorder {
949
1008
  }
950
1009
  }
951
1010
 
952
- async discardTestData() {
1011
+ async discardTestData({ tags }) {
953
1012
  resetTestData(this.envName, this.world);
954
- await this.cleanup();
1013
+ await this.cleanup({ tags });
955
1014
  }
956
1015
  async addToTestData(obj) {
957
1016
  if (!existsSync(_getDataFile(this.world, this.bvtContext, this.web))) {
@@ -966,7 +1025,7 @@ export class BVTRecorder {
966
1025
  .filter((file) => file.endsWith(".feature"))
967
1026
  .map((file) => path.join(this.projectDir, "features", file));
968
1027
  try {
969
- const parsedFiles = featureFiles.map((file) => parseFeatureFile(file));
1028
+ const parsedFiles = featureFiles.map((file) => this.parseFeatureFile(file));
970
1029
  const output = {};
971
1030
  parsedFiles.forEach((file) => {
972
1031
  if (!file.feature) return;
@@ -990,10 +1049,11 @@ export class BVTRecorder {
990
1049
  const stepParams = parseStepTextParameters(stepName);
991
1050
  return getCommandsForImplementedStep(stepName, step_definitions, stepParams).commands;
992
1051
  }
1052
+
993
1053
  loadExistingScenario({ featureName, scenarioName }) {
994
1054
  const step_definitions = loadStepDefinitions(this.projectDir);
995
1055
  const featureFilePath = path.join(this.projectDir, "features", featureName);
996
- const gherkinDoc = parseFeatureFile(featureFilePath);
1056
+ const gherkinDoc = this.parseFeatureFile(featureFilePath);
997
1057
  const scenario = gherkinDoc.feature.children.find((child) => child.scenario.name === scenarioName)?.scenario;
998
1058
 
999
1059
  const steps = [];
@@ -1018,6 +1078,7 @@ export class BVTRecorder {
1018
1078
  ..._s,
1019
1079
  keyword: step.keyword.trim(),
1020
1080
  };
1081
+ parseRouteFiles(this.projectDir, _step);
1021
1082
  steps.push(_step);
1022
1083
  }
1023
1084
  return {
@@ -1057,7 +1118,7 @@ export class BVTRecorder {
1057
1118
  }
1058
1119
  return result;
1059
1120
  }
1060
- async cleanup() {
1121
+ async cleanup({ tags }) {
1061
1122
  const noopStep = {
1062
1123
  text: "Noop",
1063
1124
  isImplemented: true,
@@ -1071,6 +1132,7 @@ export class BVTRecorder {
1071
1132
  {
1072
1133
  step: noopStep,
1073
1134
  parametersMap: {},
1135
+ tags: tags || [],
1074
1136
  },
1075
1137
  {
1076
1138
  skipAfter: false,
@@ -1109,20 +1171,68 @@ export class BVTRecorder {
1109
1171
  return false;
1110
1172
  }
1111
1173
  }
1112
- }
1174
+ async initExecution({ tags = [] }) {
1175
+ // run before hooks
1176
+ const noopStep = {
1177
+ text: "Noop",
1178
+ isImplemented: true,
1179
+ };
1180
+ await this.runStep(
1181
+ {
1182
+ step: noopStep,
1183
+ parametersMap: {},
1184
+ tags,
1185
+ },
1186
+ {
1187
+ skipBefore: false,
1188
+ skipAfter: true,
1189
+ }
1190
+ );
1191
+ }
1192
+ async cleanupExecution({ tags = [] }) {
1193
+ // run after hooks
1194
+ const noopStep = {
1195
+ text: "Noop",
1196
+ isImplemented: true,
1197
+ };
1198
+ await this.runStep(
1199
+ {
1200
+ step: noopStep,
1201
+ parametersMap: {},
1202
+ tags,
1203
+ },
1204
+ {
1205
+ skipBefore: true,
1206
+ skipAfter: false,
1207
+ }
1208
+ );
1209
+ }
1210
+ async resetExecution({ tags = [] }) {
1211
+ // run after hooks followed by before hooks
1212
+ await this.cleanupExecution({ tags });
1213
+ await this.initExecution({ tags });
1214
+ }
1113
1215
 
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);
1216
+ parseFeatureFile(featureFilePath) {
1217
+ try {
1218
+ let id = 0;
1219
+ const uuidFn = () => (++id).toString(16);
1220
+ const builder = new AstBuilder(uuidFn);
1221
+ const matcher = new GherkinClassicTokenMatcher();
1222
+ const parser = new Parser(builder, matcher);
1223
+ const source = readFileSync(featureFilePath, "utf8");
1224
+ const gherkinDocument = parser.parse(source);
1225
+ return gherkinDocument;
1226
+ } catch (e) {
1227
+ this.logger.error(`Error parsing feature file: ${featureFilePath}`);
1228
+ console.log(e);
1229
+ }
1230
+ return {};
1126
1231
  }
1127
- return {};
1128
- };
1232
+
1233
+ stopRecordingNetwork(input) {
1234
+ if (this.bvtContext) {
1235
+ this.bvtContext.STORE_DETAILED_NETWORK_DATA = false;
1236
+ }
1237
+ }
1238
+ }
@@ -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);