@dev-blinq/cucumber_client 1.0.1363-dev → 1.0.1363-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 (48) hide show
  1. package/bin/assets/bundled_scripts/recorder.js +107 -107
  2. package/bin/assets/preload/css_gen.js +10 -10
  3. package/bin/assets/preload/toolbar.js +27 -29
  4. package/bin/assets/preload/unique_locators.js +1 -1
  5. package/bin/assets/preload/yaml.js +288 -275
  6. package/bin/assets/scripts/aria_snapshot.js +223 -220
  7. package/bin/assets/scripts/dom_attr.js +329 -329
  8. package/bin/assets/scripts/dom_parent.js +169 -174
  9. package/bin/assets/scripts/event_utils.js +94 -94
  10. package/bin/assets/scripts/pw.js +2050 -1949
  11. package/bin/assets/scripts/recorder.js +13 -23
  12. package/bin/assets/scripts/snapshot_capturer.js +147 -147
  13. package/bin/assets/scripts/unique_locators.js +163 -44
  14. package/bin/assets/scripts/yaml.js +796 -783
  15. package/bin/assets/templates/_hooks_template.txt +41 -0
  16. package/bin/assets/templates/utils_template.txt +2 -45
  17. package/bin/client/apiTest/apiTest.js +6 -0
  18. package/bin/client/code_cleanup/utils.js +5 -1
  19. package/bin/client/code_gen/api_codegen.js +2 -2
  20. package/bin/client/code_gen/code_inversion.js +107 -2
  21. package/bin/client/code_gen/function_signature.js +4 -0
  22. package/bin/client/code_gen/page_reflection.js +846 -906
  23. package/bin/client/code_gen/playwright_codeget.js +45 -12
  24. package/bin/client/cucumber/feature.js +4 -0
  25. package/bin/client/cucumber/feature_data.js +2 -2
  26. package/bin/client/cucumber/project_to_document.js +9 -3
  27. package/bin/client/cucumber/steps_definitions.js +6 -3
  28. package/bin/client/cucumber_selector.js +17 -1
  29. package/bin/client/local_agent.js +4 -3
  30. package/bin/client/parse_feature_file.js +23 -26
  31. package/bin/client/playground/projects/env.json +2 -2
  32. package/bin/client/project.js +186 -196
  33. package/bin/client/recorderv3/bvt_recorder.js +238 -95
  34. package/bin/client/recorderv3/implemented_steps.js +8 -0
  35. package/bin/client/recorderv3/index.js +59 -54
  36. package/bin/client/recorderv3/scriptTest.js +1 -1
  37. package/bin/client/recorderv3/services.js +66 -16
  38. package/bin/client/recorderv3/step_runner.js +315 -205
  39. package/bin/client/recorderv3/step_utils.js +476 -16
  40. package/bin/client/recorderv3/update_feature.js +9 -5
  41. package/bin/client/recording.js +1 -0
  42. package/bin/client/upload-service.js +3 -2
  43. package/bin/client/utils/socket_logger.js +132 -0
  44. package/bin/index.js +4 -0
  45. package/bin/logger.js +3 -2
  46. package/bin/min/consoleApi.min.cjs +2 -3
  47. package/bin/min/injectedScript.min.cjs +16 -16
  48. package/package.json +20 -10
@@ -4,7 +4,7 @@ import { existsSync, readdirSync, readFileSync, rmSync } from "fs";
4
4
  import path from "path";
5
5
  import url from "url";
6
6
  import { getImplementedSteps, parseRouteFiles } from "./implemented_steps.js";
7
- import { NamesService } from "./services.js";
7
+ import { NamesService, PublishService } from "./services.js";
8
8
  import { BVTStepRunner } from "./step_runner.js";
9
9
  import { readFile, writeFile } from "fs/promises";
10
10
  import { updateStepDefinitions, loadStepDefinitions, getCommandsForImplementedStep } from "./step_utils.js";
