@dev-blinq/cucumber_client 1.0.1457-dev → 1.0.1457-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 (34) hide show
  1. package/bin/assets/bundled_scripts/recorder.js +73 -73
  2. package/bin/assets/scripts/recorder.js +87 -49
  3. package/bin/assets/scripts/snapshot_capturer.js +10 -17
  4. package/bin/assets/scripts/unique_locators.js +169 -47
  5. package/bin/assets/templates/_hooks_template.txt +6 -2
  6. package/bin/assets/templates/utils_template.txt +16 -16
  7. package/bin/client/code_cleanup/utils.js +16 -7
  8. package/bin/client/code_gen/code_inversion.js +115 -0
  9. package/bin/client/code_gen/duplication_analysis.js +2 -1
  10. package/bin/client/code_gen/function_signature.js +4 -0
  11. package/bin/client/code_gen/page_reflection.js +92 -11
  12. package/bin/client/code_gen/playwright_codeget.js +165 -76
  13. package/bin/client/cucumber/feature.js +4 -17
  14. package/bin/client/cucumber/steps_definitions.js +13 -0
  15. package/bin/client/local_agent.js +1 -0
  16. package/bin/client/recorderv3/bvt_init.js +320 -0
  17. package/bin/client/recorderv3/bvt_recorder.js +1312 -63
  18. package/bin/client/recorderv3/implemented_steps.js +2 -0
  19. package/bin/client/recorderv3/index.js +3 -293
  20. package/bin/client/recorderv3/services.js +819 -142
  21. package/bin/client/recorderv3/step_runner.js +35 -6
  22. package/bin/client/recorderv3/step_utils.js +175 -95
  23. package/bin/client/recorderv3/update_feature.js +87 -39
  24. package/bin/client/recorderv3/wbr_entry.js +61 -0
  25. package/bin/client/recording.js +1 -0
  26. package/bin/client/upload-service.js +2 -0
  27. package/bin/client/utils/app_dir.js +21 -0
  28. package/bin/client/utils/socket_logger.js +87 -125
  29. package/bin/index.js +4 -1
  30. package/package.json +11 -5
  31. package/bin/client/recorderv3/app_dir.js +0 -23
  32. package/bin/client/recorderv3/network.js +0 -299
  33. package/bin/client/recorderv3/scriptTest.js +0 -5
  34. package/bin/client/recorderv3/ws_server.js +0 -72
@@ -67,13 +67,12 @@ export class BVTStepRunner {
67
67
  resolve();
68
68
  }
69
69
  } else {
70
- console.warn(`No paused command found for cmdId: ${cmdId}`);
71
- socketLogger.error(`No paused command found for cmdId: ${cmdId}`);
70
+ socketLogger.error(`No paused command found for cmdId: ${cmdId}`, undefined, "BVTStepRunner.resumeExecution");
72
71
  }
73
72
  }
74
73
  }
75
74
 
76
- async copyCodetoTempFolder({ step, parametersMap, tempFolderPath }) {
75
+ async copyCodetoTempFolder({ step, parametersMap, tempFolderPath, AICode }) {
77
76
  if (!fs.existsSync(tempFolderPath)) {
78
77
  fs.mkdirSync(tempFolderPath);
79
78
  }
@@ -84,6 +83,18 @@ export class BVTStepRunner {
84
83
  overwrite: true,
85
84
  recursive: true,
86
85
  });
86
+
87
+ // If AICode is provided, save it as well
88
+ if (AICode) {
89
+ for (const { mjsFileContent, mjsFile } of AICode) {
90
+ const mjsPath = path
91
+ .normalize(mjsFile)
92
+ .split(path.sep)
93
+ .filter((part) => part !== "features")
94
+ .join(path.sep);
95
+ writeFileSync(path.join(tempFolderPath, mjsPath), mjsFileContent);
96
+ }
97
+ }
87
98
  }
88
99
 
