@dev-blinq/cucumber_client 1.0.1294-dev → 1.0.1294-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 (52) hide show
  1. package/bin/assets/bundled_scripts/recorder.js +106 -106
  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 +4 -16
  13. package/bin/assets/scripts/snapshot_capturer.js +153 -146
  14. package/bin/assets/scripts/unique_locators.js +924 -793
  15. package/bin/assets/scripts/yaml.js +796 -783
  16. package/bin/assets/templates/_hooks_template.txt +37 -0
  17. package/bin/assets/templates/utils_template.txt +1 -46
  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 +43 -12
  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 -84
  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 +174 -64
  35. package/bin/client/recorderv3/implemented_steps.js +74 -16
  36. package/bin/client/recorderv3/index.js +69 -25
  37. package/bin/client/recorderv3/network.js +299 -0
  38. package/bin/client/recorderv3/scriptTest.js +1 -1
  39. package/bin/client/recorderv3/services.js +4 -16
  40. package/bin/client/recorderv3/step_runner.js +329 -72
  41. package/bin/client/recorderv3/step_utils.js +570 -5
  42. package/bin/client/recorderv3/update_feature.js +32 -30
  43. package/bin/client/run_cucumber.js +5 -1
  44. package/bin/client/scenario_report.js +0 -5
  45. package/bin/client/test_scenario.js +0 -1
  46. package/bin/client/upload-service.js +2 -2
  47. package/bin/client/utils/socket_logger.js +132 -0
  48. package/bin/index.js +1 -0
  49. package/bin/logger.js +3 -2
  50. package/bin/min/consoleApi.min.cjs +2 -3
  51. package/bin/min/injectedScript.min.cjs +16 -16
  52. package/package.json +21 -12
@@ -3,7 +3,7 @@ import { closeContext, initContext, _getDataFile, resetTestData } from "automati
3
3
  import { existsSync, readdirSync, readFileSync, rmSync } from "fs";
4
4
  import path from "path";
5
5
  import url from "url";
6
- import { getImplementedSteps, getStepsAndCommandsForScenario } from "./implemented_steps.js";
6
+ import { getImplementedSteps, parseRouteFiles } from "./implemented_steps.js";
7
7
  import { NamesService } from "./services.js";
8
8
  import { BVTStepRunner } from "./step_runner.js";
9
9
  import { readFile, writeFile } from "fs/promises";
@@ -12,10 +12,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
- this.world = { attach: () => { } };
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);
@@ -290,11 +294,12 @@ export class BVTRecorder {
290
294
  }
291
295
 
292
296
  async _initBrowser({ url }) {
297
+ socketLogger.info("Only present in 1.0.1293-stage");
293
298
  this.#remoteDebuggerPort = await findAvailablePort();
294
299
  process.env.CDP_LISTEN_PORT = this.#remoteDebuggerPort;
295
300
 
296
- this.stepRunner.setRemoteDebugPort(this.#remoteDebuggerPort);
297
- this.world = { attach: () => { } };
301
+ // this.stepRunner.setRemoteDebugPort(this.#remoteDebuggerPort);
302
+ this.world = { attach: () => {} };
298
303
 
299
304
  const ai_config_file = path.join(this.projectDir, "ai_config.json");
300
305
  let ai_config = {};
@@ -302,9 +307,10 @@ export class BVTRecorder {
302
307
  try {
303
308
  ai_config = JSON.parse(readFileSync(ai_config_file, "utf8"));
304
309
  } catch (error) {
305
- console.error("Error reading ai_config.json", error);
310
+ this.logger.error("Error reading ai_config.json", error);
306
311
  }
307
312
  }
313
+ this.config = ai_config;
308
314
  const initScripts = {
309
315
  // recorderCjs: injectedScriptSource,
310
316
  scripts: [
@@ -313,16 +319,37 @@ export class BVTRecorder {
313
319
  ],
314
320
  };
315
321
 
316
- let startTime = Date.now();
317
322
  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
323
  this.bvtContext = bvtContext;
324
+ this.stepRunner = new BVTStepRunner({
325
+ projectDir: this.projectDir,
326
+ sendExecutionStatus: (data) => {
327
+ if (data && data.type) {
328
+ switch (data.type) {
329
+ case "cmdExecutionStart":
330
+ this.sendEvent(this.events.cmdExecutionStart, data);
331
+ break;
332
+ case "cmdExecutionSuccess":
333
+ this.sendEvent(this.events.cmdExecutionSuccess, data);
334
+ break;
335
+ case "cmdExecutionError":
336
+ this.sendEvent(this.events.cmdExecutionError, data);
337
+ break;
338
+ case "interceptResults":
339
+ this.sendEvent(this.events.interceptResults, data);
340
+ break;
341
+ default:
342
+ break;
343
+ }
344
+ }
345
+ },
346
+ bvtContext: this.bvtContext,
347
+ });
321
348
  const context = bvtContext.playContext;
322
349
  this.context = context;
323
350
  this.web = bvtContext.stable || bvtContext.web;
351
+ this.web.tryAllStrategies = true;
324
352
  this.page = bvtContext.page;
325
-
326
353
  this.pageSet.add(this.page);
327
354
  this.lastKnownUrlPath = this._updateUrlPath();
328
355
  const browser = await this.context.browser();
@@ -350,13 +377,15 @@ export class BVTRecorder {
350
377
  }
351
378
  return;
352
379
  } catch (error) {
353
- console.error("Error evaluting in context:", error);
380
+ // console.error("Error evaluting in context:", error);
381
+ this.logger.error("Error evaluating in context:", error);
354
382
  }
355
383
  }
356
384
  }