@@ -12,11 +12,12 @@ 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 { Step } from "../cucumber/feature.js";
19
-
17
+ import socketLogger from "../utils/socket_logger.js";
18
+ import { tmpdir } from "os";
19
+ import { faker } from "@faker-js/faker/locale/en_US";
20
+ import { chromium } from "playwright-core";
20
21
  const __dirname = path.dirname(url.fileURLToPath(import.meta.url));
21
22
 
22
23
  const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
@@ -45,7 +46,6 @@ async function evaluate(frame, script) {
45
46
  async function findNestedFrameSelector(frame, obj) {
46
47
  try {
47
48
  const parent = frame.parentFrame();
48
- if (parent) console.log(`Parent frame: ${JSON.stringify(parent)}`);
49
49
  if (!parent) return { children: obj };
50
50
  const frameElement = await frame.frameElement();
51
51
  if (!frameElement) return;
@@ -54,6 +54,7 @@ async function findNestedFrameSelector(frame, obj) {
54
54
  }, frameElement);
55
55
  return findNestedFrameSelector(parent, { children: obj, selectors });
56
56
  } catch (e) {
57
+ socketLogger.error(`Error in findNestedFrameSelector: ${e}`);
57
58
  console.error(e);
58
59
  }
59
60
  }
@@ -150,17 +151,30 @@ const transformAction = (action, el, isVerify, isPopupCloseClick, isInHoverMode,
150
151
  };
151
152
  }
152
153
  default: {
154
+ socketLogger.error(`Action not supported: ${action.name}`);
153
155
  console.log("action not supported", action);
154
156
  throw new Error("action not supported");
155
157
  }
156
158
  }
157
159
  };
160
+ const diffPaths = (currentPath, newPath) => {
161
+ const currentDomain = new URL(currentPath).hostname;
162
+ const newDomain = new URL(newPath).hostname;
163
+ if (currentDomain !== newDomain) {
164
+ return true;
165
+ } else {
166
+ const currentRoute = new URL(currentPath).pathname;
167
+ const newRoute = new URL(newPath).pathname;
168
+ return currentRoute !== newRoute;
169
+ }
170
+ };
158
171
  /**
159
172
  * @typedef {Object} BVTRecorderInput
160
173
  * @property {string} envName
161
174
  * @property {string} projectDir
162
175
  * @property {string} TOKEN
163
176
  * @property {(name:string, data:any)=> void} sendEvent
177
+ * @property {Object} logger
164
178
  */
165
179
  export class BVTRecorder {
166
180
  #currentURL = "";
@@ -174,7 +188,6 @@ export class BVTRecorder {
174
188
  */
175
189
  constructor(initialState) {
176
190
  Object.assign(this, initialState);
177
- this.logger = logger;
178
191
  this.screenshotMap = new Map();
179
192
  this.snapshotMap = new Map();
180
193
  this.scenariosStepsMap = new Map();
@@ -184,40 +197,21 @@ export class BVTRecorder {
184
197
  projectDir: this.projectDir,
185
198
  logger: this.logger,
186
199
  });
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);
194
- this.sendEvent(this.events.cmdExecutionStart, data);
195
- break;
196
- case "cmdExecutionSuccess":
197
- console.log("Sending cmdExecutionSuccess event for cmdId:", data);
198
- this.sendEvent(this.events.cmdExecutionSuccess, data);
199
- break;
200
- case "cmdExecutionError":
201
- console.log("Sending cmdExecutionError event for cmdId:", data);
202
- this.sendEvent(this.events.cmdExecutionError, data);
203
- break;
204
- case "interceptResults":
205
- console.log("Sending interceptResults event");
206
- this.sendEvent(this.events.interceptResults, data);
207
- break;
208
- default:
209
- console.warn("Unknown command execution status type:", data.type);
210
- break;
211
- }
212
- }
213
- },
214
- });
200
+ this.workspaceService = new PublishService(this.TOKEN);
215
201
  this.pageSet = new Set();
202
+ this.pageMetaDataSet = new Set();
216
203
  this.lastKnownUrlPath = "";
217
- // TODO: what is world?
218
204
  this.world = { attach: () => {} };
219
205
  this.shouldTakeScreenshot = true;
220
206
  this.watcher = null;
