@dev-blinq/cucumber_client 1.0.1318-dev → 1.0.1318-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 (51) hide show
  1. package/bin/assets/bundled_scripts/recorder.js +108 -108
  2. package/bin/assets/preload/css_gen.js +10 -10
  3. package/bin/assets/preload/recorderv3.js +3 -1
  4. package/bin/assets/preload/toolbar.js +27 -29
  5. package/bin/assets/preload/unique_locators.js +1 -1
  6. package/bin/assets/preload/yaml.js +288 -275
  7. package/bin/assets/scripts/aria_snapshot.js +223 -220
  8. package/bin/assets/scripts/dom_attr.js +329 -329
  9. package/bin/assets/scripts/dom_parent.js +169 -174
  10. package/bin/assets/scripts/event_utils.js +94 -94
  11. package/bin/assets/scripts/pw.js +2050 -1949
  12. package/bin/assets/scripts/recorder.js +5 -17
  13. package/bin/assets/scripts/snapshot_capturer.js +153 -146
  14. package/bin/assets/scripts/unique_locators.js +156 -48
  15. package/bin/assets/scripts/yaml.js +796 -783
  16. package/bin/assets/templates/_hooks_template.txt +41 -0
  17. package/bin/assets/templates/utils_template.txt +1 -44
  18. package/bin/client/apiTest/apiTest.js +6 -0
  19. package/bin/client/cli_helpers.js +11 -13
  20. package/bin/client/code_cleanup/utils.js +5 -1
  21. package/bin/client/code_gen/api_codegen.js +2 -2
  22. package/bin/client/code_gen/code_inversion.js +53 -4
  23. package/bin/client/code_gen/page_reflection.js +839 -906
  24. package/bin/client/code_gen/playwright_codeget.js +32 -17
  25. package/bin/client/cucumber/feature.js +89 -27
  26. package/bin/client/cucumber/feature_data.js +2 -2
  27. package/bin/client/cucumber/project_to_document.js +9 -3
  28. package/bin/client/cucumber/steps_definitions.js +90 -87
  29. package/bin/client/cucumber_selector.js +17 -1
  30. package/bin/client/local_agent.js +6 -5
  31. package/bin/client/parse_feature_file.js +23 -26
  32. package/bin/client/playground/projects/env.json +2 -2
  33. package/bin/client/project.js +186 -196
  34. package/bin/client/recorderv3/bvt_recorder.js +187 -76
  35. package/bin/client/recorderv3/implemented_steps.js +74 -16
  36. package/bin/client/recorderv3/index.js +68 -54
  37. package/bin/client/recorderv3/scriptTest.js +1 -1
  38. package/bin/client/recorderv3/services.js +4 -16
  39. package/bin/client/recorderv3/step_runner.js +313 -170
  40. package/bin/client/recorderv3/step_utils.js +516 -4
  41. package/bin/client/recorderv3/update_feature.js +32 -30
  42. package/bin/client/run_cucumber.js +5 -1
  43. package/bin/client/scenario_report.js +0 -5
  44. package/bin/client/test_scenario.js +0 -1
  45. package/bin/client/upload-service.js +2 -2
  46. package/bin/client/utils/socket_logger.js +132 -0
  47. package/bin/index.js +1 -0
  48. package/bin/logger.js +3 -2
  49. package/bin/min/consoleApi.min.cjs +2 -3
  50. package/bin/min/injectedScript.min.cjs +16 -16
  51. package/package.json +21 -12
@@ -1,9 +1,9 @@
1
1
  // define the jsdoc type for the input
2
2
  import { closeContext, initContext, _getDataFile, resetTestData } from "automation_model";
3
- import { existsSync, mkdir, mkdirSync, readdirSync, readFileSync, rmSync } from "fs";
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,11 +12,11 @@ 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
- import NetworkMonitor from "./network.js";
19
-
17
+ import socketLogger from "../utils/socket_logger.js";
18
+ import { tmpdir } from "os";
19
+ import { faker } from "@faker-js/faker";
20
20
  const __dirname = path.dirname(url.fileURLToPath(import.meta.url));
21
21
 
22
22
  const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