89
100
  async writeTempFeatureFile({ step, parametersMap, tempFolderPath, tags }) {
@@ -250,7 +261,7 @@ export class BVTStepRunner {
250
261
  return { result, info };
251
262
  }
252
263
 
253
- async runStep({ step, parametersMap, envPath, tags, config }, bvtContext, options) {
264
+ async runStep({ step, parametersMap, envPath, tags, config, AICode }, bvtContext, options) {
254
265
  // Create a new AbortController for this specific step execution
255
266
  this.#currentStepController = new AbortController();
256
267
  const { signal } = this.#currentStepController;
@@ -284,13 +295,27 @@ export class BVTStepRunner {
284
295
  return cId;
285
296
  };
286
297
  }
298
+ if (bvtContext.api) {
299
+ bvtContext.api.getCmdId = () => {
300
+ if (cmdIDs.length === 0) {
301
+ cmdIDs = (step.commands || []).map((cmd) => cmd.cmdId ?? cmd.id);
302
+ }
303
+ const cId = cmdIDs.shift();
304
+ this.sendExecutionStatus({
305
+ type: "cmdExecutionStart",
306
+ cmdId: cId,
307
+ });
308
+ this.#lastAttemptedCmdId = cId;
309
+ return cId;
310
+ };
311
+ }
287
312
 
288
313
  const __temp_features_FolderName = "__temp_features" + Math.random().toString(36).substring(2, 7);
289
314
  const tempFolderPath = path.join(this.projectDir, __temp_features_FolderName);
290
315
  process.env.tempFeaturesFolderPath = __temp_features_FolderName;
291
316
  process.env.TESTCASE_REPORT_FOLDER_PATH = tempFolderPath;
292
317
 
293
- await this.copyCodetoTempFolder({ step, parametersMap, tempFolderPath });
318
+ await this.copyCodetoTempFolder({ step, parametersMap, tempFolderPath, AICode });
294
319
 
295
320
  // Write abort wrapper code with this step's signal
296
321
  await this.writeWrapperCode(tempFolderPath, signal);
@@ -306,7 +331,10 @@ export class BVTStepRunner {
306
331
  });
307
332
  }
308
333
 
