@dev-blinq/cucumber_client 1.0.1241-dev → 1.0.1241-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 (43) 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/preload/unique_locators.js +1 -1
  4. package/bin/assets/scripts/aria_snapshot.js +235 -0
  5. package/bin/assets/scripts/dom_attr.js +372 -0
  6. package/bin/assets/scripts/dom_element.js +0 -0
  7. package/bin/assets/scripts/dom_parent.js +185 -0
  8. package/bin/assets/scripts/event_utils.js +105 -0
  9. package/bin/assets/scripts/pw.js +7886 -0
  10. package/bin/assets/scripts/recorder.js +1147 -0
  11. package/bin/assets/scripts/snapshot_capturer.js +155 -0
  12. package/bin/assets/scripts/unique_locators.js +852 -0
  13. package/bin/assets/scripts/yaml.js +4770 -0
  14. package/bin/assets/templates/_hooks_template.txt +37 -0
  15. package/bin/assets/templates/page_template.txt +2 -16
  16. package/bin/assets/templates/utils_template.txt +44 -71
  17. package/bin/client/apiTest/apiTest.js +6 -0
  18. package/bin/client/cli_helpers.js +11 -13
  19. package/bin/client/code_cleanup/utils.js +41 -14
  20. package/bin/client/code_gen/code_inversion.js +68 -10
  21. package/bin/client/code_gen/page_reflection.js +12 -15
  22. package/bin/client/code_gen/playwright_codeget.js +134 -42
  23. package/bin/client/cucumber/feature.js +89 -27
  24. package/bin/client/cucumber/project_to_document.js +1 -1
  25. package/bin/client/cucumber/steps_definitions.js +84 -76
  26. package/bin/client/cucumber_selector.js +13 -1
  27. package/bin/client/local_agent.js +3 -3
  28. package/bin/client/project.js +7 -1
  29. package/bin/client/recorderv3/bvt_recorder.js +307 -124
  30. package/bin/client/recorderv3/implemented_steps.js +63 -5
  31. package/bin/client/recorderv3/index.js +47 -25
  32. package/bin/client/recorderv3/network.js +299 -0
  33. package/bin/client/recorderv3/services.js +3 -15
  34. package/bin/client/recorderv3/step_runner.js +319 -67
  35. package/bin/client/recorderv3/step_utils.js +152 -5
  36. package/bin/client/recorderv3/update_feature.js +66 -34
  37. package/bin/client/recording.js +5 -0
  38. package/bin/client/run_cucumber.js +5 -1
  39. package/bin/client/scenario_report.js +0 -5
  40. package/bin/client/test_scenario.js +0 -1
  41. package/bin/client/utils/socket_logger.js +132 -0
  42. package/bin/index.js +1 -0
  43. 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,62 @@ 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
+ CURRENT_STEP_ID: step.id,
840
+ DEBUG: "blinq:route",
719
841
  };
720
842
 
721
843
  this.bvtContext.navigate = true;
844
+ this.bvtContext.loadedRoutes = null;
722
845
  for (const [key, value] of Object.entries(_env)) {
723
846
  process.env[key] = value;
724
847
  }
848
+
725
849
  if (this.timerId) {
726
850
  clearTimeout(this.timerId);
727
851
  this.timerId = null;
728
852
  }
729
853
  await this.setMode("running");