@@ -45,15 +45,15 @@ async function evaluate(frame, script) {
45
45
  async function findNestedFrameSelector(frame, obj) {
46
46
  try {
47
47
  const parent = frame.parentFrame();
48
- if (parent) console.log(`Parent frame: ${JSON.stringify(parent)}`);
49
48
  if (!parent) return { children: obj };
50
49
  const frameElement = await frame.frameElement();
51
50
  if (!frameElement) return;
52
51
  const selectors = await parent.evaluate((element) => {
53
- return window.getPWLocators(element);
52
+ return window.__bvt_Recorder.locatorGenerator.getElementLocators(element, { excludeText: true }).locators;
54
53
  }, frameElement);
55
54
  return findNestedFrameSelector(parent, { children: obj, selectors });
56
55
  } catch (e) {
56
+ socketLogger.error(`Error in findNestedFrameSelector: ${e}`);
57
57
  console.error(e);
58
58
  }
59
59
  }
@@ -150,6 +150,7 @@ const transformAction = (action, el, isVerify, isPopupCloseClick, isInHoverMode,
150
150
  };
151
151
  }
152
152
  default: {
153
+ socketLogger.error(`Action not supported: ${action.name}`);
153
154
  console.log("action not supported", action);
154
155
  throw new Error("action not supported");
155
156
  }
