@dev-blinq/cucumber_client 1.0.1250-dev → 1.0.1250-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 (42) hide show
  1. package/bin/assets/bundled_scripts/recorder.js +220 -0
  2. package/bin/assets/preload/recorderv3.js +5 -3
  3. package/bin/assets/scripts/aria_snapshot.js +235 -0
  4. package/bin/assets/scripts/dom_attr.js +372 -0
  5. package/bin/assets/scripts/dom_element.js +0 -0
  6. package/bin/assets/scripts/dom_parent.js +185 -0
  7. package/bin/assets/scripts/event_utils.js +105 -0
  8. package/bin/assets/scripts/pw.js +7886 -0
  9. package/bin/assets/scripts/recorder.js +1147 -0
  10. package/bin/assets/scripts/snapshot_capturer.js +155 -0
  11. package/bin/assets/scripts/unique_locators.js +852 -0
  12. package/bin/assets/scripts/yaml.js +4770 -0
  13. package/bin/assets/templates/_hooks_template.txt +37 -0
  14. package/bin/assets/templates/page_template.txt +2 -16
  15. package/bin/assets/templates/utils_template.txt +44 -71
  16. package/bin/client/apiTest/apiTest.js +6 -0
  17. package/bin/client/cli_helpers.js +11 -13
  18. package/bin/client/code_cleanup/utils.js +5 -1
  19. package/bin/client/code_gen/code_inversion.js +61 -4
  20. package/bin/client/code_gen/page_reflection.js +10 -3
  21. package/bin/client/code_gen/playwright_codeget.js +55 -16
  22. package/bin/client/cucumber/feature.js +89 -27
  23. package/bin/client/cucumber/project_to_document.js +1 -1
  24. package/bin/client/cucumber/steps_definitions.js +84 -76
  25. package/bin/client/cucumber_selector.js +13 -1
  26. package/bin/client/local_agent.js +3 -3
  27. package/bin/client/project.js +7 -1
  28. package/bin/client/recorderv3/bvt_recorder.js +298 -123
  29. package/bin/client/recorderv3/implemented_steps.js +74 -16
  30. package/bin/client/recorderv3/index.js +47 -25
  31. package/bin/client/recorderv3/network.js +299 -0
  32. package/bin/client/recorderv3/services.js +3 -15
  33. package/bin/client/recorderv3/step_runner.js +326 -67
  34. package/bin/client/recorderv3/step_utils.js +152 -5
  35. package/bin/client/recorderv3/update_feature.js +66 -34
  36. package/bin/client/recording.js +3 -0
  37. package/bin/client/run_cucumber.js +5 -1
  38. package/bin/client/scenario_report.js +0 -5
  39. package/bin/client/test_scenario.js +0 -1
  40. package/bin/client/utils/socket_logger.js +132 -0
  41. package/bin/index.js +1 -0
  42. package/package.json +17 -9
@@ -3,8 +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 pkg from "../../min/injectedScript.min.cjs";
7
- import { getImplementedSteps, getStepsAndCommandsForScenario } from "./implemented_steps.js";
6
+ import { getImplementedSteps, parseRouteFiles } from "./implemented_steps.js";
8
7
  import { NamesService } from "./services.js";
9
8
  import { BVTStepRunner } from "./step_runner.js";
10
9
  import { readFile, writeFile } from "fs/promises";
@@ -13,45 +12,22 @@ import { updateFeatureFile } from "./update_feature.js";
13
12
  import { parseStepTextParameters } from "../cucumber/utils.js";
14
13
  import { AstBuilder, GherkinClassicTokenMatcher, Parser } from "@cucumber/gherkin";
15
14
  import chokidar from "chokidar";
16
- import logger from "../../logger.js";
17
15
  import { unEscapeNonPrintables } from "../cucumber/utils.js";
18
16
  import { findAvailablePort } from "../utils/index.js";
19
- import { getAiConfig } from "../code_gen/page_reflection.js";
17
+ import socketLogger from "../utils/socket_logger.js";
20
18
  const __dirname = path.dirname(url.fileURLToPath(import.meta.url));
21
- const { source: injectedScriptSource } = pkg;
19
+
22
20
  const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