854
+
730
855
  try {
731
- await this.stepRunner.runStep(
856
+ const { result, info } = await this.stepRunner.runStep(
732
857
  {
733
858
  step,
734
859
  parametersMap,
735
860
  envPath: this.envName,
861
+ tags,
862
+ config: this.config,
736
863
  },
737
864
  this.bvtContext,
738
- options
865
+ {
866
+ skipAfter,
867
+ skipBefore,
868
+ }
739
869
  );
740
870
  await this.revertMode();
871
+ return { info };
741
872
  } catch (error) {
742
873
  await this.revertMode();
743
874
  throw error;
@@ -748,15 +879,10 @@ export class BVTRecorder {
748
879
  this.bvtContext.navigate = false;
749
880
  }
750
881
  }
751
- async runScenario({ steps, parametersMap }) {
752
- for (const step of steps) {
753
- await this.runStep({ step, parametersMap });
754
- }
755
- }
756
882
  async saveScenario({ scenario, featureName, override, isSingleStep }) {
757
883
  await updateStepDefinitions({ scenario, featureName, projectDir: this.projectDir }); // updates mjs files
758
884
  if (!isSingleStep) await updateFeatureFile({ featureName, scenario, override, projectDir: this.projectDir }); // updates gherkin files
759
- await this.cleanup();
885
+ await this.cleanup({ tags: scenario.tags });
760
886
  }
761
887
  async getImplementedSteps() {
762
888
  const stepsAndScenarios = await getImplementedSteps(this.projectDir);
@@ -772,7 +898,15 @@ export class BVTRecorder {
772
898
  };
773
899
  }
774
900
  async getStepsAndCommandsForScenario({ name, featureName }) {
775
- return this.scenariosStepsMap.get(name) || [];
901
+ const steps = this.scenariosStepsMap.get(name) || [];
902
+ for (const step of steps) {
903
+ if (step.isImplemented) {
904
+ step.commands = this.getCommandsForImplementedStep({ stepName: step.text });
905
+ } else {
906
+ step.commands = [];
907
+ }
908
+ }
909
+ return steps;
776
910
  // return getStepsAndCommandsForScenario({
777
911
  // name,
778
912
  // featureName,
@@ -780,6 +914,7 @@ export class BVTRecorder {
780
914
  // map: this.scenariosStepsMap,
781
915
  // });
782
916
  }
917
+
783
918
  async generateStepName({ commands, stepsNames, parameters, map }) {
784
919
  return await this.namesService.generateStepName({ commands, stepsNames, parameters, map });
785
920
  }
@@ -831,10 +966,11 @@ export class BVTRecorder {
831
966
  if (existsSync(_getDataFile(this.world, this.bvtContext, this.web))) {
832
967
  try {
833
968
  const testData = JSON.parse(readFileSync(_getDataFile(this.world, this.bvtContext, this.web), "utf8"));
834
- this.logger.info("Test data", testData);
969
+ // this.logger.info("Test data", testData);
835
970
  this.sendEvent(this.events.getTestData, testData);
836
971
  } catch (e) {
837
- this.logger.error("Error reading test data file", e);
972
+ // this.logger.error("Error reading test data file", e);
973
+ console.log("Error reading test data file", e);
838
974
  }
839
975
  }
840
976
 
@@ -843,10 +979,12 @@ export class BVTRecorder {
843
979
  this.watcher.on("all", async (event, path) => {
844
980
  try {
845
981
  const testData = JSON.parse(await readFile(_getDataFile(this.world, this.bvtContext, this.web), "utf8"));
846
- this.logger.info("Test data", testData);
982
+ // this.logger.info("Test data", testData);
983
+ console.log("Test data changed", testData);
847
984
  this.sendEvent(this.events.getTestData, testData);
848
985
  } catch (e) {
849
- this.logger.error("Error reading test data file", e);
986
+ // this.logger.error("Error reading test data file", e);
987
+ console.log("Error reading test data file", e);
850
988
  }
851
989
  });
852
990
  }
@@ -862,9 +1000,9 @@ export class BVTRecorder {
862
1000
  }
863
1001
  }
864
1002
 
865
- async discardTestData() {
1003
+ async discardTestData({ tags }) {
866
1004
  resetTestData(this.envName, this.world);
867
- await this.cleanup();
1005
+ await this.cleanup({ tags });
868
1006
  }
869
1007
  async addToTestData(obj) {
870
1008
  if (!existsSync(_getDataFile(this.world, this.bvtContext, this.web))) {
@@ -879,7 +1017,7 @@ export class BVTRecorder {
879
1017
  .filter((file) => file.endsWith(".feature"))
880
1018
  .map((file) => path.join(this.projectDir, "features", file));
881
1019
  try {
882
- const parsedFiles = featureFiles.map((file) => parseFeatureFile(file));
1020
+ const parsedFiles = featureFiles.map((file) => this.parseFeatureFile(file));
883
1021
  const output = {};
884
1022
  parsedFiles.forEach((file) => {
885
1023
  if (!file.feature) return;
@@ -903,10 +1041,11 @@ export class BVTRecorder {
903
1041
  const stepParams = parseStepTextParameters(stepName);
904
1042
  return getCommandsForImplementedStep(stepName, step_definitions, stepParams).commands;
905
1043
  }
1044
+
906
1045
  loadExistingScenario({ featureName, scenarioName }) {
907
1046
  const step_definitions = loadStepDefinitions(this.projectDir);
908
1047
  const featureFilePath = path.join(this.projectDir, "features", featureName);
909
- const gherkinDoc = parseFeatureFile(featureFilePath);
1048
+ const gherkinDoc = this.parseFeatureFile(featureFilePath);
910
1049
  const scenario = gherkinDoc.feature.children.find((child) => child.scenario.name === scenarioName)?.scenario;
911
1050
 
912
1051
  const steps = [];
@@ -931,6 +1070,7 @@ export class BVTRecorder {
931
1070
  ..._s,
932
1071
  keyword: step.keyword.trim(),
933
1072
  };
1073
+ parseRouteFiles(this.projectDir, _step);
934
1074
  steps.push(_step);
935
1075
  }
936
1076
  return {
@@ -970,7 +1110,7 @@ export class BVTRecorder {
970
1110
  }
971
1111
  return result;
972
1112
  }
973
- async cleanup() {
1113
+ async cleanup({ tags }) {
974
1114
  const noopStep = {
975
1115
  text: "Noop",
976
1116
  isImplemented: true,
@@ -984,6 +1124,7 @@ export class BVTRecorder {
984
1124
  {
985
1125
  step: noopStep,
986
1126
  parametersMap: {},
1127
+ tags: tags || [],
987
1128
  },
988
1129
  {
989
1130
  skipAfter: false,
@@ -1022,20 +1163,62 @@ export class BVTRecorder {
1022
1163
  return false;
1023
1164
  }
1024
1165
  }
1025
- }
1166
+ async initExecution({ tags = [] }) {
1167
+ // run before hooks
1168
+ const noopStep = {
1169
+ text: "Noop",
1170
+ isImplemented: true,
1171
+ };
1172
+ await this.runStep(
1173
+ {
1174
+ step: noopStep,
1175
+ parametersMap: {},
1176
+ tags,
1177
+ },
1178
+ {
1179
+ skipBefore: false,
1180
+ skipAfter: true,
1181
+ }
1182
+ );
1183
+ }
1184
+ async cleanupExecution({ tags = [] }) {
1185
+ // run after hooks
1186
+ const noopStep = {
1187
+ text: "Noop",
1188
+ isImplemented: true,
1189
+ };
1190
+ await this.runStep(
1191
+ {
1192
+ step: noopStep,
1193
+ parametersMap: {},
1194
+ tags,
1195
+ },
1196
+ {
1197
+ skipBefore: true,
1198
+ skipAfter: false,
1199
+ }
1200
+ );
1201
+ }
1202
+ async resetExecution({ tags = [] }) {
1203
+ // run after hooks followed by before hooks
1204
+ await this.cleanupExecution({ tags });
1205
+ await this.initExecution({ tags });
1206
+ }
1026
1207
 
1027
- const parseFeatureFile = (featureFilePath) => {
1028
- try {
1029
- let id = 0;
1030
- const uuidFn = () => (++id).toString(16);
1031
- const builder = new AstBuilder(uuidFn);
1032
- const matcher = new GherkinClassicTokenMatcher();
1033
- const parser = new Parser(builder, matcher);
1034
- const source = readFileSync(featureFilePath, "utf8");
1035
- const gherkinDocument = parser.parse(source);
1036
- return gherkinDocument;
1037
- } catch (e) {
1038
- console.log(e);
1208
+ parseFeatureFile(featureFilePath) {
1209
+ try {
1210
+ let id = 0;
1211
+ const uuidFn = () => (++id).toString(16);
1212
+ const builder = new AstBuilder(uuidFn);
1213
+ const matcher = new GherkinClassicTokenMatcher();
1214
+ const parser = new Parser(builder, matcher);
1215
+ const source = readFileSync(featureFilePath, "utf8");
1216
+ const gherkinDocument = parser.parse(source);
1217
+ return gherkinDocument;
1218
+ } catch (e) {
1219
+ this.logger.error(`Error parsing feature file: ${featureFilePath}`);
1220
+ console.log(e);
1221
+ }
1222
+ return {};
1039
1223
  }
1040
- return {};
1041
- };
1224
+ }