@@ -161,6 +162,7 @@ const transformAction = (action, el, isVerify, isPopupCloseClick, isInHoverMode,
161
162
  * @property {string} projectDir
162
163
  * @property {string} TOKEN
163
164
  * @property {(name:string, data:any)=> void} sendEvent
165
+ * @property {Object} logger
164
166
  */
165
167
  export class BVTRecorder {
166
168
  #currentURL = "";
@@ -174,7 +176,6 @@ export class BVTRecorder {
174
176
  */
175
177
  constructor(initialState) {
176
178
  Object.assign(this, initialState);
177
- this.logger = logger;
178
179
  this.screenshotMap = new Map();
179
180
  this.snapshotMap = new Map();
180
181
  this.scenariosStepsMap = new Map();
@@ -184,37 +185,16 @@ export class BVTRecorder {
184
185
  projectDir: this.projectDir,
185
186
  logger: this.logger,
186
187
  });
187
- this.stepRunner = new BVTStepRunner({
188
- projectDir: this.projectDir,
189
- sendExecutionStatus: (data) => {
190
- if (data && data.type) {
191
- switch (data.type) {
192
- case "cmdExecutionStart":
193
- console.log("Sending cmdExecutionStart event for cmdId:", data.cmdId);
194
- this.sendEvent(this.events.cmdExecutionStart, data.cmdId);
195
- break;
196
- case "cmdExecutionSuccess":
197
- console.log("Sending cmdExecutionSuccess event for cmdId:", data.cmdId);
198
- this.sendEvent(this.events.cmdExecutionSuccess, data.cmdId);
199
- break;
200
- case "cmdExecutionFailure":
201
- console.log("Sending cmdExecutionFailure event for cmdId:", data.cmdId);
202
- this.sendEvent(this.events.cmdExecutionFailure, data.cmdId);
203
- break;
204
- default:
205
- console.warn("Unknown command execution status type:", data.type);
206
- break;
207
- }
208
- }
209
- },
210
- });
211
188
  this.pageSet = new Set();
212
- this.networkMonitor = new NetworkMonitor();
189
+ this.pageMetaDataSet = new Set();
213
190
  this.lastKnownUrlPath = "";
214
- // TODO: what is world?
215
191
  this.world = { attach: () => {} };
216
192
  this.shouldTakeScreenshot = true;
217
193
  this.watcher = null;
194
+ this.networkEventsFolder = path.join(tmpdir(), "blinq_network_events");
195
+ if (existsSync(this.networkEventsFolder)) {
196
+ rmSync(this.networkEventsFolder, { recursive: true, force: true });
197
+ }
218
198
  }
219
199
  events = {
220
200
  onFrameNavigate: "BVTRecorder.onFrameNavigate",
@@ -227,13 +207,14 @@ export class BVTRecorder {
227
207
  onGoto: "BVTRecorder.onGoto",
228
208
  cmdExecutionStart: "BVTRecorder.cmdExecutionStart",
229
209
  cmdExecutionSuccess: "BVTRecorder.cmdExecutionSuccess",
230
- cmdExecutionFailure: "BVTRecorder.cmdExecutionFailure",
210
+ cmdExecutionError: "BVTRecorder.cmdExecutionError",
211
+ interceptResults: "BVTRecorder.interceptResults",
231
212
  };
232
213
  bindings = {
233
214
  __bvt_recordCommand: async ({ frame, page, context }, event) => {
234
215
  this.#activeFrame = frame;
235
216
  const nestFrmLoc = await findNestedFrameSelector(frame);
236
- console.log(`Time taken for action: ${event.statistics.time}`);
217
+ this.logger.info(`Time taken for action: ${event.statistics.time}`);
237
218
  await this.onAction({ ...event, nestFrmLoc });
238
219
  },
239
220
  __bvt_getMode: async () => {
@@ -252,8 +233,7 @@ export class BVTRecorder {
252
233
  await this.onClosePopup();
253
234
  },
254
235
  __bvt_log: async (src, message) => {
255
- // this.logger.info(message);
256
- console.log(`Inside Browser: ${message}`);
236
+ this.logger.info(`Inside Browser: ${message}`);
257
237
  },
258
238
  __bvt_getObject: (_src, obj) => {
259
239
  this.processObject(obj);
@@ -265,7 +245,6 @@ export class BVTRecorder {
265
245
  const locator = await this.web.page.locator(selector);
266
246
  const snapshot = await locator.ariaSnapshot();
267
247
  return snapshot;
268
- // Triggering workflow
269
248
  };
270
249
 
271
250
  processObject = async ({ type, action, value }) => {
@@ -316,10 +295,11 @@ export class BVTRecorder {
316
295
  }
317
296
 
318
297
  async _initBrowser({ url }) {
298
+ socketLogger.info("Only present in 1.0.1293-stage");
319
299
  this.#remoteDebuggerPort = await findAvailablePort();
320
300
  process.env.CDP_LISTEN_PORT = this.#remoteDebuggerPort;
321
301
 
322
- this.stepRunner.setRemoteDebugPort(this.#remoteDebuggerPort);
302
+ // this.stepRunner.setRemoteDebugPort(this.#remoteDebuggerPort);
323
303
  this.world = { attach: () => {} };
324
304
 
325
305
  const ai_config_file = path.join(this.projectDir, "ai_config.json");
@@ -328,9 +308,10 @@ export class BVTRecorder {
328
308
  try {
329
309
  ai_config = JSON.parse(readFileSync(ai_config_file, "utf8"));
330
310
  } catch (error) {
331
- console.error("Error reading ai_config.json", error);
311
+ this.logger.error("Error reading ai_config.json", error);
332
312
  }
333
313
  }
314
+ this.config = ai_config;
334
315
  const initScripts = {
335
316
  // recorderCjs: injectedScriptSource,
336
317
  scripts: [
@@ -339,16 +320,37 @@ export class BVTRecorder {
339
320
  ],
340
321
  };
341
322
 
342
- let startTime = Date.now();
343
323
  const bvtContext = await initContext(url, false, false, this.world, 450, initScripts, this.envName);
344
- let stopTime = Date.now();
345
- this.logger.info(`Browser launched in ${(stopTime - startTime) / 1000} s`);
346
324
  this.bvtContext = bvtContext;
325
+ this.stepRunner = new BVTStepRunner({
326
+ projectDir: this.projectDir,
327
+ sendExecutionStatus: (data) => {
328
+ if (data && data.type) {
329
+ switch (data.type) {
330
+ case "cmdExecutionStart":
331
+ this.sendEvent(this.events.cmdExecutionStart, data);
332
+ break;
333
+ case "cmdExecutionSuccess":
334
+ this.sendEvent(this.events.cmdExecutionSuccess, data);
335
+ break;
336
+ case "cmdExecutionError":
337
+ this.sendEvent(this.events.cmdExecutionError, data);
338
+ break;
339
+ case "interceptResults":
340
+ this.sendEvent(this.events.interceptResults, data);
341
+ break;
342
+ default:
343
+ break;
344
+ }
345
+ }
346
+ },
347
+ bvtContext: this.bvtContext,
348
+ });
347
349
  const context = bvtContext.playContext;
348
350
  this.context = context;
349
351
  this.web = bvtContext.stable || bvtContext.web;
352
+ this.web.tryAllStrategies = true;
350
353
  this.page = bvtContext.page;
351
-
352
354
  this.pageSet.add(this.page);
353
355
  this.lastKnownUrlPath = this._updateUrlPath();
354
356
  const browser = await this.context.browser();
@@ -376,13 +378,15 @@ export class BVTRecorder {
376
378
  }
377
379
  return;
378
380
  } catch (error) {
379
- console.error("Error evaluting in context:", error);
381
+ // console.error("Error evaluting in context:", error);
382
+ this.logger.error("Error evaluating in context:", error);
380
383
  }
381
384
  }
382
385
  }
383
386
 
384
387
  getMode() {
385
- console.log("getMode", this.#mode);
388
+ // console.log("getMode", this.#mode);
389
+ this.logger.info("Current mode:", this.#mode);
386
390
  return this.#mode;
387
391
  }
388
392
 
@@ -424,6 +428,8 @@ export class BVTRecorder {
424
428
  this.sendEvent(this.events.onBrowserClose);
425
429
  }
426
430
  } catch (error) {
431
+ this.logger.error("Error in page close event");
432
+ this.logger.error(error);
427
433
  console.error("Error in page close event");
428
434
  console.error(error);
429
435
  }
@@ -434,8 +440,10 @@ export class BVTRecorder {
434
440
  if (frame !== page.mainFrame()) return;
435
441
  this.handlePageTransition();
436
442
  } catch (error) {
443
+ this.logger.error("Error in handlePageTransition event");
444
+ this.logger.error(error);
437
445
  console.error("Error in handlePageTransition event");
438
- // console.error(error);
446
+ console.error(error);
439
447
  }
440
448
  try {
441
449
  if (frame !== this.#activeFrame) return;
@@ -454,6 +462,8 @@ export class BVTRecorder {
454
462
  // await this._setRecordingMode(frame);
455
463
  // await this._initPage(page);
456
464
  } catch (error) {
465
+ this.logger.error("Error in frame navigate event");
466
+ this.logger.error(error);
457
467
  console.error("Error in frame navigate event");
458
468
  // console.error(error);
459
469
  }
@@ -536,13 +546,9 @@ export class BVTRecorder {
536
546
 
537
547
  try {
538
548
  const result = await client.send("Page.getNavigationHistory");
539
- // console.log("Navigation History:", result);
540
549
  const entries = result.entries;
541
550
  const currentIndex = result.currentIndex;
542
551
 
543
- // ignore if currentIndex is not the last entry
544
- // if (currentIndex !== entries.length - 1) return;
545
-
546
552
  const currentEntry = entries[currentIndex];
547
553
  const transitionInfo = this.analyzeTransitionType(entries, currentIndex, currentEntry);
548
554
  this.previousIndex = currentIndex;
@@ -555,6 +561,8 @@ export class BVTRecorder {
555
561
  navigationAction: transitionInfo.action,
556
562
  };
557
563
  } catch (error) {
564
+ this.logger.error("Error in getCurrentTransition event");
565
+ this.logger.error(error);
558
566
  console.error("Error in getTransistionType event", error);
559
567
  } finally {
560
568
  await client.detach();
@@ -623,6 +631,8 @@ export class BVTRecorder {
623
631
  // add listener for frame navigation on new tab
624
632
  this._addFrameNavigateListener(page);
625
633
  } catch (error) {
634
+ this.logger.error("Error in page event");
635
+ this.logger.error(error);
626
636
  console.error("Error in page event");
627
637
  console.error(error);
628
638
  }
@@ -664,6 +674,7 @@ export class BVTRecorder {
664
674
  const { data } = await client.send("Page.captureScreenshot", { format: "png" });
665
675
  return data;
666
676
  } catch (error) {
677
+ this.logger.error("Error in taking browser screenshot");
667
678
  console.error("Error in taking browser screenshot", error);
668
679
  } finally {
669
680
  await client.detach();
@@ -809,6 +820,15 @@ export class BVTRecorder {
809
820
  async abortExecution() {
810
821
  await this.stepRunner.abortExecution();
811
822
  }
823
+
824
+ async pauseExecution({ cmdId }) {
825
+ await this.stepRunner.pauseExecution(cmdId);
826
+ }
827
+
828
+ async resumeExecution({ cmdId }) {
829
+ await this.stepRunner.resumeExecution(cmdId);
830
+ }
831
+
812
832
  async dealyedRevertMode() {
813
833
  const timerId = setTimeout(async () => {
814
834
  await this.revertMode();
@@ -816,16 +836,21 @@ export class BVTRecorder {
816
836
  this.timerId = timerId;
817
837
  }
818
838
  async runStep({ step, parametersMap, tags, isFirstStep, listenNetwork }, options) {
839
+ const { skipAfter = true, skipBefore = !isFirstStep } = options || {};
819
840
  const _env = {
820
841
  TOKEN: this.TOKEN,
821
842
  TEMP_RUN: true,
822
843
  REPORT_FOLDER: this.bvtContext.reportFolder,
823
844
  BLINQ_ENV: this.envName,
824
- STORE_DETAILED_NETWORK_DATA: listenNetwork ? "true" : "false",
825
- CURRENT_STEP_ID: step.id,
826
845
  };
827
846
 
828
847
  this.bvtContext.navigate = true;
848
+ this.bvtContext.loadedRoutes = null;
849
+ if (listenNetwork) {
850
+ this.bvtContext.STORE_DETAILED_NETWORK_DATA = true;
851
+ } else {
852
+ this.bvtContext.STORE_DETAILED_NETWORK_DATA = false;
853
+ }
829
854
  for (const [key, value] of Object.entries(_env)) {
830
855
  process.env[key] = value;
831
856
  }
@@ -843,9 +868,13 @@ export class BVTRecorder {
843
868
  parametersMap,
844
869
  envPath: this.envName,
845
870
  tags,
871
+ config: this.config,
846
872
  },
847
873
  this.bvtContext,
848
- options ? { ...options, skipBefore: !isFirstStep } : { skipBefore: !isFirstStep }
874
+ {
875
+ skipAfter,
876
+ skipBefore,
877
+ }
849
878
  );
850
879
  await this.revertMode();
851
880
  return { info };
@@ -886,6 +915,7 @@ export class BVTRecorder {
886
915
  step.commands = [];
887
916
  }
888
917
  }
918
+ return steps;
889
919
  // return getStepsAndCommandsForScenario({
890
920
  // name,
891
921
  // featureName,
@@ -893,6 +923,7 @@ export class BVTRecorder {
893
923
  // map: this.scenariosStepsMap,
894
924
  // });
895
925
  }
926
+
896
927
  async generateStepName({ commands, stepsNames, parameters, map }) {
897
928
  return await this.namesService.generateStepName({ commands, stepsNames, parameters, map });
898
929
  }
@@ -944,10 +975,11 @@ export class BVTRecorder {
944
975
  if (existsSync(_getDataFile(this.world, this.bvtContext, this.web))) {
945
976
  try {
946
977
  const testData = JSON.parse(readFileSync(_getDataFile(this.world, this.bvtContext, this.web), "utf8"));
947
- this.logger.info("Test data", testData);
978
+ // this.logger.info("Test data", testData);
948
979
  this.sendEvent(this.events.getTestData, testData);
949
980
  } catch (e) {
950
- this.logger.error("Error reading test data file", e);
981
+ // this.logger.error("Error reading test data file", e);
982
+ console.log("Error reading test data file", e);
951
983
  }
952
984
  }
953
985
 
@@ -956,10 +988,12 @@ export class BVTRecorder {
956
988
  this.watcher.on("all", async (event, path) => {
957
989
  try {
958
990
  const testData = JSON.parse(await readFile(_getDataFile(this.world, this.bvtContext, this.web), "utf8"));
959
- this.logger.info("Test data", testData);
991
+ // this.logger.info("Test data", testData);
992
+ console.log("Test data changed", testData);
960
993
  this.sendEvent(this.events.getTestData, testData);
961
994
  } catch (e) {
962
- this.logger.error("Error reading test data file", e);
995
+ // this.logger.error("Error reading test data file", e);
996
+ console.log("Error reading test data file", e);
963
997
  }
964
998
  });
965
999
  }
@@ -992,7 +1026,7 @@ export class BVTRecorder {
992
1026
  .filter((file) => file.endsWith(".feature"))
993
1027
  .map((file) => path.join(this.projectDir, "features", file));
994
1028
  try {
995
- const parsedFiles = featureFiles.map((file) => parseFeatureFile(file));
1029
+ const parsedFiles = featureFiles.map((file) => this.parseFeatureFile(file));
996
1030
  const output = {};
997
1031
  parsedFiles.forEach((file) => {
998
1032
  if (!file.feature) return;
@@ -1016,10 +1050,11 @@ export class BVTRecorder {
1016
1050
  const stepParams = parseStepTextParameters(stepName);
1017
1051
  return getCommandsForImplementedStep(stepName, step_definitions, stepParams).commands;
1018
1052
  }
1053
+
1019
1054
  loadExistingScenario({ featureName, scenarioName }) {
1020
1055
  const step_definitions = loadStepDefinitions(this.projectDir);
1021
1056
  const featureFilePath = path.join(this.projectDir, "features", featureName);
1022
- const gherkinDoc = parseFeatureFile(featureFilePath);
1057
+ const gherkinDoc = this.parseFeatureFile(featureFilePath);
1023
1058
  const scenario = gherkinDoc.feature.children.find((child) => child.scenario.name === scenarioName)?.scenario;
1024
1059
 
1025
1060
  const steps = [];
@@ -1044,6 +1079,7 @@ export class BVTRecorder {
1044
1079
  ..._s,
1045
1080
  keyword: step.keyword.trim(),
1046
1081
  };
1082
+ parseRouteFiles(this.projectDir, _step);
1047
1083
  steps.push(_step);
1048
1084
  }
1049
1085
  return {
@@ -1136,20 +1172,95 @@ export class BVTRecorder {
1136
1172
  return false;
1137
1173
  }
1138
1174
  }
1139
- }
1175
+ async initExecution({ tags = [] }) {
1176
+ // run before hooks
1177
+ const noopStep = {
1178
+ text: "Noop",
1179
+ isImplemented: true,
1180
+ };
1181
+ await this.runStep(
1182
+ {
1183
+ step: noopStep,
1184
+ parametersMap: {},
1185
+ tags,
1186
+ },
1187
+ {
1188
+ skipBefore: false,
1189
+ skipAfter: true,
1190
+ }
1191
+ );
1192
+ }
1193
+ async cleanupExecution({ tags = [] }) {
1194
+ // run after hooks
1195
+ const noopStep = {
1196
+ text: "Noop",
1197
+ isImplemented: true,
1198
+ };
1199
+ await this.runStep(
1200
+ {
1201
+ step: noopStep,
1202
+ parametersMap: {},
1203
+ tags,
1204
+ },
1205
+ {
1206
+ skipBefore: true,
1207
+ skipAfter: false,
1208
+ }
1209
+ );
1210
+ }
1211
+ async resetExecution({ tags = [] }) {
1212
+ // run after hooks followed by before hooks
1213
+ await this.cleanupExecution({ tags });
1214
+ await this.initExecution({ tags });
1215
+ }
1140
1216
 
1141
- const parseFeatureFile = (featureFilePath) => {
1142
- try {
1143
- let id = 0;
1144
- const uuidFn = () => (++id).toString(16);
1145
- const builder = new AstBuilder(uuidFn);
1146
- const matcher = new GherkinClassicTokenMatcher();
1147
- const parser = new Parser(builder, matcher);
1148
- const source = readFileSync(featureFilePath, "utf8");
1149
- const gherkinDocument = parser.parse(source);
1150
- return gherkinDocument;
1151
- } catch (e) {
1152
- console.log(e);
1217
+ parseFeatureFile(featureFilePath) {
1218
+ try {
1219
+ let id = 0;
1220
+ const uuidFn = () => (++id).toString(16);
1221
+ const builder = new AstBuilder(uuidFn);
1222
+ const matcher = new GherkinClassicTokenMatcher();
1223
+ const parser = new Parser(builder, matcher);
1224
+ const source = readFileSync(featureFilePath, "utf8");
1225
+ const gherkinDocument = parser.parse(source);
1226
+ return gherkinDocument;
1227
+ } catch (e) {
1228
+ this.logger.error(`Error parsing feature file: ${featureFilePath}`);
1229
+ console.log(e);
1230
+ }
1231
+ return {};
1153
1232
  }
1154
- return {};
1155
- };
1233
+
1234
+ stopRecordingNetwork(input) {
1235
+ if (this.bvtContext) {
1236
+ this.bvtContext.STORE_DETAILED_NETWORK_DATA = false;
1237
+ }
1238
+ }
1239
+
1240
+ async fakeParams(params) {
1241
+ const newFakeParams = {};
1242
+ Object.keys(params).forEach((key) => {
1243
+ if (!params[key].startsWith("{{") || !params[key].endsWith("}}")) {
1244
+ newFakeParams[key] = params[key];
1245
+ return;
1246
+ }
1247
+
1248
+ try {
1249
+ const value = params[key].substring(2, params[key].length - 2).trim();
1250
+ const faking = value.split("(")[0].split(".");
1251
+ let argument = value.substring(value.indexOf("(") + 1, value.lastIndexOf(")"));
1252
+ argument = isNaN(Number(argument)) ? argument : Number(argument);
1253
+ let fakeFunc = faker;
1254
+ faking.forEach((f) => {
1255
+ fakeFunc = fakeFunc[f];
1256
+ });
1257
+ const newValue = fakeFunc(argument);
1258
+ newFakeParams[key] = newValue;
1259
+ } catch (error) {
1260
+ newFakeParams[key] = params[key];
1261
+ }
1262
+ });
1263
+
1264
+ return newFakeParams;
1265
+ }
1266
+ }
@@ -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);