207
+ this.networkEventsFolder = path.join(tmpdir(), "blinq_network_events");
208
+
209
+ this.tempProjectFolder = `${tmpdir()}/bvt_temp_project_${Math.floor(Math.random() * 1000000)}`;
210
+ this.tempSnapshotsFolder = path.join(this.tempProjectFolder, "data/snapshots");
211
+
212
+ if (existsSync(this.networkEventsFolder)) {
213
+ rmSync(this.networkEventsFolder, { recursive: true, force: true });
214
+ }
221
215
  }
222
216
  events = {
223
217
  onFrameNavigate: "BVTRecorder.onFrameNavigate",
@@ -232,12 +226,14 @@ export class BVTRecorder {
232
226
  cmdExecutionSuccess: "BVTRecorder.cmdExecutionSuccess",
233
227
  cmdExecutionError: "BVTRecorder.cmdExecutionError",
234
228
  interceptResults: "BVTRecorder.interceptResults",
229
+ onDebugURLChange: "BVTRecorder.onDebugURLChange",
230
+ updateCommand: "BVTRecorder.updateCommand",
235
231
  };
236
232
  bindings = {
237
233
  __bvt_recordCommand: async ({ frame, page, context }, event) => {
238
234
  this.#activeFrame = frame;
239
235
  const nestFrmLoc = await findNestedFrameSelector(frame);
240
- console.log(`Time taken for action: ${event.statistics.time}`);
236
+ this.logger.info(`Time taken for action: ${event.statistics.time}`);
241
237
  await this.onAction({ ...event, nestFrmLoc });
242
238
  },
243
239
  __bvt_getMode: async () => {
@@ -256,8 +252,7 @@ export class BVTRecorder {
256
252
  await this.onClosePopup();
257
253
  },
258
254
  __bvt_log: async (src, message) => {
259
- // this.logger.info(message);
260
- console.log(`Inside Browser: ${message}`);
255
+ this.logger.info(`Inside Browser: ${message}`);
261
256
  },
262
257
  __bvt_getObject: (_src, obj) => {
263
258
  this.processObject(obj);
@@ -319,10 +314,11 @@ export class BVTRecorder {
319
314
  }
320
315
 
321
316
  async _initBrowser({ url }) {
317
+ socketLogger.info("Only present in 1.0.1293-stage");
322
318
  this.#remoteDebuggerPort = await findAvailablePort();
323
319
  process.env.CDP_LISTEN_PORT = this.#remoteDebuggerPort;
324
320
 
325
- this.stepRunner.setRemoteDebugPort(this.#remoteDebuggerPort);
321
+ // this.stepRunner.setRemoteDebugPort(this.#remoteDebuggerPort);
326
322
  this.world = { attach: () => {} };
327
323
 
328
324
  const ai_config_file = path.join(this.projectDir, "ai_config.json");
@@ -331,7 +327,7 @@ export class BVTRecorder {
331
327
  try {
332
328
  ai_config = JSON.parse(readFileSync(ai_config_file, "utf8"));
333
329
  } catch (error) {
334
- console.error("Error reading ai_config.json", error);
330
+ this.logger.error("Error reading ai_config.json", error);
335
331
  }
336
332
  }
337
333
  this.config = ai_config;
@@ -343,16 +339,37 @@ export class BVTRecorder {
343
339
  ],
344
340
  };
345
341
 
346
- let startTime = Date.now();
347
342
  const bvtContext = await initContext(url, false, false, this.world, 450, initScripts, this.envName);
348
- let stopTime = Date.now();
349
- this.logger.info(`Browser launched in ${(stopTime - startTime) / 1000} s`);
350
343
  this.bvtContext = bvtContext;
344
+ this.stepRunner = new BVTStepRunner({
345
+ projectDir: this.projectDir,
346
+ sendExecutionStatus: (data) => {
347
+ if (data && data.type) {
348
+ switch (data.type) {
349
+ case "cmdExecutionStart":
350
+ this.sendEvent(this.events.cmdExecutionStart, data);
351
+ break;
352
+ case "cmdExecutionSuccess":
353
+ this.sendEvent(this.events.cmdExecutionSuccess, data);
354
+ break;
355
+ case "cmdExecutionError":
356
+ this.sendEvent(this.events.cmdExecutionError, data);
357
+ break;
358
+ case "interceptResults":
359
+ this.sendEvent(this.events.interceptResults, data);
360
+ break;
361
+ default:
362
+ break;
363
+ }
364
+ }
365
+ },
366
+ bvtContext: this.bvtContext,
367
+ });
351
368
  const context = bvtContext.playContext;
352
369
  this.context = context;
353
370
  this.web = bvtContext.stable || bvtContext.web;
371
+ this.web.tryAllStrategies = true;
354
372
  this.page = bvtContext.page;
355
-
356
373
  this.pageSet.add(this.page);
357
374
  this.lastKnownUrlPath = this._updateUrlPath();
358
375
  const browser = await this.context.browser();
@@ -366,6 +383,14 @@ export class BVTRecorder {
366
383
  this.web.onRestoreSaveState = (url) => {
367
384
  this._initBrowser({ url });
368
385
  };
386
+
387
+ // create a second browser for locator generation
388
+ this.backgroundBrowser = await chromium.launch({
389
+ headless: true,
390
+ });
391
+ this.backgroundContext = await this.backgroundBrowser.newContext({});
392
+ await this.backgroundContext.addInitScript({ content: this.getInitScripts(this.config) });
393
+ await this.backgroundContext.newPage();
369
394
  }
370
395
  async onClosePopup() {
371
396
  // console.log("close popups");
@@ -380,13 +405,15 @@ export class BVTRecorder {
380
405
  }
381
406
  return;
382
407
  } catch (error) {
383
- console.error("Error evaluting in context:", error);
408
+ // console.error("Error evaluting in context:", error);
409
+ this.logger.error("Error evaluating in context:", error);
384
410
  }
385
411
  }
386
412
  }
387
413
 
388
414
  getMode() {
389
- console.log("getMode", this.#mode);
415
+ // console.log("getMode", this.#mode);
416
+ this.logger.info("Current mode:", this.#mode);
390
417
  return this.#mode;
391
418
  }
392
419
 
@@ -411,7 +438,7 @@ export class BVTRecorder {
411
438
 
412
439
  // eval init script on current tab
413
440
  // await this._initPage(this.page);
414
- this.#currentURL = new URL(url).pathname;
441
+ this.#currentURL = url;
415
442
 
416
443
  await this.page.dispatchEvent("html", "scroll");
417
444
  await delay(1000);
@@ -428,6 +455,8 @@ export class BVTRecorder {
428
455
  this.sendEvent(this.events.onBrowserClose);
429
456
  }
430
457
  } catch (error) {
458
+ this.logger.error("Error in page close event");
459
+ this.logger.error(error);
431
460
  console.error("Error in page close event");
432
461
  console.error(error);
433
462
  }
@@ -438,8 +467,10 @@ export class BVTRecorder {
438
467
  if (frame !== page.mainFrame()) return;
439
468
  this.handlePageTransition();
440
469
  } catch (error) {
470
+ this.logger.error("Error in handlePageTransition event");
471
+ this.logger.error(error);
441
472
  console.error("Error in handlePageTransition event");
442
- // console.error(error);
473
+ console.error(error);
443
474
  }
444
475
  try {
445
476
  if (frame !== this.#activeFrame) return;
@@ -449,15 +480,18 @@ export class BVTRecorder {
449
480
  element: { inputID: "frame" },
450
481
  });
451
482
 
452
- const newPath = new URL(frame.url()).pathname;
483
+ const newUrl = frame.url();
484
+ const newPath = new URL(newUrl).pathname;
453
485
  const newTitle = await frame.title();
454
- if (newPath !== this.#currentURL) {
486
+ const changed = diffPaths(this.#currentURL, newUrl);
487
+
488
+ if (changed) {
455
489
  this.sendEvent(this.events.onFrameNavigate, { url: newPath, title: newTitle });
456
- this.#currentURL = newPath;
490
+ this.#currentURL = newUrl;
457
491
  }
458
- // await this._setRecordingMode(frame);
459
- // await this._initPage(page);
460
492
  } catch (error) {
493
+ this.logger.error("Error in frame navigate event");
494
+ this.logger.error(error);
461
495
  console.error("Error in frame navigate event");
462
496
  // console.error(error);
463
497
  }
@@ -540,13 +574,9 @@ export class BVTRecorder {
540
574
 
541
575
  try {
542
576
  const result = await client.send("Page.getNavigationHistory");
543
- // console.log("Navigation History:", result);
544
577
  const entries = result.entries;
545
578
  const currentIndex = result.currentIndex;
546
579
 
547
- // ignore if currentIndex is not the last entry
548
- // if (currentIndex !== entries.length - 1) return;
549
-
550
580
  const currentEntry = entries[currentIndex];
551
581
  const transitionInfo = this.analyzeTransitionType(entries, currentIndex, currentEntry);
552
582
  this.previousIndex = currentIndex;
@@ -559,6 +589,8 @@ export class BVTRecorder {
559
589
  navigationAction: transitionInfo.action,
560
590
  };
561
591
  } catch (error) {
592
+ this.logger.error("Error in getCurrentTransition event");
593
+ this.logger.error(error);
562
594
  console.error("Error in getTransistionType event", error);
563
595
  } finally {
564
596
  await client.detach();
@@ -627,6 +659,8 @@ export class BVTRecorder {
627
659
  // add listener for frame navigation on new tab
628
660
  this._addFrameNavigateListener(page);
629
661
  } catch (error) {
662
+ this.logger.error("Error in page event");
663
+ this.logger.error(error);
630
664
  console.error("Error in page event");
631
665
  console.error(error);
632
666
  }
@@ -668,6 +702,7 @@ export class BVTRecorder {
668
702
  const { data } = await client.send("Page.captureScreenshot", { format: "png" });
669
703
  return data;
670
704
  } catch (error) {
705
+ this.logger.error("Error in taking browser screenshot");
671
706
  console.error("Error in taking browser screenshot", error);
672
707
  } finally {
673
708
  await client.detach();
@@ -683,6 +718,52 @@ export class BVTRecorder {
683
718
  console.error("Error in saving screenshot: ", error);
684
719
  }
685
720
  }
721
+ async generateLocators(event) {
722
+ const snapshotDetails = event.snapshotDetails;
723
+ if (!snapshotDetails) {
724
+ throw new Error("No snapshot details found");
725
+ }
726
+ const mode = event.mode;
727
+ const inputID = event.element.inputID;
728
+
729
+ const { id, contextId, doc } = snapshotDetails;
730
+ // const selector = `[data-blinq-id="${id}"]`;
731
+ const newPage = await this.backgroundContext.newPage();
732
+ await newPage.setContent(doc, { waitUntil: "domcontentloaded" });
733
+ const locatorsObj = await newPage.evaluate(
734
+ ([id, contextId, mode]) => {
735
+ const recorder = window.__bvt_Recorder;
736
+ const contextElement = document.querySelector(`[data-blinq-context-id="${contextId}"]`);
737
+ const el = document.querySelector(`[data-blinq-id="${id}"]`);
738
+ if (contextElement) {
739
+ const result = recorder.locatorGenerator.toContextLocators(el, contextElement);
740
+ return result;
741
+ }
742
+ const isRecordingText = mode === "recordingText";
743
+ return recorder.locatorGenerator.getElementLocators(el, {
744
+ excludeText: isRecordingText,
745
+ });
746
+ },
747
+ [id, contextId, mode]
748
+ );
749
+
750
+ console.log(`Generated locators: for ${inputID}: `, JSON.stringify(locatorsObj));
751
+ await newPage.close();
752
+ if (event.nestFrmLoc?.children) {
753
+ locatorsObj.nestFrmLoc = event.nestFrmLoc.children;
754
+ }
755
+
756
+ this.sendEvent(this.events.updateCommand, {
757
+ locators: {
758
+ locators: locatorsObj.locators,
759
+ nestFrmLoc: locatorsObj.nestFrmLoc,
760
+ iframe_src: !event.frame.isTop ? event.frame.url : undefined,
761
+ },
762
+ allStrategyLocators: locatorsObj.allStrategyLocators,
763
+ inputID,
764
+ });
765
+ // const
766
+ }
686
767
  async onAction(event) {
687
768
  this._updateUrlPath();
688
769
  // const locators = this.overlayLocators(event);
@@ -696,25 +777,26 @@ export class BVTRecorder {
696
777
  event.mode === "recordingHover",
697
778
  event.mode === "multiInspecting"
698
779
  ),
699
- locators: {
700
- locators: event.locators,
701
- iframe_src: !event.frame.isTop ? event.frame.url : undefined,
702
- },
703
- allStrategyLocators: event.allStrategyLocators,
780
+ // locators: {
781
+ // locators: event.locators,
782
+ // iframe_src: !event.frame.isTop ? event.frame.url : undefined,
783
+ // },
784
+ // allStrategyLocators: event.allStrategyLocators,
704
785
  url: event.frame.url,
705
786
  title: event.frame.title,
706
787
  extract: {},
707
788
  lastKnownUrlPath: this.lastKnownUrlPath,
708
789
  };
709
- if (event.nestFrmLoc?.children) {
710
- cmdEvent.locators.nestFrmLoc = event.nestFrmLoc.children;
711
- }
790
+ // if (event.nestFrmLoc?.children) {
791
+ // cmdEvent.locators.nestFrmLoc = event.nestFrmLoc.children;
792
+ // }
712
793
  // this.logger.info({ event });
713
794
  if (this.shouldTakeScreenshot) {
714
795
  await this.storeScreenshot(event);
715
796
  }
716
797
  this.sendEvent(this.events.onNewCommand, cmdEvent);
717
798
  this._updateUrlPath();
799
+ await this.generateLocators(event);
718
800
  }
719
801
  _updateUrlPath() {
720
802
  try {
@@ -788,7 +870,6 @@ export class BVTRecorder {
788
870
  }
789
871
 
790
872
  async startRecordingInput() {
791
- console.log("startRecordingInput");
792
873
  await this.setMode("recordingInput");
793
874
  }
794
875
  async stopRecordingInput() {
@@ -812,9 +893,17 @@ export class BVTRecorder {
812
893
  }
813
894
 
814
895
  async abortExecution() {
815
- this.bvtContext.web.abortedExecution = true;
816
896
  await this.stepRunner.abortExecution();
817
897
  }
898
+
899
+ async pauseExecution({ cmdId }) {
900
+ await this.stepRunner.pauseExecution(cmdId);
901
+ }
902
+
903
+ async resumeExecution({ cmdId }) {
904
+ await this.stepRunner.resumeExecution(cmdId);
905
+ }
906
+
818
907
  async dealyedRevertMode() {
819
908
  const timerId = setTimeout(async () => {
820
909
  await this.revertMode();
@@ -828,13 +917,17 @@ export class BVTRecorder {
828
917
  TEMP_RUN: true,
829
918
  REPORT_FOLDER: this.bvtContext.reportFolder,
830
919
  BLINQ_ENV: this.envName,
831
- STORE_DETAILED_NETWORK_DATA: listenNetwork ? "true" : "false",
832
- CURRENT_STEP_ID: step.id,
920
+ DEBUG: "blinq:route",
921
+ BVT_TEMP_SNAPSHOTS_FOLDER: this.tempSnapshotsFolder,
833
922
  };
834
923
 
835
924
  this.bvtContext.navigate = true;
836
925
  this.bvtContext.loadedRoutes = null;
837
- this.bvtContext.web.abortedExecution = false;
926
+ if (listenNetwork) {
927
+ this.bvtContext.STORE_DETAILED_NETWORK_DATA = true;
928
+ } else {
929
+ this.bvtContext.STORE_DETAILED_NETWORK_DATA = false;
930
+ }
838
931
  for (const [key, value] of Object.entries(_env)) {
839
932
  process.env[key] = value;
840
933
  }
@@ -872,10 +965,23 @@ export class BVTRecorder {
872
965
  this.bvtContext.navigate = false;
873
966
  }
874
967
  }
875
- async saveScenario({ scenario, featureName, override, isSingleStep }) {
876
- await updateStepDefinitions({ scenario, featureName, projectDir: this.projectDir }); // updates mjs files
877
- if (!isSingleStep) await updateFeatureFile({ featureName, scenario, override, projectDir: this.projectDir }); // updates gherkin files
878
- await this.cleanup({ tags: scenario.tags });
968
+ async saveScenario({ scenario, featureName, override, isSingleStep, branch, isEditing }) {
969
+ // await updateStepDefinitions({ scenario, featureName, projectDir: this.projectDir }); // updates mjs files
970
+ // if (!isSingleStep) await updateFeatureFile({ featureName, scenario, override, projectDir: this.projectDir }); // updates gherkin files
971
+ const res = await this.workspaceService.saveScenario({
972
+ scenario,
973
+ featureName,
974
+ override,
975
+ isSingleStep,
976
+ branch,
977
+ isEditing,
978
+ projectId: path.basename(this.projectDir),
979
+ });
980
+ if (res.success) {
981
+ await this.cleanup({ tags: scenario.tags });
982
+ } else {
983
+ throw new Error(res.message || "Error saving scenario");
984
+ }
879
985
  }
880
986
  async getImplementedSteps() {
881
987
  const stepsAndScenarios = await getImplementedSteps(this.projectDir);
@@ -959,10 +1065,11 @@ export class BVTRecorder {
959
1065
  if (existsSync(_getDataFile(this.world, this.bvtContext, this.web))) {
960
1066
  try {
961
1067
  const testData = JSON.parse(readFileSync(_getDataFile(this.world, this.bvtContext, this.web), "utf8"));
962
- this.logger.info("Test data", testData);
1068
+ // this.logger.info("Test data", testData);
963
1069
  this.sendEvent(this.events.getTestData, testData);
964
1070
  } catch (e) {
965
- this.logger.error("Error reading test data file", e);
1071
+ // this.logger.error("Error reading test data file", e);
1072
+ console.log("Error reading test data file", e);
966
1073
  }
967
1074
  }
968
1075
 
@@ -971,10 +1078,12 @@ export class BVTRecorder {
971
1078
  this.watcher.on("all", async (event, path) => {
972
1079
  try {
973
1080
  const testData = JSON.parse(await readFile(_getDataFile(this.world, this.bvtContext, this.web), "utf8"));
974
- this.logger.info("Test data", testData);
1081
+ // this.logger.info("Test data", testData);
1082
+ console.log("Test data changed", testData);
975
1083
  this.sendEvent(this.events.getTestData, testData);
976
1084
  } catch (e) {
977
- this.logger.error("Error reading test data file", e);
1085
+ // this.logger.error("Error reading test data file", e);
1086
+ console.log("Error reading test data file", e);
978
1087
  }
979
1088
  });
980
1089
  }
@@ -1007,7 +1116,7 @@ export class BVTRecorder {
1007
1116
  .filter((file) => file.endsWith(".feature"))
1008
1117
  .map((file) => path.join(this.projectDir, "features", file));
1009
1118
  try {
1010
- const parsedFiles = featureFiles.map((file) => parseFeatureFile(file));
1119
+ const parsedFiles = featureFiles.map((file) => this.parseFeatureFile(file));
1011
1120
  const output = {};
1012
1121
  parsedFiles.forEach((file) => {
1013
1122
  if (!file.feature) return;
@@ -1035,7 +1144,7 @@ export class BVTRecorder {
1035
1144
  loadExistingScenario({ featureName, scenarioName }) {
1036
1145
  const step_definitions = loadStepDefinitions(this.projectDir);
1037
1146
  const featureFilePath = path.join(this.projectDir, "features", featureName);
1038
- const gherkinDoc = parseFeatureFile(featureFilePath);
1147
+ const gherkinDoc = this.parseFeatureFile(featureFilePath);
1039
1148
  const scenario = gherkinDoc.feature.children.find((child) => child.scenario.name === scenarioName)?.scenario;
1040
1149
 
1041
1150
  const steps = [];
@@ -1194,20 +1303,54 @@ export class BVTRecorder {
1194
1303
  await this.cleanupExecution({ tags });
1195
1304
  await this.initExecution({ tags });
1196
1305
  }
1197
- }
1198
1306
 
1199
- const parseFeatureFile = (featureFilePath) => {
1200
- try {
1201
- let id = 0;
1202
- const uuidFn = () => (++id).toString(16);
1203
- const builder = new AstBuilder(uuidFn);
1204
- const matcher = new GherkinClassicTokenMatcher();
1205
- const parser = new Parser(builder, matcher);
1206
- const source = readFileSync(featureFilePath, "utf8");
1207
- const gherkinDocument = parser.parse(source);
1208
- return gherkinDocument;
1209
- } catch (e) {
1210
- console.log(e);
1307
+ parseFeatureFile(featureFilePath) {
1308
+ try {
1309
+ let id = 0;
1310
+ const uuidFn = () => (++id).toString(16);
1311
+ const builder = new AstBuilder(uuidFn);
1312
+ const matcher = new GherkinClassicTokenMatcher();
1313
+ const parser = new Parser(builder, matcher);
1314
+ const source = readFileSync(featureFilePath, "utf8");
1315
+ const gherkinDocument = parser.parse(source);
1316
+ return gherkinDocument;
1317
+ } catch (e) {
1318
+ this.logger.error(`Error parsing feature file: ${featureFilePath}`);
1319
+ console.log(e);
1320
+ }
1321
+ return {};
1211
1322
  }
1212
- return {};
1213
- };
1323
+
1324
+ stopRecordingNetwork(input) {
1325
+ if (this.bvtContext) {
1326
+ this.bvtContext.STORE_DETAILED_NETWORK_DATA = false;
1327
+ }
1328
+ }
1329
+
1330
+ async fakeParams(params) {
1331
+ const newFakeParams = {};
1332
+ Object.keys(params).forEach((key) => {
1333
+ if (!params[key].startsWith("{{") || !params[key].endsWith("}}")) {
1334
+ newFakeParams[key] = params[key];
1335
+ return;
1336
+ }
1337
+
1338
+ try {
1339
+ const value = params[key].substring(2, params[key].length - 2).trim();
1340
+ const faking = value.split("(")[0].split(".");
1341
+ let argument = value.substring(value.indexOf("(") + 1, value.lastIndexOf(")"));
1342
+ argument = isNaN(Number(argument)) || argument === "" ? argument : Number(argument);
1343
+ let fakeFunc = faker;
1344
+ faking.forEach((f) => {
1345
+ fakeFunc = fakeFunc[f];
1346
+ });
1347
+ const newValue = fakeFunc(argument);
1348
+ newFakeParams[key] = newValue;
1349
+ } catch (error) {
1350
+ newFakeParams[key] = params[key];
1351
+ }
1352
+ });
1353
+
1354
+ return newFakeParams;
1355
+ }
1356
+ }
@@ -116,6 +116,12 @@ export const getImplementedSteps = async (projectDir) => {
116
116
  const utilsContent = readFileSync(utilsTemplateFilePath, "utf8");
117
117
  //console.log({ utilsContent });
118
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
+ }
119
125
  } catch (error) {
120
126
  foundErrors.push({ error });
121
127
  }
@@ -229,6 +235,7 @@ export const getImplementedSteps = async (projectDir) => {
229
235
  pattern: template.pattern,
230
236
  paths: template.paths,
231
237
  routeItems: step.routeItems,
238
+ isApiStep: template.source === "api",
232
239
  };
233
240
 
234
241
  implementedSteps.push(implementedStep);
@@ -254,6 +261,7 @@ export const getImplementedSteps = async (projectDir) => {
254
261
  mjsFile: template.mjsFile,
255
262
  pattern: template.pattern,
256
263
  paths: template.paths,
264
+ isApiStep: template.source === "api",
257
265
  };
258
266
  // reconstruct the parameters
259
267
  const parameters = [];