309
- if (!step.isImplemented && step.commands.length > 0) {
334
+ if (
335
+ step.isUtilStep ||
336
+ ((!(step.isImplemented && !step.shouldOverride) || step.shouldMakeStepTextUnique) && step.commands.length > 0)
337
+ ) {
310
338
  const pageName = generatePageName(step.startFrame?.url ?? "default");
311
339
  const stepDefinitionFolderPath = path.join(tempFolderPath, "step_definitions");
312
340
  if (!existsSync(stepDefinitionFolderPath)) {
@@ -321,6 +349,7 @@ export class BVTStepRunner {
321
349
  projectDir: this.projectDir,
322
350
  stepsDefinitions,
323
351
  parametersMap,
352
+ logger: socketLogger,
324
353
  });
325
354
  if (codePage) {
326
355
  await codePage.save(stepDefsFilePath);
@@ -1,7 +1,6 @@
1
- import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
2
- import path from "path";
3
- import url from "url";
4
- import logger from "../../logger.js";
1
+ import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
2
+ import path from "node:path";
3
+ import url from "node:url";
5
4
  import { CodePage, getAiConfig } from "../code_gen/page_reflection.js";
6
5
  import { generateCode, generatePageName } from "../code_gen/playwright_codeget.js";
7
6
  import { invertCodeToCommand } from "../code_gen/code_inversion.js";
@@ -9,8 +8,9 @@ import { Step } from "../cucumber/feature.js";
9
8
  import { locateDefinitionPath, StepsDefinitions } from "../cucumber/steps_definitions.js";
10
9
  import { Recording } from "../recording.js";
11
10
  import { generateApiCode } from "../code_gen/api_codegen.js";
12
- import { tmpdir } from "os";
13
- import { createHash } from "crypto";
11
+ import { tmpdir } from "node:os";
12
+ import { createHash } from "node:crypto";
13
+ import { getErrorMessage } from "../utils/socket_logger.js";
14
14
 
15
15
  const __dirname = path.dirname(url.fileURLToPath(import.meta.url));
16
16
 
@@ -48,7 +48,7 @@ const replaceLastOccurence = (str, search, replacement) => {
48
48
  return str.substring(0, lastIndex) + replacement + str.substring(lastIndex + search.length);
49
49
  };
50
50
 
51
- const _toRecordingStep = (cmd) => {
51
+ export const _toRecordingStep = (cmd, stepText) => {
52
52
  switch (cmd.type) {
53
53
  case "hover_element": {
54
54
  return {
@@ -250,6 +250,7 @@ const _toRecordingStep = (cmd) => {
250
250
  },
251
251
  parameters: [cmd.selectedField, cmd.value],
252
252
  lastKnownUrlPath: cmd.lastKnownUrlPath,
253
+ valueInStepText: stepText && typeof stepText === "string" ? stepText.includes(`"${cmd.value}"`) : false,
253
254
  };
254
255
  }
255
256
  case "conditional_wait": {
@@ -261,6 +262,7 @@ const _toRecordingStep = (cmd) => {
261
262
  },
262
263
  parameters: [cmd.timeout, cmd.selectedField, cmd.value],
263
264
  lastKnownUrlPath: cmd.lastKnownUrlPath,
265
+ valueInStepText: stepText && typeof stepText === "string" ? stepText.includes(`"${cmd.value}"`) : false,
264
266
  };
265
267
  }
266
268
  case "navigate": {
@@ -296,6 +298,8 @@ const _toRecordingStep = (cmd) => {
296
298
  type: "verify_page_snapshot",
297
299
  parameters: [cmd.value],
298
300
  selectors: cmd.selectors,
301
+ data: cmd.data,
302
+ valueInStepText: stepText && typeof stepText === "string" ? stepText.includes(`"${cmd.value}"`) : false,
299
303
  };
300
304
  }
301
305
  default: {
@@ -332,45 +336,62 @@ const _parameterizeLocators = (locators, replacementFromValue, replacementToValu
332
336
  }
333
337
  return locators;
334
338
  };
335
- const parameterizeLocators = ({ cmd, locs, isValueVariable, isTextVariable, parametersMap }) => {
339
+ const parameterizeLocators = ({ cmd, locs, isValueVariable, isTargetValueVariable, parametersMap }) => {
336
340
  if (isValueVariable) {
337
341
  const variable = cmd.value.slice(1, -1);
338
- const val = parametersMap[variable];
339
- const replacementFromValue = val.trim();
340
- const replacementToValue = `{${variable}}`;
341
- locs = _parameterizeLocators(locs, replacementFromValue, replacementToValue);
342
- }
343
- if (isTextVariable) {
344
- const variable = cmd.text.slice(1, -1);
345
- const val = parametersMap[variable];
346
- const replacementFromValue = val.trim();
347
- const replacementToValue = `{${variable}}`;
348
- locs = _parameterizeLocators(locs, replacementFromValue, replacementToValue);
342
+ // const val = parametersMap[variable];
343
+ if (typeof cmd.text === "string") {
344
+ const replacementFromValue = cmd.text.trim().replace(/\s+/g, " ") ?? ""; // val.trim();
345
+ if (replacementFromValue.length > 0) {
346
+ const replacementToValue = `{${variable}}`;
347
+ locs = _parameterizeLocators(locs, replacementFromValue, replacementToValue);
348
+ }
349
+ }
350
+ }
351
+ if (isTargetValueVariable) {
352
+ const variable = cmd.targetValue.slice(1, -1);
353
+ // const val = parametersMap[variable];
354
+ if (typeof cmd.targetText === "string") {
355
+ const replacementFromValue = cmd.targetText.trim().replace(/\s+/g, " ") ?? ""; // val.trim();
356
+ if (replacementFromValue.length > 0) {
357
+ const replacementToValue = `{${variable}}`;
358
+ locs = _parameterizeLocators(locs, replacementFromValue, replacementToValue);
359
+ }
360
+ }
349
361
  }
350
362
  return locs;
351
363
  };
352
364
 
353
365
  //TODO: IMPORTAN
354
- export const toRecordingStep = (cmd, parametersMap) => {
366
+ export const toRecordingStep = (cmd, parametersMap, stepText) => {
355
367
  if (cmd.type === "api") {
356
368
  return {
357
369
  type: "api",
358
370
  value: cmd.value,
359
371
  };
360
372
  }
361
- const step = _toRecordingStep(cmd);
373
+ const step = _toRecordingStep(cmd, stepText);
362
374
  const cmdID = {
363
375
  cmdId: cmd.id,
376
+ options: cmd.options ?? null,
364
377
  };
365
378
  Object.assign(step, cmdID);
366
379
 
367
380
  const locatorsObject = JSON.parse(JSON.stringify(cmd.locators ?? null));
368
381
 
369
382
  if (!locatorsObject) return step;
383
+
384
+ const element_name = cmd?.locators?.element_name ?? `${cmd.label} ${cmd.role ?? "Text"}`;
385
+ locatorsObject.element_name = element_name;
386
+
370
387
  const isValueVariable = isVariable(cmd.value);
371
- const isTextVariable = isVariable(cmd.text);
388
+ const isTargetValueVariable = isVariable(cmd.targetValue);
389
+ const allStrategyLocators = JSON.parse(JSON.stringify(cmd?.allStrategyLocators ?? null));
390
+ step.locators = locatorsObject;
391
+ step.allStrategyLocators = allStrategyLocators;
392
+ step.isLocatorsAssigned = true;
372
393
 
373
- if (!isValueVariable && !isTextVariable) {
394
+ if (!isValueVariable && !isTargetValueVariable) {
374
395
  return step;
375
396
  }
376
397
 
@@ -378,7 +399,6 @@ export const toRecordingStep = (cmd, parametersMap) => {
378
399
  step.dataSource = "parameters";
379
400
  step.dataKey = convertToIdentifier(cmd.value.slice(1, -1));
380
401
  }
381
- const allStrategyLocators = JSON.parse(JSON.stringify(cmd?.allStrategyLocators ?? null));
382
402
 
383
403
  if (!allStrategyLocators) {
384
404
  let locs = locatorsObject.locators;
@@ -386,7 +406,7 @@ export const toRecordingStep = (cmd, parametersMap) => {
386
406
  cmd,
387
407
  locs,
388
408
  isValueVariable,
389
- isTextVariable,
409
+ isTargetValueVariable,
390
410
  parametersMap,
391
411
  });
392
412
  locatorsObject.locators = locs;
@@ -405,7 +425,7 @@ export const toRecordingStep = (cmd, parametersMap) => {
405
425
  cmd,
406
426
  locs: locators,
407
427
  isValueVariable,
408
- isTextVariable,
428
+ isTargetValueVariable,
409
429
  parametersMap,
410
430
  });
411
431
  }
@@ -442,7 +462,7 @@ export function getCodePage(stepDefsFilePath) {
442
462
  export function getCucumberStep({ step }) {
443
463
  const cucumberStep = new Step();
444
464
  cucumberStep.loadFromJson({
445
- text: step.text,
465
+ text: step.renamedText ? step.renamedText : step.text,
446
466
  keyword: step.keyword,
447
467
  keywordType: step.keywordType,
448
468
  parameters: [],
@@ -460,12 +480,16 @@ export function getCucumberStep({ step }) {
460
480
 
461
481
  function makeStepTextUnique(step, stepsDefinitions) {
462
482
  // const utilsFilePath = path.join("features", "step_definitions", "utils.mjs");
463
- let stepText = step.text;
483
+ let stepText = step.renamedText ? step.renamedText : step.text;
464
484
  let stepIndex = 1;
465
485
  // console.log("makeStepTextUnique", step.text);
466
486
  let stepDef = stepsDefinitions.findMatchingStep(stepText);
467
487
  // console.log({ stepDef });
468
- if (stepDef && stepDef?.file.endsWith("utils.mjs")) {
488
+ if (
489
+ stepDef &&
490
+ (stepDef?.file.endsWith("utils.mjs") || stepDef?.file.endsWith("default_page.mjs")) &&
491
+ !step.shouldMakeStepTextUnique
492
+ ) {
469
493
  return true;
470
494
  }
471
495
  while (stepDef) {
@@ -476,7 +500,18 @@ function makeStepTextUnique(step, stepsDefinitions) {
476
500
  step.text = stepText;
477
501
  }
478
502
 
479
- export async function saveRecording({ step, cucumberStep, codePage, projectDir, stepsDefinitions, parametersMap }) {
503
+ export async function saveRecording({
504
+ step,
505
+ cucumberStep,
506
+ codePage,
507
+ projectDir,
508
+ stepsDefinitions,
509
+ parametersMap,
510
+ logger,
511
+ }) {
512
+ if (step.commands && Array.isArray(step.commands)) {
513
+ step.commands = step.commands.map((cmd) => toRecordingStep(cmd, parametersMap, step.text));
514
+ }
480
515
  let routesPath = path.join(tmpdir(), "blinq_temp_routes");
481
516
 
482
517
  if (process.env.TEMP_RUN === "true") {
@@ -484,21 +519,19 @@ export async function saveRecording({ step, cucumberStep, codePage, projectDir,
484
519
  rmSync(routesPath, { recursive: true });
485
520
  }
486
521
  mkdirSync(routesPath, { recursive: true });
487
- saveRoutes({ step, folderPath: routesPath });
522
+ saveRoutes({ step, folderPath: routesPath }, logger);
488
523
  } else {
489
524
  if (existsSync(routesPath)) {
490
- // remove the folder
491
525
  try {
492
526
  rmSync(routesPath, { recursive: true });
493
- console.log("Removed temp_routes_folder:", routesPath);
494
527
  } catch (error) {
495
- console.error("Error removing temp_routes folder", error);
528
+ logger.error(`Error removing temp routes folder: ${getErrorMessage(error)}`);
496
529
  }
497
530
  routesPath = path.join(projectDir, "data", "routes");
498
531
  if (!existsSync(routesPath)) {
499
532
  mkdirSync(routesPath, { recursive: true });
500
533
  }
501
- saveRoutes({ step, folderPath: routesPath });
534
+ saveRoutes({ step, folderPath: routesPath }, logger);
502
535
  }
503
536
  }
504
537
 
@@ -506,49 +539,63 @@ export async function saveRecording({ step, cucumberStep, codePage, projectDir,
506
539
  return;
507
540
  }
508
541
 
542
+ let isUtilStep = false;
543
+ let isChangedUtilStepName = false;
544
+
509
545
  if (step.isImplemented && step.shouldOverride) {
510
- let stepDef = stepsDefinitions.findMatchingStep(step.text);
546
+ const stepDef = stepsDefinitions.findMatchingStep(step.text);
511
547
  codePage = getCodePage(stepDef.file);
512
548
  } else {
513
- const isUtilStep = makeStepTextUnique(step, stepsDefinitions);
549
+ isUtilStep = makeStepTextUnique(step, stepsDefinitions);
514
550
 
515
551
  if (isUtilStep) {
516
- return;
552
+ isChangedUtilStepName =
553
+ step.renamedText && stepsDefinitions.findMatchingStep(step.renamedText)?.file.endsWith("default_page.mjs");
554
+
555
+ if (!isChangedUtilStepName) {
556
+ const isUtilStep = stepsDefinitions.findMatchingStep(step.text)?.file.endsWith("utils.mjs");
557
+ if (!step.renamedText || step.renamedText === step.text || isUtilStep) {
558
+ return;
559
+ }
560
+ step.text = step.text.trim();
561
+ const { functionName } = stepsDefinitions.findMatchingStep(step.renamedText);
562
+ step.renamedText = functionName;
563
+ const newImportLine = `import { ${functionName} } from "./utils.mjs";\n`;
564
+
565
+ if (!codePage.fileContent.includes(newImportLine)) {
566
+ codePage.fileContent = newImportLine + codePage.fileContent;
567
+ }
568
+ }
517
569
  }
518
570
  }
519
571
 
572
+ const renamedUtil = step.renamedText && isUtilStep;
573
+
520
574
  routesPath = path.join(tmpdir(), "blinq_temp_routes");
521
575
  if (process.env.TEMP_RUN === "true") {
522
- console.log("Save routes in temp folder for running:", routesPath);
523
576
  if (existsSync(routesPath)) {
524
- console.log("Removing existing temp_routes_folder:", routesPath);
525
577
  rmSync(routesPath, { recursive: true });
526
578
  }
527
579
  mkdirSync(routesPath, { recursive: true });
528
- console.log("Created temp_routes_folder:", routesPath);
529
- saveRoutes({ step, folderPath: routesPath });
580
+ saveRoutes({ step, folderPath: routesPath }, logger);
530
581
  } else {
531
- console.log("Saving routes in project directory:", projectDir);
532
582
  if (existsSync(routesPath)) {
533
- // remove the folder
534
583
  try {
535
584
  rmSync(routesPath, { recursive: true });
536
- console.log("Removed temp_routes_folder:", routesPath);
537
585
  } catch (error) {
538
- console.error("Error removing temp_routes folder", error);
586
+ logger.error(`Error removing temp routes folder: ${getErrorMessage(error)}`);
539
587
  }
540
588
  }
541
589
  routesPath = path.join(projectDir, "data", "routes");
542
- console.log("Saving routes to:", routesPath);
543
590
  if (!existsSync(routesPath)) {
544
591
  mkdirSync(routesPath, { recursive: true });
545
592
  }
546
- saveRoutes({ step, folderPath: routesPath });
593
+ saveRoutes({ step, folderPath: routesPath }, logger);
547
594
  }
548
595
 
549
596
  cucumberStep.text = step.text;
550
597
  const recording = new Recording();
551
- step.commands = step.commands.map((cmd) => toRecordingStep(cmd, parametersMap));
598
+
552
599
  const steps = step.commands;
553
600
 
554
601
  recording.loadFromObject({ steps, step: cucumberStep });
@@ -599,7 +646,7 @@ export async function saveRecording({ step, cucumberStep, codePage, projectDir,
599
646
  stepsDefinitions
600
647
  );
601
648
 
602
- if (!step.isImplemented) {
649
+ if (!renamedUtil && !(step.isImplemented && step.shouldOverride)) {
603
650
  stepsDefinitions.addStep({
604
651
  name: step.text,
605
652
  file: result.codePage.sourceFileName,
@@ -611,13 +658,16 @@ export async function saveRecording({ step, cucumberStep, codePage, projectDir,
611
658
  return result.codePage;
612
659
  } else {
613
660
  const generateCodeResult = generateCode(recording, codePage, userData, projectDir, methodName);
614
- if (generateCodeResult.noCode === true) {
615
- logger.log("No code generated for step: " + step.text);
661
+ console.log("Generated code for step:", step.text);
662
+ if (!isUtilStep && generateCodeResult.noCode === true) {
616
663
  return generateCodeResult.page;
617
664
  }
618
665
  codePage = generateCodeResult.page;
619
666
  methodName = generateCodeResult.methodName;
620
- codePage.insertElements(generateCodeResult.elements);
667
+
668
+ if (!renamedUtil) {
669
+ codePage.insertElements(generateCodeResult.elements);
670
+ }
621
671
 
622
672
  const description = cucumberStep.text;
623
673
  let path = null;
@@ -630,15 +680,43 @@ export async function saveRecording({ step, cucumberStep, codePage, projectDir,
630
680
  protect = true;
631
681
  }
632
682
  }
633
- const infraResult = codePage.addInfraCommand(
634
- methodName,
635
- description,
636
- cucumberStep.getVariablesList(),
637
- generateCodeResult.codeLines,
638
- protect,
639
- "recorder",
640
- path
641
- );
683
+
684
+ if (renamedUtil) {
685
+ if (isChangedUtilStepName) {
686
+ const newFileContent = codePage.fileContent
687
+ .replace(`async function ${step.renamedText}`, `async function ${methodName}`)
688
+ .replace(`Then("${step.renamedText}"`, `// Then("${step.renamedText}"`)
689
+ .replace(`When("${step.renamedText}"`, `// When("${step.renamedText}"`)
690
+ .replace(`Given("${step.renamedText}"`, `// Given("${step.renamedText}"`);
691
+
692
+ codePage._init();
693
+ codePage.generateModel(newFileContent);
694
+ } else {
695
+ codePage.addInfraCommandUtil(
696
+ methodName,
697
+ description,
698
+ cucumberStep.parameters,
699
+ generateCodeResult.codeLines,
700
+ step.renamedText,
701
+ step.text,
702
+ parametersMap,
703
+ protect,
704
+ "recorder",
705
+ path
706
+ );
707
+ }
708
+ } else {
709
+ codePage.addInfraCommand(
710
+ methodName,
711
+ description,
712
+ cucumberStep.getVariablesList(),
713
+ generateCodeResult.codeLines,
714
+ protect,
715
+ "recorder",
716
+ path
717
+ );
718
+ }
719
+
642
720
  const keyword = (cucumberStep.keywordAlias ?? cucumberStep.keyword).trim();
643
721
  const stepResult = codePage.addCucumberStep(
644
722
  keyword,
@@ -648,7 +726,7 @@ export async function saveRecording({ step, cucumberStep, codePage, projectDir,
648
726
  step.finalTimeout
649
727
  );
650
728
 
651
- if (!step.isImplemented) {
729
+ if (!renamedUtil && !(step.isImplemented && step.shouldOverride)) {
652
730
  stepsDefinitions.addStep({
653
731
  name: step.text,
654
732
  file: codePage.sourceFileName,
@@ -657,6 +735,7 @@ export async function saveRecording({ step, cucumberStep, codePage, projectDir,
657
735
  }
658
736
 
659
737
  codePage.removeUnusedElements();
738
+ codePage.mergeSimilarElements();
660
739
  cucumberStep.methodName = methodName;
661
740
  if (generateCodeResult.locatorsMetadata) {
662
741
  codePage.addLocatorsMetadata(generateCodeResult.locatorsMetadata);
@@ -700,7 +779,7 @@ export const getCommandsForImplementedStep = (stepName, stepsDefinitions, stepPa
700
779
  const file = step?.file;
701
780
  const locatorsJson = getLocatorsJson(file);
702
781
  if (!step) {
703
- throw new Error("Step definition not found" + stepName);
782
+ throw new Error(`Step definition not found: ${stepName}`);
704
783
  }
705
784
  isImplemented = true;
706
785
  const { codeCommands, codePage, elements, parametersNames, error } =
@@ -708,26 +787,35 @@ export const getCommandsForImplementedStep = (stepName, stepsDefinitions, stepPa
708
787
  if (error) {
709
788
  throw new Error(error);
710
789
  }
790
+ isUtilStep = codePage.sourceFileName.endsWith("utils.mjs") || codePage.sourceFileName.endsWith("default_page.mjs");
711
791
 
712
792
  if (parametersNames.length !== stepParams.length) {
713
793
  // console.log("Parameters mismatch", parametersNames, stepParams);
714
794
  throw new Error("Parameters mismatch");
715
795
  }
716
- for (let i = 0; i < parametersNames.length; i++) {
717
- stepParams[i].argumentName = parametersNames[i];
718
- }
719
796
 
720
- isUtilStep = codePage.sourceFileName.endsWith("utils.mjs");
721
- for (const { code } of codeCommands) {
722
- const command = invertCodeToCommand(code, elements, stepParams, stepsDefinitions, codePage, stepName)[0];
723
- if (command === undefined || command.type === null) continue;
724
- if (command.element) {
725
- const key = command.element.key;
726
- if (key && locatorsJson[key]) {
727
- command.allStrategyLocators = locatorsJson[key];
797
+ const pattern = step.name;
798
+ if (isUtilStep && pattern === "Verify the file {string} exists") {
799
+ commands.push({
800
+ type: "verify_file_exists",
801
+ parameters: [stepParams[0].text],
802
+ });
803
+ } else {
804
+ for (let i = 0; i < parametersNames.length; i++) {
805
+ stepParams[i].argumentName = parametersNames[i];
806
+ }
807
+
808
+ for (const { code } of codeCommands) {
809
+ const command = invertCodeToCommand(code, elements, stepParams, stepsDefinitions, codePage, stepName)[0];
810
+ if (command === undefined || command.type === null) continue;
811
+ if (command.element) {
812
+ const key = command.element.key;
813
+ if (key && locatorsJson[key]) {
814
+ command.allStrategyLocators = locatorsJson[key];
815
+ }
728
816
  }
817
+ commands.push(command);
729
818
  }
730
- commands.push(command);
731
819
  }
732
820
  } catch (error) {
733
821
  console.error(error);
@@ -775,7 +863,7 @@ export async function executeStep({ stepsDefinitions, cucumberStep, context, cod
775
863
  }
776
864
  }
777
865
 
778
- export async function updateStepDefinitions({ scenario, featureName, projectDir }) {
866
+ export async function updateStepDefinitions({ scenario, featureName, projectDir, logger }) {
779
867
  // set the candidate step definition file name
780
868
  // set the utils file path
781
869
  const utilsFilePath = path.join(projectDir, "features", "step_definitions", "utils.mjs");
@@ -806,42 +894,37 @@ export async function updateStepDefinitions({ scenario, featureName, projectDir
806
894
  step.isImplementedWhileRecording = true;
807
895
  }
808
896
  }
809
- if ((step.isImplemented && !step.shouldOverride) || step.commands.length === 0) {
897
+ if (!step.isUtilStep && ((step.isImplemented && !step.shouldOverride) || step.commands.length === 0)) {
810
898
  let routesPath = path.join(tmpdir(), `blinq_temp_routes`);
811
899
  if (process.env.TEMP_RUN === "true") {
812
- console.log("Save routes in temp folder for running:", routesPath);
813
900
  if (existsSync(routesPath)) {
814
- console.log("Removing existing temp_routes_folder:", routesPath);
815
901
  routesPath = path.join(tmpdir(), `blinq_temp_routes`);
816
902
  rmSync(routesPath, { recursive: true });
817
903
  }
818
904
  mkdirSync(routesPath, { recursive: true });
819
- console.log("Created temp_routes_folder:", routesPath);
820
- saveRoutes({ step, folderPath: routesPath });
905
+ saveRoutes({ step, folderPath: routesPath }, logger);
821
906
  } else {
822
- console.log("Saving routes in project directory:", projectDir);
823
907
  if (existsSync(routesPath)) {
824
- // remove the folder
825
908
  try {
826
909
  rmSync(routesPath, { recursive: true });
827
- console.log("Removed temp_routes_folder:", routesPath);
828
910
  } catch (error) {
829
- console.error("Error removing temp_routes folder", error);
911
+ logger.error(`Error removing temp routes folder: ${getErrorMessage(error)}`);
830
912
  }
831
913
  }
832
914
  routesPath = path.join(projectDir, "data", "routes");
833
- console.log("Saving routes to:", routesPath);
834
915
  if (!existsSync(routesPath)) {
835
916
  mkdirSync(routesPath, { recursive: true });
836
917
  }
837
- saveRoutes({ step, folderPath: routesPath });
918
+ saveRoutes({ step, folderPath: routesPath }, logger);
919
+ }
920
+ if (step.commands && Array.isArray(step.commands)) {
921
+ step.commands = step.commands.map((cmd) => toRecordingStep(cmd, scenario.parametersMap));
838
922
  }
839
923
  continue;
840
924
  }
841
925
  const cucumberStep = getCucumberStep({ step });
842
926
  const pageName = generatePageName(step.startFrame?.url ?? "default");
843
927
  const stepDefsFilePath = locateDefinitionPath(featureFolder, pageName);
844
- // path.join(stepDefinitionFolderPath, pageName + "_page.mjs");
845
928
  let codePage = getCodePage(stepDefsFilePath);
846
929
  codePage = await saveRecording({
847
930
  step,
@@ -862,7 +945,7 @@ export async function updateStepDefinitions({ scenario, featureName, projectDir
862
945
  writeFileSync(utilsFilePath, utilsContent, "utf8");
863
946
  }
864
947
 
865
- export function saveRoutes({ step, folderPath }) {
948
+ export function saveRoutes({ step, folderPath }, logger) {
866
949
  const routeItems = step.routeItems;
867
950
  if (!routeItems || routeItems.length === 0) {
868
951
  return;
@@ -885,21 +968,18 @@ export function saveRoutes({ step, folderPath }) {
885
968
  };
886
969
  });
887
970
 
888
- const routesFilePath = path.join(folderPath, stepNameHash + ".json");
889
- console.log("Routes file path:", routesFilePath);
971
+ const routesFilePath = path.join(folderPath, `${stepNameHash}.json`);
890
972
  const routesData = {
891
973
  template,
892
974
  routes: routeItemsWithFilters,
893
975
  };
894
- console.log("Routes data to save:", routesData);
895
976
 
896
977
  if (!existsSync(folderPath)) {
897
978
  mkdirSync(folderPath, { recursive: true });
898
979
  }
899
980
  try {
900
981
  writeFileSync(routesFilePath, JSON.stringify(routesData, null, 2), "utf8");
901
- console.log("Saved routes to", routesFilePath);
902
982
  } catch (error) {
903
- console.error("Failed to save routes to", routesFilePath, "Error:", error);
983
+ logger.error(`Error saving routes to ${routesFilePath}: ${getErrorMessage(error)}`);
904
984
  }
905
985
  }