23
- export function getInitScript(config) {
24
- const popupHandlers = config?.popupHandlers ?? [];
25
-
26
- const disableHighlight = config?.disableHighlight ?? false;
27
-
28
- const disableMultipleLocators = config?.disableMultipleLocators ?? false;
29
-
30
- const popupScript = `
31
- window.__bvt_Recorder.setPopupHandlers(${JSON.stringify(popupHandlers)});
32
- window.__bvt_Recorder.setDisableHighlight(${JSON.stringify(disableHighlight)});
33
- window.__bvt_Recorder.setImproviseLocators(${JSON.stringify(!disableMultipleLocators)});`;
34
- return (
35
- [
36
- path.join(__dirname, "..", "..", "assets", "preload", "accessibility.js"),
37
- path.join(__dirname, "..", "..", "assets", "preload", "dom-utils.js"),
38
- path.join(__dirname, "..", "..", "assets", "preload", "generateSelector.js"),
39
- path.join(__dirname, "..", "..", "assets", "preload", "locators.js"),
40
- path.join(__dirname, "..", "..", "assets", "preload", "unique_locators.js"),
41
- // path.join(__dirname, "..", "..", "assets", "preload", "pw_locators.js"),
42
- path.join(__dirname, "..", "..", "assets", "preload", "climb.js"),
43
- path.join(__dirname, "..", "..", "assets", "preload", "text-locator.js"),
44
- path.join(__dirname, "..", "..", "assets", "preload", "toolbar.js"),
45
- path.join(__dirname, "..", "..", "assets", "preload", "recording-tool.js"),
46
- path.join(__dirname, "..", "..", "assets", "preload", "find_context.js"),
47
- path.join(__dirname, "..", "..", "assets", "preload", "recorderv3.js"),
48
- path.join(__dirname, "..", "..", "assets", "preload", "findElementText.js"),
49
- path.join(__dirname, "..", "..", "assets", "preload", "css_gen.js"),
50
- path.join(__dirname, "..", "..", "assets", "preload", "yaml.js"),
51
- ]
52
- .map((filePath) => readFileSync(filePath, "utf8"))
53
- .join("\n") + popupScript
21
+ export function getInitScript(config, options) {
22
+ const preScript = `
23
+ window.__bvt_Recorder_config = ${JSON.stringify(config ?? null)};
24
+ window.__PW_options = ${JSON.stringify(options ?? null)};
25
+ `;
26
+ const recorderScript = readFileSync(
27
+ path.join(__dirname, "..", "..", "assets", "bundled_scripts", "recorder.js"),
28
+ "utf8"
54
29
  );
30
+ return preScript + recorderScript;
55
31
  }
56
32
 
57
33
  async function evaluate(frame, script) {
@@ -67,15 +43,15 @@ async function evaluate(frame, script) {
67
43
  async function findNestedFrameSelector(frame, obj) {
68
44
  try {
69
45
  const parent = frame.parentFrame();
70
- if (parent) console.log(`Parent frame: ${JSON.stringify(parent)}`);
71
46
  if (!parent) return { children: obj };
72
47
  const frameElement = await frame.frameElement();
73
48
  if (!frameElement) return;
74
49
  const selectors = await parent.evaluate((element) => {
75
- return window.getPWLocators(element);
50
+ return window.__bvt_Recorder.locatorGenerator.getElementLocators(element, { excludeText: true }).locators;
76
51
  }, frameElement);
77
52
  return findNestedFrameSelector(parent, { children: obj, selectors });
78
53
  } catch (e) {
54
+ socketLogger.error(`Error in findNestedFrameSelector: ${e}`);
79
55
  console.error(e);
80
56
  }
81
57
  }
@@ -172,6 +148,7 @@ const transformAction = (action, el, isVerify, isPopupCloseClick, isInHoverMode,
172
148
  };
173
149
  }
174
150
  default: {
151
+ socketLogger.error(`Action not supported: ${action.name}`);
175
152
  console.log("action not supported", action);
176
153
  throw new Error("action not supported");
177
154
  }
@@ -183,6 +160,7 @@ const transformAction = (action, el, isVerify, isPopupCloseClick, isInHoverMode,
183
160
  * @property {string} projectDir
184
161
  * @property {string} TOKEN
185
162
  * @property {(name:string, data:any)=> void} sendEvent
163
+ * @property {Object} logger
186
164
  */
187
165
  export class BVTRecorder {
188
166
  #currentURL = "";
@@ -196,7 +174,6 @@ export class BVTRecorder {
196
174
  */
197
175
  constructor(initialState) {
198
176
  Object.assign(this, initialState);
199
- this.logger = logger;
200
177
  this.screenshotMap = new Map();
201
178
  this.snapshotMap = new Map();
202
179
  this.scenariosStepsMap = new Map();
@@ -206,13 +183,9 @@ export class BVTRecorder {
206
183
  projectDir: this.projectDir,
207
184
  logger: this.logger,
208
185
  });
209
- this.stepRunner = new BVTStepRunner({
210
- projectDir: this.projectDir,
211
- });
212
186
  this.pageSet = new Set();
213
-
187
+ this.pageMetaDataSet = new Set();
214
188
  this.lastKnownUrlPath = "";
215
- // TODO: what is world?
216
189
  this.world = { attach: () => {} };
217
190
  this.shouldTakeScreenshot = true;
218
191
  this.watcher = null;
@@ -226,12 +199,16 @@ export class BVTRecorder {
226
199
  onStepDetails: "BVTRecorder.onStepDetails",
227
200
  getTestData: "BVTRecorder.getTestData",
228
201
  onGoto: "BVTRecorder.onGoto",
202
+ cmdExecutionStart: "BVTRecorder.cmdExecutionStart",
203
+ cmdExecutionSuccess: "BVTRecorder.cmdExecutionSuccess",
204
+ cmdExecutionError: "BVTRecorder.cmdExecutionError",
205
+ interceptResults: "BVTRecorder.interceptResults",
229
206
  };
230
207
  bindings = {
231
208
  __bvt_recordCommand: async ({ frame, page, context }, event) => {
232
209
  this.#activeFrame = frame;
233
210
  const nestFrmLoc = await findNestedFrameSelector(frame);
234
- console.log(`Time taken for action: ${event.statistics.time}`);
211
+ this.logger.info(`Time taken for action: ${event.statistics.time}`);
235
212
  await this.onAction({ ...event, nestFrmLoc });
236
213
  },
237
214
  __bvt_getMode: async () => {
@@ -250,8 +227,7 @@ export class BVTRecorder {
250
227
  await this.onClosePopup();
251
228
  },
252
229
  __bvt_log: async (src, message) => {
253
- // this.logger.info(message);
254
- console.log(`Inside Browser: ${message}`);
230
+ this.logger.info(`Inside Browser: ${message}`);
255
231
  },
256
232
  __bvt_getObject: (_src, obj) => {
257
233
  this.processObject(obj);
@@ -301,29 +277,22 @@ export class BVTRecorder {
301
277
  return result;
302
278
  }
303
279
  getInitScripts(config) {
304
- return getInitScript(config);
305
- // const scripts = []
306
- // scripts.push(...this.getPWScript());
307
- // scripts.push(...this.getRecorderScripts());
308
-
309
- // const popupHandlers = config?.popupHandlers ?? [];
310
- // const disableHighlight = config?.disableHighlight ?? false;
311
- // const disableMultipleLocators = config?.disableMultipleLocators ?? false;
312
-
313
- // const inlineScript = `
314
- // window.__bvt_Recorder.setPopupHandlers(${JSON.stringify(popupHandlers)});
315
- // window.__bvt_Recorder.setDisableHighlight(${JSON.stringify(disableHighlight)});
316
- // window.__bvt_Recorder.setImproviseLocators(${JSON.stringify(!disableMultipleLocators)});
317
- // `
318
- // scripts.push(inlineScript);
319
- // return scripts;
280
+ return getInitScript(config, {
281
+ sdkLanguage: "javascript",
282
+ testIdAttributeName: "blinq-test-id",
283
+ stableRafCount: 0,
284
+ browserName: this.browser?.browserType().name(),
285
+ inputFileRoleTextbox: false,
286
+ customEngines: [],
287
+ isUnderTest: true,
288
+ });
320
289
  }
321
290
 
322
291
  async _initBrowser({ url }) {
323
292
  this.#remoteDebuggerPort = await findAvailablePort();
324
293
  process.env.CDP_LISTEN_PORT = this.#remoteDebuggerPort;
325
294
 
326
- this.stepRunner.setRemoteDebugPort(this.#remoteDebuggerPort);
295
+ // this.stepRunner.setRemoteDebugPort(this.#remoteDebuggerPort);
327
296
  this.world = { attach: () => {} };
328
297
 
329
298
  const ai_config_file = path.join(this.projectDir, "ai_config.json");
@@ -332,27 +301,49 @@ export class BVTRecorder {
332
301
  try {
333
302
  ai_config = JSON.parse(readFileSync(ai_config_file, "utf8"));
334
303
  } catch (error) {
335
- console.error("Error reading ai_config.json", error);
304
+ this.logger.error("Error reading ai_config.json", error);
336
305
  }
337
306
  }
307
+ this.config = ai_config;
338
308
  const initScripts = {
339
- recorderCjs: injectedScriptSource,
309
+ // recorderCjs: injectedScriptSource,
340
310
  scripts: [
341
311
  this.getInitScripts(ai_config),
342
312
  `\ndelete Object.getPrototypeOf(navigator).webdriver;${process.env.WINDOW_DEBUGGER ? "window.debug=true;\n" : ""}`,
343
313
  ],
344
314
  };
345
315
 
346
- let startTime = Date.now();
347
316
  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
317
  this.bvtContext = bvtContext;
318
+ this.stepRunner = new BVTStepRunner({
319
+ projectDir: this.projectDir,
320
+ sendExecutionStatus: (data) => {
321
+ if (data && data.type) {
322
+ switch (data.type) {
323
+ case "cmdExecutionStart":
324
+ this.sendEvent(this.events.cmdExecutionStart, data);
325
+ break;
326
+ case "cmdExecutionSuccess":
327
+ this.sendEvent(this.events.cmdExecutionSuccess, data);
328
+ break;
329
+ case "cmdExecutionError":
330
+ this.sendEvent(this.events.cmdExecutionError, data);
331
+ break;
332
+ case "interceptResults":
333
+ this.sendEvent(this.events.interceptResults, data);
334
+ break;
335
+ default:
336
+ break;
337
+ }
338
+ }
339
+ },
340
+ bvtContext: this.bvtContext,
341
+ });
351
342
  const context = bvtContext.playContext;
352
343
  this.context = context;
353
344
  this.web = bvtContext.stable || bvtContext.web;
345
+ this.web.tryAllStrategies = true;
354
346
  this.page = bvtContext.page;
355
-
356
347
  this.pageSet.add(this.page);
357
348
  this.lastKnownUrlPath = this._updateUrlPath();
358
349
  const browser = await this.context.browser();
@@ -380,13 +371,15 @@ export class BVTRecorder {
380
371
  }
381
372
  return;
382
373
  } catch (error) {
383
- console.error("Error evaluting in context:", error);
374
+ // console.error("Error evaluting in context:", error);
375
+ this.logger.error("Error evaluating in context:", error);
384
376
  }
385
377
  }
386
378
  }
387
379
 
388
380
  getMode() {
389
- console.log("getMode", this.#mode);
381
+ // console.log("getMode", this.#mode);
382
+ this.logger.info("Current mode:", this.#mode);
390
383
  return this.#mode;
391
384
  }
392
385
 
@@ -428,6 +421,8 @@ export class BVTRecorder {
428
421
  this.sendEvent(this.events.onBrowserClose);
429
422
  }
430
423
  } catch (error) {
424
+ this.logger.error("Error in page close event");
425
+ this.logger.error(error);
431
426
  console.error("Error in page close event");
432
427
  console.error(error);
433
428
  }
@@ -438,8 +433,10 @@ export class BVTRecorder {
438
433
  if (frame !== page.mainFrame()) return;
439
434
  this.handlePageTransition();
440
435
  } catch (error) {
436
+ this.logger.error("Error in handlePageTransition event");
437
+ this.logger.error(error);
441
438
  console.error("Error in handlePageTransition event");
442
- // console.error(error);
439
+ console.error(error);
443
440
  }
444
441
  try {
445
442
  if (frame !== this.#activeFrame) return;
@@ -458,11 +455,82 @@ export class BVTRecorder {
458
455
  // await this._setRecordingMode(frame);
459
456
  // await this._initPage(page);
460
457
  } catch (error) {
458
+ this.logger.error("Error in frame navigate event");
459
+ this.logger.error(error);
461
460
  console.error("Error in frame navigate event");
462
461
  // console.error(error);
463
462
  }
464
463
  });
465
464
  }
465
+
466
+ hasHistoryReplacementAtIndex(previousEntries, currentEntries, index) {
467
+ if (!previousEntries || !currentEntries) return false;
468
+ if (index >= previousEntries.length || index >= currentEntries.length) return false;
469
+
470
+ const prevEntry = previousEntries[index];
471
+ // console.log("prevEntry", prevEntry);
472
+ const currEntry = currentEntries[index];
473
+ // console.log("currEntry", currEntry);
474
+
475
+ // Check if the entry at this index has been replaced
476
+ return prevEntry.id !== currEntry.id;
477
+ }
478
+
479
+ // Even simpler approach for your specific case
480
+ analyzeTransitionType(entries, currentIndex, currentEntry) {
481
+ // console.log("Analyzing transition type");
482
+ // console.log("===========================");
483
+ // console.log("Current Index:", currentIndex);
484
+ // console.log("Current Entry:", currentEntry);
485
+ // console.log("Current Entries:", entries);
486
+ // console.log("Current entries length:", entries.length);
487
+ // console.log("===========================");
488
+ // console.log("Previous Index:", this.previousIndex);
489
+ // // console.log("Previous Entry:", this.previousEntries[this.previousIndex]);
490
+ // console.log("Previous Entries:", this.previousEntries);
491
+ // console.log("Previous entries length:", this.previousHistoryLength);
492
+
493
+ if (this.previousIndex === null || this.previousHistoryLength === null || !this.previousEntries) {
494
+ return {
495
+ action: "initial",
496
+ };
497
+ }
498
+
499
+ const indexDiff = currentIndex - this.previousIndex;
500
+ const lengthDiff = entries.length - this.previousHistoryLength;
501
+
502
+ // Backward navigation
503
+ if (indexDiff < 0) {
504
+ return { action: "back" };
505
+ }
506
+
507
+ // Forward navigation
508
+ if (indexDiff > 0 && lengthDiff === 0) {
509
+ // Check if the entry at current index is the same as before
510
+ const entryReplaced = this.hasHistoryReplacementAtIndex(this.previousEntries, entries, currentIndex);
511
+
512
+ if (entryReplaced) {
513
+ return { action: "navigate" }; // New navigation that replaced forward history
514
+ } else {
515
+ return { action: "forward" }; // True forward navigation
516
+ }
517
+ }
518
+
519
+ // New navigation (history grew)
520
+ if (lengthDiff > 0) {
521
+ return { action: "navigate" };
522
+ }
523
+
524
+ // Same position, same length
525
+ if (lengthDiff <= 0) {
526
+ const entryReplaced = this.hasHistoryReplacementAtIndex(this.previousEntries, entries, currentIndex);
527
+
528
+ return entryReplaced ? { action: "navigate" } : { action: "reload" };
529
+ }
530
+
531
+ return { action: "unknown" };
532
+ }
533
+
466
534
  async getCurrentTransition() {
467
535
  if (this?.web?.browser?._name !== "chromium") {
468
536
  return;
@@ -471,35 +539,68 @@ export class BVTRecorder {
471
539
 
472
540
  try {
473
541
  const result = await client.send("Page.getNavigationHistory");
474
- // console.log("result", result);
475
542
  const entries = result.entries;
476
543
  const currentIndex = result.currentIndex;
477
544
 
478
- // ignore if currentIndex is not the last entry
479
- if (currentIndex !== entries.length - 1) return;
480
-
481
545
  const currentEntry = entries[currentIndex];
482
- return currentEntry;
546
+ const transitionInfo = this.analyzeTransitionType(entries, currentIndex, currentEntry);
547
+ this.previousIndex = currentIndex;
548
+ this.previousHistoryLength = entries.length;
549
+ this.previousUrl = currentEntry.url;
550
+ this.previousEntries = [...entries]; // Store a copy of current entries
551
+
552
+ return {
553
+ currentEntry,
554
+ navigationAction: transitionInfo.action,
555
+ };
483
556
  } catch (error) {
484
- console.error("Error in getTransistionType event");
557
+ this.logger.error("Error in getCurrentTransition event");
558
+ this.logger.error(error);
559
+ console.error("Error in getTransistionType event", error);
485
560
  } finally {
486
561
  await client.detach();
487
562
  }
488
563
  }
489
- userInitiatedTranistionTypes = ["typed", "address_bar"];
564
+ userInitiatedTransitionTypes = ["typed", "address_bar"];
490
565
  async handlePageTransition() {
491
566
  const transition = await this.getCurrentTransition();
492
567
  if (!transition) return;
493
- // console.log("transitionType", transition.transitionType);
494
- if (this.userInitiatedTranistionTypes.includes(transition.transitionType)) {
495
- const env = JSON.parse(readFileSync(this.envName), "utf8");
496
- const baseUrl = env.baseUrl;
497
- let url = transition.userTypedURL;
498
- if (baseUrl && url.startsWith(baseUrl)) {
499
- url = url.replace(baseUrl, "{{env.baseUrl}}");
500
- }
501
- // console.log("User initiated transition");
502
- this.sendEvent(this.events.onGoto, { url });
568
+
569
+ const { currentEntry, navigationAction } = transition;
570
+
571
+ switch (navigationAction) {
572
+ case "initial":
573
+ // console.log("Initial navigation, no action taken");
574
+ return;
575
+ case "navigate":
576
+ // console.log("transitionType", transition.transitionType);
577
+ // console.log("sending onGoto event", { url: currentEntry.url,
578
+ // type: "navigate", });
579
+ if (this.userInitiatedTransitionTypes.includes(currentEntry.transitionType)) {
580
+ const env = JSON.parse(readFileSync(this.envName), "utf8");
581
+ const baseUrl = env.baseUrl;
582
+ let url = currentEntry.userTypedURL;
583
+ if (baseUrl && url.startsWith(baseUrl)) {
584
+ url = url.replace(baseUrl, "{{env.baseUrl}}");
585
+ }
586
+ // console.log("User initiated transition");
587
+ this.sendEvent(this.events.onGoto, { url, type: "navigate" });
588
+ }
589
+ return;
590
+ case "back":
591
+ // console.log("User navigated back");
592
+ // console.log("sending onGoto event", {
593
+ // type: "back",
594
+ // });
595
+ this.sendEvent(this.events.onGoto, { type: "back" });
596
+ return;
597
+ case "forward":
598
+ // console.log("User navigated forward"); console.log("sending onGoto event", { type: "forward", });
599
+ this.sendEvent(this.events.onGoto, { type: "forward" });
600
+ return;
601
+ default:
602
+ this.sendEvent(this.events.onGoto, { type: "unknown" });
603
+ return;
503
604
  }
504
605
  }
505
606
 
@@ -523,6 +624,8 @@ export class BVTRecorder {
523
624
  // add listener for frame navigation on new tab
524
625
  this._addFrameNavigateListener(page);
525
626
  } catch (error) {
627
+ this.logger.error("Error in page event");
628
+ this.logger.error(error);
526
629
  console.error("Error in page event");
527
630
  console.error(error);
528
631
  }
@@ -564,6 +667,7 @@ export class BVTRecorder {
564
667
  const { data } = await client.send("Page.captureScreenshot", { format: "png" });
565
668
  return data;
566
669
  } catch (error) {
670
+ this.logger.error("Error in taking browser screenshot");
567
671
  console.error("Error in taking browser screenshot", error);
568
672
  } finally {
569
673
  await client.detach();
@@ -628,6 +732,11 @@ export class BVTRecorder {
628
732
  delete process.env.TEMP_RUN;
629
733
  await this.watcher.close().then(() => {});
630
734
  this.watcher = null;
735
+ this.previousIndex = null;
736
+ this.previousHistoryLength = null;
737
+ this.previousUrl = null;
738
+ this.previousEntries = null;
739
+
631
740
  await closeContext();
632
741
  this.pageSet.clear();
633
742
  }
@@ -704,40 +813,61 @@ export class BVTRecorder {
704
813
  async abortExecution() {
705
814
  await this.stepRunner.abortExecution();
706
815
  }
816
+
817
+ async pauseExecution({ cmdId }) {
818
+ await this.stepRunner.pauseExecution(cmdId);
819
+ }
820
+
821
+ async resumeExecution({ cmdId }) {
822
+ await this.stepRunner.resumeExecution(cmdId);
823
+ }
824
+
707
825
  async dealyedRevertMode() {
708
826
  const timerId = setTimeout(async () => {
709
827
  await this.revertMode();
710
828
  }, 100);
711
829
  this.timerId = timerId;
712
830
  }
713
- async runStep({ step, parametersMap }, options) {
831
+ async runStep({ step, parametersMap, tags, isFirstStep, listenNetwork }, options) {
832
+ const { skipAfter = true, skipBefore = !isFirstStep } = options || {};
714
833
  const _env = {
715
834
  TOKEN: this.TOKEN,
716
835
  TEMP_RUN: true,
717
836
  REPORT_FOLDER: this.bvtContext.reportFolder,
718
837
  BLINQ_ENV: this.envName,
838
+ STORE_DETAILED_NETWORK_DATA: listenNetwork ? "true" : "false",
839
+ DEBUG: "blinq:route",
719
840
  };
720
841
 
721
842
  this.bvtContext.navigate = true;
843
+ this.bvtContext.loadedRoutes = null;
722
844
  for (const [key, value] of Object.entries(_env)) {
723
845
  process.env[key] = value;
724
846
  }
847
+
725
848
  if (this.timerId) {
726
849
  clearTimeout(this.timerId);
727
850
  this.timerId = null;
728
851
  }
729
852
  await this.setMode("running");
853
+
730
854
  try {
731
- await this.stepRunner.runStep(
855
+ const { result, info } = await this.stepRunner.runStep(
732
856
  {
733
857
  step,
734
858
  parametersMap,
735
859
  envPath: this.envName,
860
+ tags,
861
+ config: this.config,
736
862
  },
737
863
  this.bvtContext,
738
- options
864
+ {
865
+ skipAfter,
866
+ skipBefore,
867
+ }
739
868
  );
740
869
  await this.revertMode();
870
+ return { info };
741
871
  } catch (error) {
742
872
  await this.revertMode();
743
873
  throw error;
@@ -748,15 +878,10 @@ export class BVTRecorder {
748
878
  this.bvtContext.navigate = false;
749
879
  }
750
880
  }
751
- async runScenario({ steps, parametersMap }) {
752
- for (const step of steps) {
753
- await this.runStep({ step, parametersMap });
754
- }
755
- }
756
881
  async saveScenario({ scenario, featureName, override, isSingleStep }) {
757
882
  await updateStepDefinitions({ scenario, featureName, projectDir: this.projectDir }); // updates mjs files
758
883
  if (!isSingleStep) await updateFeatureFile({ featureName, scenario, override, projectDir: this.projectDir }); // updates gherkin files
759
- await this.cleanup();
884
+ await this.cleanup({ tags: scenario.tags });
760
885
  }
761
886
  async getImplementedSteps() {
762
887
  const stepsAndScenarios = await getImplementedSteps(this.projectDir);
@@ -780,6 +905,7 @@ export class BVTRecorder {
780
905
  step.commands = [];
781
906
  }
782
907
  }
908
+ return steps;
783
909
  // return getStepsAndCommandsForScenario({
784
910
  // name,
785
911
  // featureName,
@@ -787,6 +913,7 @@ export class BVTRecorder {
787
913
  // map: this.scenariosStepsMap,
788
914
  // });
789
915
  }
916
+
790
917
  async generateStepName({ commands, stepsNames, parameters, map }) {
791
918
  return await this.namesService.generateStepName({ commands, stepsNames, parameters, map });
792
919
  }
@@ -838,10 +965,11 @@ export class BVTRecorder {
838
965
  if (existsSync(_getDataFile(this.world, this.bvtContext, this.web))) {
839
966
  try {
840
967
  const testData = JSON.parse(readFileSync(_getDataFile(this.world, this.bvtContext, this.web), "utf8"));
841
- this.logger.info("Test data", testData);
968
+ // this.logger.info("Test data", testData);
842
969
  this.sendEvent(this.events.getTestData, testData);
843
970
  } catch (e) {
844
- this.logger.error("Error reading test data file", e);
971
+ // this.logger.error("Error reading test data file", e);
972
+ console.log("Error reading test data file", e);
845
973
  }
846
974
  }
847
975
 
@@ -850,10 +978,12 @@ export class BVTRecorder {
850
978
  this.watcher.on("all", async (event, path) => {
851
979
  try {
852
980
  const testData = JSON.parse(await readFile(_getDataFile(this.world, this.bvtContext, this.web), "utf8"));
853
- this.logger.info("Test data", testData);
981
+ // this.logger.info("Test data", testData);
982
+ console.log("Test data changed", testData);
854
983
  this.sendEvent(this.events.getTestData, testData);
855
984
  } catch (e) {
856
- this.logger.error("Error reading test data file", e);
985
+ // this.logger.error("Error reading test data file", e);
986
+ console.log("Error reading test data file", e);
857
987
  }
858
988
  });
859
989
  }
@@ -869,9 +999,9 @@ export class BVTRecorder {
869
999
  }
870
1000
  }
871
1001
 
872
- async discardTestData() {
1002
+ async discardTestData({ tags }) {
873
1003
  resetTestData(this.envName, this.world);
874
- await this.cleanup();
1004
+ await this.cleanup({ tags });
875
1005
  }
876
1006
  async addToTestData(obj) {
877
1007
  if (!existsSync(_getDataFile(this.world, this.bvtContext, this.web))) {
@@ -886,7 +1016,7 @@ export class BVTRecorder {
886
1016
  .filter((file) => file.endsWith(".feature"))
887
1017
  .map((file) => path.join(this.projectDir, "features", file));
888
1018
  try {
889
- const parsedFiles = featureFiles.map((file) => parseFeatureFile(file));
1019
+ const parsedFiles = featureFiles.map((file) => this.parseFeatureFile(file));
890
1020
  const output = {};
891
1021
  parsedFiles.forEach((file) => {
892
1022
  if (!file.feature) return;
@@ -910,10 +1040,11 @@ export class BVTRecorder {
910
1040
  const stepParams = parseStepTextParameters(stepName);
911
1041
  return getCommandsForImplementedStep(stepName, step_definitions, stepParams).commands;
912
1042
  }
1043
+
913
1044
  loadExistingScenario({ featureName, scenarioName }) {
914
1045
  const step_definitions = loadStepDefinitions(this.projectDir);
915
1046
  const featureFilePath = path.join(this.projectDir, "features", featureName);
916
- const gherkinDoc = parseFeatureFile(featureFilePath);
1047
+ const gherkinDoc = this.parseFeatureFile(featureFilePath);
917
1048
  const scenario = gherkinDoc.feature.children.find((child) => child.scenario.name === scenarioName)?.scenario;
918
1049
 
919
1050
  const steps = [];
@@ -938,6 +1069,7 @@ export class BVTRecorder {
938
1069
  ..._s,
939
1070
  keyword: step.keyword.trim(),
940
1071
  };
1072
+ parseRouteFiles(this.projectDir, _step);
941
1073
  steps.push(_step);
942
1074
  }
943
1075
  return {
@@ -977,7 +1109,7 @@ export class BVTRecorder {
977
1109
  }
978
1110
  return result;
979
1111
  }
980
- async cleanup() {
1112
+ async cleanup({ tags }) {
981
1113
  const noopStep = {
982
1114
  text: "Noop",
983
1115
  isImplemented: true,
@@ -991,6 +1123,7 @@ export class BVTRecorder {
991
1123
  {
992
1124
  step: noopStep,
993
1125
  parametersMap: {},
1126
+ tags: tags || [],
994
1127
  },
995
1128
  {
996
1129
  skipAfter: false,
@@ -1029,20 +1162,62 @@ export class BVTRecorder {
1029
1162
  return false;
1030
1163
  }
1031
1164
  }
1032
- }
1165
+ async initExecution({ tags = [] }) {
1166
+ // run before hooks
1167
+ const noopStep = {
1168
+ text: "Noop",
1169
+ isImplemented: true,
1170
+ };
1171
+ await this.runStep(
1172
+ {
1173
+ step: noopStep,
1174
+ parametersMap: {},
1175
+ tags,
1176
+ },
1177
+ {
1178
+ skipBefore: false,
1179
+ skipAfter: true,
1180
+ }
1181
+ );
1182
+ }
1183
+ async cleanupExecution({ tags = [] }) {
1184
+ // run after hooks
1185
+ const noopStep = {
1186
+ text: "Noop",
1187
+ isImplemented: true,
1188
+ };
1189
+ await this.runStep(
1190
+ {
1191
+ step: noopStep,
1192
+ parametersMap: {},
1193
+ tags,
1194
+ },
1195
+ {
1196
+ skipBefore: true,
1197
+ skipAfter: false,
1198
+ }
1199
+ );
1200
+ }
1201
+ async resetExecution({ tags = [] }) {
1202
+ // run after hooks followed by before hooks
1203
+ await this.cleanupExecution({ tags });
1204
+ await this.initExecution({ tags });
1205
+ }
1033
1206
 
1034
- const parseFeatureFile = (featureFilePath) => {
1035
- try {
1036
- let id = 0;
1037
- const uuidFn = () => (++id).toString(16);
1038
- const builder = new AstBuilder(uuidFn);
1039
- const matcher = new GherkinClassicTokenMatcher();
1040
- const parser = new Parser(builder, matcher);
1041
- const source = readFileSync(featureFilePath, "utf8");
1042
- const gherkinDocument = parser.parse(source);
1043
- return gherkinDocument;
1044
- } catch (e) {
1045
- console.log(e);
1207
+ parseFeatureFile(featureFilePath) {
1208
+ try {
1209
+ let id = 0;
1210
+ const uuidFn = () => (++id).toString(16);
1211
+ const builder = new AstBuilder(uuidFn);
1212
+ const matcher = new GherkinClassicTokenMatcher();
1213
+ const parser = new Parser(builder, matcher);
1214
+ const source = readFileSync(featureFilePath, "utf8");
1215
+ const gherkinDocument = parser.parse(source);
1216
+ return gherkinDocument;
1217
+ } catch (e) {
1218
+ this.logger.error(`Error parsing feature file: ${featureFilePath}`);
1219
+ console.log(e);
1220
+ }
1221
+ return {};
1046
1222
  }
1047
- return {};
1048
- };
1223
+ }