357
385
 
358
386
  getMode() {
359
- console.log("getMode", this.#mode);
387
+ // console.log("getMode", this.#mode);
388
+ this.logger.info("Current mode:", this.#mode);
360
389
  return this.#mode;
361
390
  }
362
391
 
@@ -398,6 +427,8 @@ export class BVTRecorder {
398
427
  this.sendEvent(this.events.onBrowserClose);
399
428
  }
400
429
  } catch (error) {
430
+ this.logger.error("Error in page close event");
431
+ this.logger.error(error);
401
432
  console.error("Error in page close event");
402
433
  console.error(error);
403
434
  }
@@ -408,8 +439,10 @@ export class BVTRecorder {
408
439
  if (frame !== page.mainFrame()) return;
409
440
  this.handlePageTransition();
410
441
  } catch (error) {
442
+ this.logger.error("Error in handlePageTransition event");
443
+ this.logger.error(error);
411
444
  console.error("Error in handlePageTransition event");
412
- // console.error(error);
445
+ console.error(error);
413
446
  }
414
447
  try {
415
448
  if (frame !== this.#activeFrame) return;
@@ -428,6 +461,8 @@ export class BVTRecorder {
428
461
  // await this._setRecordingMode(frame);
429
462
  // await this._initPage(page);
430
463
  } catch (error) {
464
+ this.logger.error("Error in frame navigate event");
465
+ this.logger.error(error);
431
466
  console.error("Error in frame navigate event");
432
467
  // console.error(error);
433
468
  }
@@ -510,13 +545,9 @@ export class BVTRecorder {
510
545
 
511
546
  try {
512
547
  const result = await client.send("Page.getNavigationHistory");
513
- // console.log("Navigation History:", result);
514
548
  const entries = result.entries;
515
549
  const currentIndex = result.currentIndex;
516
550
 
517
- // ignore if currentIndex is not the last entry
518
- // if (currentIndex !== entries.length - 1) return;
519
-
520
551
  const currentEntry = entries[currentIndex];
521
552
  const transitionInfo = this.analyzeTransitionType(entries, currentIndex, currentEntry);
522
553
  this.previousIndex = currentIndex;
@@ -529,6 +560,8 @@ export class BVTRecorder {
529
560
  navigationAction: transitionInfo.action,
530
561
  };
531
562
  } catch (error) {
563
+ this.logger.error("Error in getCurrentTransition event");
564
+ this.logger.error(error);
532
565
  console.error("Error in getTransistionType event", error);
533
566
  } finally {
534
567
  await client.detach();
@@ -597,6 +630,8 @@ export class BVTRecorder {
597
630
  // add listener for frame navigation on new tab
598
631
  this._addFrameNavigateListener(page);
599
632
  } catch (error) {
633
+ this.logger.error("Error in page event");
634
+ this.logger.error(error);
600
635
  console.error("Error in page event");
601
636
  console.error(error);
602
637
  }
@@ -638,6 +673,7 @@ export class BVTRecorder {
638
673
  const { data } = await client.send("Page.captureScreenshot", { format: "png" });
639
674
  return data;
640
675
  } catch (error) {
676
+ this.logger.error("Error in taking browser screenshot");
641
677
  console.error("Error in taking browser screenshot", error);
642
678
  } finally {
643
679
  await client.detach();
@@ -700,7 +736,7 @@ export class BVTRecorder {
700
736
  }
701
737
  async closeBrowser() {
702
738
  delete process.env.TEMP_RUN;
703
- await this.watcher.close().then(() => { });
739
+ await this.watcher.close().then(() => {});
704
740
  this.watcher = null;
705
741
  this.previousIndex = null;
706
742
  this.previousHistoryLength = null;
@@ -783,42 +819,65 @@ export class BVTRecorder {
783
819
  async abortExecution() {
784
820
  await this.stepRunner.abortExecution();
785
821
  }
822
+
823
+ async pauseExecution({ cmdId }) {
824
+ await this.stepRunner.pauseExecution(cmdId);
825
+ }
826
+
827
+ async resumeExecution({ cmdId }) {
828
+ await this.stepRunner.resumeExecution(cmdId);
829
+ }
830
+
786
831
  async dealyedRevertMode() {
787
832
  const timerId = setTimeout(async () => {
788
833
  await this.revertMode();
789
834
  }, 100);
790
835
  this.timerId = timerId;
791
836
  }
792
- async runStep({ step, parametersMap, tags }, options) {
837
+ async runStep({ step, parametersMap, tags, isFirstStep, listenNetwork }, options) {
838
+ const { skipAfter = true, skipBefore = !isFirstStep } = options || {};
793
839
  const _env = {
794
840
  TOKEN: this.TOKEN,
795
841
  TEMP_RUN: true,
796
842
  REPORT_FOLDER: this.bvtContext.reportFolder,
797
843
  BLINQ_ENV: this.envName,
844
+ DEBUG: "blinq:route",
798
845
  };
799
846
 
800
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
+ }
801
854
  for (const [key, value] of Object.entries(_env)) {
802
855
  process.env[key] = value;
803
856
  }
857
+
804
858
  if (this.timerId) {
805
859
  clearTimeout(this.timerId);
806
860
  this.timerId = null;
807
861
  }
808
862
  await this.setMode("running");
863
+
809
864
  try {
810
865
  const { result, info } = await this.stepRunner.runStep(
811
866
  {
812
867
  step,
813
868
  parametersMap,
814
869
  envPath: this.envName,
815
- tags
870
+ tags,
871
+ config: this.config,
816
872
  },
817
873
  this.bvtContext,
818
- options
874
+ {
875
+ skipAfter,
876
+ skipBefore,
877
+ }
819
878
  );
820
879
  await this.revertMode();
821
- return { result, info };
880
+ return { info };
822
881
  } catch (error) {
823
882
  await this.revertMode();
824
883
  throw error;
@@ -829,15 +888,10 @@ export class BVTRecorder {
829
888
  this.bvtContext.navigate = false;
830
889
  }
831
890
  }
832
- async runScenario({ steps, parametersMap, tags }) {
833
- for (const step of steps) {
834
- await this.runStep({ step, parametersMap, tags });
835
- }
836
- }
837
891
  async saveScenario({ scenario, featureName, override, isSingleStep }) {
838
892
  await updateStepDefinitions({ scenario, featureName, projectDir: this.projectDir }); // updates mjs files
839
893
  if (!isSingleStep) await updateFeatureFile({ featureName, scenario, override, projectDir: this.projectDir }); // updates gherkin files
840
- await this.cleanup();
894
+ await this.cleanup({ tags: scenario.tags });
841
895
  }
842
896
  async getImplementedSteps() {
843
897
  const stepsAndScenarios = await getImplementedSteps(this.projectDir);
@@ -861,6 +915,7 @@ export class BVTRecorder {
861
915
  step.commands = [];
862
916
  }
863
917
  }
918
+ return steps;
864
919
  // return getStepsAndCommandsForScenario({
865
920
  // name,
866
921
  // featureName,
@@ -868,6 +923,7 @@ export class BVTRecorder {
868
923
  // map: this.scenariosStepsMap,
869
924
  // });
870
925
  }
926
+
871
927
  async generateStepName({ commands, stepsNames, parameters, map }) {
872
928
  return await this.namesService.generateStepName({ commands, stepsNames, parameters, map });
873
929
  }
@@ -919,10 +975,11 @@ export class BVTRecorder {
919
975
  if (existsSync(_getDataFile(this.world, this.bvtContext, this.web))) {
920
976
  try {
921
977
  const testData = JSON.parse(readFileSync(_getDataFile(this.world, this.bvtContext, this.web), "utf8"));
922
- this.logger.info("Test data", testData);
978
+ // this.logger.info("Test data", testData);
923
979
  this.sendEvent(this.events.getTestData, testData);
924
980
  } catch (e) {
925
- 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);
926
983
  }
927
984
  }
928
985
 
@@ -931,10 +988,12 @@ export class BVTRecorder {
931
988
  this.watcher.on("all", async (event, path) => {
932
989
  try {
933
990
  const testData = JSON.parse(await readFile(_getDataFile(this.world, this.bvtContext, this.web), "utf8"));
934
- this.logger.info("Test data", testData);
991
+ // this.logger.info("Test data", testData);
992
+ console.log("Test data changed", testData);
935
993
  this.sendEvent(this.events.getTestData, testData);
936
994
  } catch (e) {
937
- 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);
938
997
  }
939
998
  });
940
999
  }
@@ -950,9 +1009,9 @@ export class BVTRecorder {
950
1009
  }
951
1010
  }
952
1011
 
953
- async discardTestData() {
1012
+ async discardTestData({ tags }) {
954
1013
  resetTestData(this.envName, this.world);
955
- await this.cleanup();
1014
+ await this.cleanup({ tags });
956
1015
  }
957
1016
  async addToTestData(obj) {
958
1017
  if (!existsSync(_getDataFile(this.world, this.bvtContext, this.web))) {
@@ -967,7 +1026,7 @@ export class BVTRecorder {
967
1026
  .filter((file) => file.endsWith(".feature"))
968
1027
  .map((file) => path.join(this.projectDir, "features", file));
969
1028
  try {
970
- const parsedFiles = featureFiles.map((file) => parseFeatureFile(file));
1029
+ const parsedFiles = featureFiles.map((file) => this.parseFeatureFile(file));
971
1030
  const output = {};
972
1031
  parsedFiles.forEach((file) => {
973
1032
  if (!file.feature) return;
@@ -991,10 +1050,11 @@ export class BVTRecorder {
991
1050
  const stepParams = parseStepTextParameters(stepName);
992
1051
  return getCommandsForImplementedStep(stepName, step_definitions, stepParams).commands;
993
1052
  }
1053
+
994
1054
  loadExistingScenario({ featureName, scenarioName }) {
995
1055
  const step_definitions = loadStepDefinitions(this.projectDir);
996
1056
  const featureFilePath = path.join(this.projectDir, "features", featureName);
997
- const gherkinDoc = parseFeatureFile(featureFilePath);
1057
+ const gherkinDoc = this.parseFeatureFile(featureFilePath);
998
1058
  const scenario = gherkinDoc.feature.children.find((child) => child.scenario.name === scenarioName)?.scenario;
999
1059
 
1000
1060
  const steps = [];
@@ -1019,6 +1079,7 @@ export class BVTRecorder {
1019
1079
  ..._s,
1020
1080
  keyword: step.keyword.trim(),
1021
1081
  };
1082
+ parseRouteFiles(this.projectDir, _step);
1022
1083
  steps.push(_step);
1023
1084
  }
1024
1085
  return {
@@ -1058,7 +1119,7 @@ export class BVTRecorder {
1058
1119
  }
1059
1120
  return result;
1060
1121
  }
1061
- async cleanup() {
1122
+ async cleanup({ tags }) {
1062
1123
  const noopStep = {
1063
1124
  text: "Noop",
1064
1125
  isImplemented: true,
@@ -1072,6 +1133,7 @@ export class BVTRecorder {
1072
1133
  {
1073
1134
  step: noopStep,
1074
1135
  parametersMap: {},
1136
+ tags: tags || [],
1075
1137
  },
1076
1138
  {
1077
1139
  skipAfter: false,
@@ -1110,20 +1172,68 @@ export class BVTRecorder {
1110
1172
  return false;
1111
1173
  }
1112
1174
  }
1113
- }
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
+ }
1114
1216
 
1115
- const parseFeatureFile = (featureFilePath) => {
1116
- try {
1117
- let id = 0;
1118
- const uuidFn = () => (++id).toString(16);
1119
- const builder = new AstBuilder(uuidFn);
1120
- const matcher = new GherkinClassicTokenMatcher();
1121
- const parser = new Parser(builder, matcher);
1122
- const source = readFileSync(featureFilePath, "utf8");
1123
- const gherkinDocument = parser.parse(source);
1124
- return gherkinDocument;
1125
- } catch (e) {
1126
- 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 {};
1127
1232
  }
1128
- return {};
1129
- };
1233
+
1234
+ stopRecordingNetwork(input) {
1235
+ if (this.bvtContext) {
1236
+ this.bvtContext.STORE_DETAILED_NETWORK_DATA = false;
1237
+ }
1238
+ }
1239
+ }
@@ -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);