@dev-blinq/cucumber_client 1.0.1472-dev → 1.0.1472-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 (45) 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 +168 -40
  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 +125 -1
  9. package/bin/client/code_gen/duplication_analysis.js +2 -1
  10. package/bin/client/code_gen/function_signature.js +8 -0
  11. package/bin/client/code_gen/index.js +4 -0
  12. package/bin/client/code_gen/page_reflection.js +90 -9
  13. package/bin/client/code_gen/playwright_codeget.js +173 -77
  14. package/bin/client/codemod/find_harcoded_locators.js +173 -0
  15. package/bin/client/codemod/fix_hardcoded_locators.js +247 -0
  16. package/bin/client/codemod/index.js +8 -0
  17. package/bin/client/codemod/locators_array/find_misstructured_elements.js +148 -0
  18. package/bin/client/codemod/locators_array/fix_misstructured_elements.js +144 -0
  19. package/bin/client/codemod/locators_array/index.js +114 -0
  20. package/bin/client/codemod/types.js +1 -0
  21. package/bin/client/cucumber/feature.js +4 -17
  22. package/bin/client/cucumber/steps_definitions.js +17 -12
  23. package/bin/client/recorderv3/bvt_init.js +310 -0
  24. package/bin/client/recorderv3/bvt_recorder.js +1560 -1183
  25. package/bin/client/recorderv3/constants.js +45 -0
  26. package/bin/client/recorderv3/implemented_steps.js +2 -0
  27. package/bin/client/recorderv3/index.js +3 -293
  28. package/bin/client/recorderv3/mixpanel.js +41 -0
  29. package/bin/client/recorderv3/services.js +839 -142
  30. package/bin/client/recorderv3/step_runner.js +36 -7
  31. package/bin/client/recorderv3/step_utils.js +300 -98
  32. package/bin/client/recorderv3/update_feature.js +87 -39
  33. package/bin/client/recorderv3/utils.js +74 -0
  34. package/bin/client/recorderv3/wbr_entry.js +61 -0
  35. package/bin/client/recording.js +1 -0
  36. package/bin/client/types/locators.js +2 -0
  37. package/bin/client/upload-service.js +2 -0
  38. package/bin/client/utils/app_dir.js +21 -0
  39. package/bin/client/utils/socket_logger.js +100 -125
  40. package/bin/index.js +5 -0
  41. package/package.json +21 -6
  42. package/bin/client/recorderv3/app_dir.js +0 -23
  43. package/bin/client/recorderv3/network.js +0 -299
  44. package/bin/client/recorderv3/scriptTest.js +0 -5
  45. package/bin/client/recorderv3/ws_server.js +0 -72
@@ -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,11 @@ 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
+ import { FIXED_FOLDER_NAMES, SaveJobErrorType, UpdateStepDefinitionsError, UTF8_ENCODING } from "./constants.js";
15
+ import { handleFileInitOps, potentialErrorWrapper } from "./utils.js";
14
16
 
15
17
  const __dirname = path.dirname(url.fileURLToPath(import.meta.url));
16
18
 
@@ -48,7 +50,7 @@ const replaceLastOccurence = (str, search, replacement) => {
48
50
  return str.substring(0, lastIndex) + replacement + str.substring(lastIndex + search.length);
49
51
  };
50
52
 
51
- const _toRecordingStep = (cmd) => {
53
+ export const _toRecordingStep = (cmd, stepText) => {
52
54
  switch (cmd.type) {
53
55
  case "hover_element": {
54
56
  return {
@@ -250,6 +252,7 @@ const _toRecordingStep = (cmd) => {
250
252
  },
251
253
  parameters: [cmd.selectedField, cmd.value],
252
254
  lastKnownUrlPath: cmd.lastKnownUrlPath,
255
+ valueInStepText: stepText && typeof stepText === "string" ? stepText.includes(`"${cmd.value}"`) : false,
253
256
  };
254
257
  }
255
258
  case "conditional_wait": {
@@ -261,6 +264,7 @@ const _toRecordingStep = (cmd) => {
261
264
  },
262
265
  parameters: [cmd.timeout, cmd.selectedField, cmd.value],
263
266
  lastKnownUrlPath: cmd.lastKnownUrlPath,
267
+ valueInStepText: stepText && typeof stepText === "string" ? stepText.includes(`"${cmd.value}"`) : false,
264
268
  };
265
269
  }
266
270
  case "navigate": {
@@ -296,6 +300,8 @@ const _toRecordingStep = (cmd) => {
296
300
  type: "verify_page_snapshot",
297
301
  parameters: [cmd.value],
298
302
  selectors: cmd.selectors,
303
+ data: cmd.data,
304
+ valueInStepText: stepText && typeof stepText === "string" ? stepText.includes(`"${cmd.value}"`) : false,
299
305
  };
300
306
  }
301
307
  default: {
@@ -332,49 +338,62 @@ const _parameterizeLocators = (locators, replacementFromValue, replacementToValu
332
338
  }
333
339
  return locators;
334
340
  };
335
- const parameterizeLocators = ({ cmd, locs, isValueVariable, isTextVariable, parametersMap }) => {
341
+ const parameterizeLocators = ({ cmd, locs, isValueVariable, isTargetValueVariable, parametersMap }) => {
336
342
  if (isValueVariable) {
337
343
  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);
344
+ // const val = parametersMap[variable];
345
+ if (typeof cmd.text === "string") {
346
+ const replacementFromValue = cmd.text.trim().replace(/\s+/g, " ") ?? ""; // val.trim();
347
+ if (replacementFromValue.length > 0) {
348
+ const replacementToValue = `{${variable}}`;
349
+ locs = _parameterizeLocators(locs, replacementFromValue, replacementToValue);
350
+ }
351
+ }
352
+ }
353
+ if (isTargetValueVariable) {
354
+ const variable = cmd.targetValue.slice(1, -1);
355
+ // const val = parametersMap[variable];
356
+ if (typeof cmd.targetText === "string") {
357
+ const replacementFromValue = cmd.targetText.trim().replace(/\s+/g, " ") ?? ""; // val.trim();
358
+ if (replacementFromValue.length > 0) {
359
+ const replacementToValue = `{${variable}}`;
360
+ locs = _parameterizeLocators(locs, replacementFromValue, replacementToValue);
361
+ }
362
+ }
349
363
  }
350
364
  return locs;
351
365
  };
352
366
 
353
367
  //TODO: IMPORTAN
354
- export const toRecordingStep = (cmd, parametersMap) => {
368
+ export const toRecordingStep = (cmd, parametersMap, stepText) => {
355
369
  if (cmd.type === "api") {
356
370
  return {
357
371
  type: "api",
358
372
  value: cmd.value,
359
373
  };
360
374
  }
361
- const step = _toRecordingStep(cmd);
375
+ const step = _toRecordingStep(cmd, stepText);
362
376
  const cmdID = {
363
377
  cmdId: cmd.id,
378
+ options: cmd.options ?? null,
364
379
  };
365
380
  Object.assign(step, cmdID);
366
381
 
367
382
  const locatorsObject = JSON.parse(JSON.stringify(cmd.locators ?? null));
368
383
 
369
384
  if (!locatorsObject) return step;
385
+
386
+ const element_name = cmd?.locators?.element_name ?? `${cmd.label} ${cmd.role ?? "Text"}`;
387
+ locatorsObject.element_name = element_name;
388
+
370
389
  const isValueVariable = isVariable(cmd.value);
371
- const isTextVariable = isVariable(cmd.text);
390
+ const isTargetValueVariable = isVariable(cmd.targetValue);
372
391
  const allStrategyLocators = JSON.parse(JSON.stringify(cmd?.allStrategyLocators ?? null));
373
392
  step.locators = locatorsObject;
374
393
  step.allStrategyLocators = allStrategyLocators;
375
394
  step.isLocatorsAssigned = true;
376
395
 
377
- if (!isValueVariable && !isTextVariable) {
396
+ if (!isValueVariable && !isTargetValueVariable) {
378
397
  return step;
379
398
  }
380
399
 
@@ -389,7 +408,7 @@ export const toRecordingStep = (cmd, parametersMap) => {
389
408
  cmd,
390
409
  locs,
391
410
  isValueVariable,
392
- isTextVariable,
411
+ isTargetValueVariable,
393
412
  parametersMap,
394
413
  });
395
414
  locatorsObject.locators = locs;
@@ -408,7 +427,7 @@ export const toRecordingStep = (cmd, parametersMap) => {
408
427
  cmd,
409
428
  locs: locators,
410
429
  isValueVariable,
411
- isTextVariable,
430
+ isTargetValueVariable,
412
431
  parametersMap,
413
432
  });
414
433
  }
@@ -437,7 +456,7 @@ export function getCodePage(stepDefsFilePath) {
437
456
  return codePage;
438
457
  }
439
458
  // const templateFile = path.join(__dirname, "../../assets", "templates", "page_template.txt");
440
- // const initialCode = readFileSync(templateFile, "utf8");
459
+ // const initialCode = readFileSync(templateFile, UTF8_ENCODING);
441
460
  const codePage = new CodePage();
442
461
  codePage.generateModel();
443
462
  return codePage;
@@ -445,7 +464,7 @@ export function getCodePage(stepDefsFilePath) {
445
464
  export function getCucumberStep({ step }) {
446
465
  const cucumberStep = new Step();
447
466
  cucumberStep.loadFromJson({
448
- text: step.text,
467
+ text: step.renamedText ? step.renamedText : step.text,
449
468
  keyword: step.keyword,
450
469
  keywordType: step.keywordType,
451
470
  parameters: [],
@@ -463,12 +482,16 @@ export function getCucumberStep({ step }) {
463
482
 
464
483
  function makeStepTextUnique(step, stepsDefinitions) {
465
484
  // const utilsFilePath = path.join("features", "step_definitions", "utils.mjs");
466
- let stepText = step.text;
485
+ let stepText = step.renamedText ? step.renamedText : step.text;
467
486
  let stepIndex = 1;
468
487
  // console.log("makeStepTextUnique", step.text);
469
488
  let stepDef = stepsDefinitions.findMatchingStep(stepText);
470
489
  // console.log({ stepDef });
471
- if (stepDef && stepDef?.file.endsWith("utils.mjs")) {
490
+ if (
491
+ stepDef &&
492
+ (stepDef?.file.endsWith("utils.mjs") || stepDef?.file.endsWith("renamed_util.mjs")) &&
493
+ !step.shouldMakeStepTextUnique
494
+ ) {
472
495
  return true;
473
496
  }
474
497
  while (stepDef) {
@@ -479,9 +502,18 @@ function makeStepTextUnique(step, stepsDefinitions) {
479
502
  step.text = stepText;
480
503
  }
481
504
 
482
- export async function saveRecording({ step, cucumberStep, codePage, projectDir, stepsDefinitions, parametersMap }) {
505
+ export async function saveRecording({
506
+ step,
507
+ cucumberStep,
508
+ codePage,
509
+ projectDir,
510
+ stepsDefinitions,
511
+ parametersMap,
512
+ logger,
513
+ bvtSnapshotsDir,
514
+ }) {
483
515
  if (step.commands && Array.isArray(step.commands)) {
484
- step.commands = step.commands.map((cmd) => toRecordingStep(cmd, parametersMap));
516
+ step.commands = step.commands.map((cmd) => toRecordingStep(cmd, parametersMap, step.text));
485
517
  }
486
518
  let routesPath = path.join(tmpdir(), "blinq_temp_routes");
487
519
 
@@ -490,21 +522,19 @@ export async function saveRecording({ step, cucumberStep, codePage, projectDir,
490
522
  rmSync(routesPath, { recursive: true });
491
523
  }
492
524
  mkdirSync(routesPath, { recursive: true });
493
- saveRoutes({ step, folderPath: routesPath });
525
+ saveRoutes({ step, folderPath: routesPath }, logger);
494
526
  } else {
495
527
  if (existsSync(routesPath)) {
496
- // remove the folder
497
528
  try {
498
529
  rmSync(routesPath, { recursive: true });
499
- console.log("Removed temp_routes_folder:", routesPath);
500
530
  } catch (error) {
501
- console.error("Error removing temp_routes folder", error);
531
+ logger.error(`Error removing temp routes folder: ${getErrorMessage(error)}`);
502
532
  }
503
533
  routesPath = path.join(projectDir, "data", "routes");
504
534
  if (!existsSync(routesPath)) {
505
535
  mkdirSync(routesPath, { recursive: true });
506
536
  }
507
- saveRoutes({ step, folderPath: routesPath });
537
+ saveRoutes({ step, folderPath: routesPath }, logger);
508
538
  }
509
539
  }
510
540
 
@@ -512,44 +542,58 @@ export async function saveRecording({ step, cucumberStep, codePage, projectDir,
512
542
  return;
513
543
  }
514
544
 
545
+ let isUtilStep = false;
546
+ let isChangedUtilStepName = false;
547
+
515
548
  if (step.isImplemented && step.shouldOverride) {
516
- let stepDef = stepsDefinitions.findMatchingStep(step.text);
549
+ const stepDef = stepsDefinitions.findMatchingStep(step.text);
517
550
  codePage = getCodePage(stepDef.file);
518
551
  } else {
519
- const isUtilStep = makeStepTextUnique(step, stepsDefinitions);
552
+ isUtilStep = makeStepTextUnique(step, stepsDefinitions);
520
553
 
521
554
  if (isUtilStep) {
522
- return;
555
+ isChangedUtilStepName =
556
+ step.renamedText && stepsDefinitions.findMatchingStep(step.renamedText)?.file.endsWith("renamed_util.mjs");
557
+
558
+ if (!isChangedUtilStepName) {
559
+ const isUtilStep = stepsDefinitions.findMatchingStep(step.text)?.file.endsWith("utils.mjs");
560
+ if (!step.renamedText || step.renamedText === step.text || isUtilStep) {
561
+ return;
562
+ }
563
+ step.text = step.text.trim();
564
+ const { functionName } = stepsDefinitions.findMatchingStep(step.renamedText);
565
+ step.renamedText = functionName;
566
+ const newImportLine = `import { ${functionName} } from "./utils.mjs";\n`;
567
+
568
+ if (!codePage.fileContent.includes(newImportLine)) {
569
+ codePage.fileContent = newImportLine + codePage.fileContent;
570
+ }
571
+ }
523
572
  }
524
573
  }
525
574
 
575
+ const renamedUtil = step.renamedText && isUtilStep;
576
+
526
577
  routesPath = path.join(tmpdir(), "blinq_temp_routes");
527
578
  if (process.env.TEMP_RUN === "true") {
528
- console.log("Save routes in temp folder for running:", routesPath);
529
579
  if (existsSync(routesPath)) {
530
- console.log("Removing existing temp_routes_folder:", routesPath);
531
580
  rmSync(routesPath, { recursive: true });
532
581
  }
533
582
  mkdirSync(routesPath, { recursive: true });
534
- console.log("Created temp_routes_folder:", routesPath);
535
- saveRoutes({ step, folderPath: routesPath });
583
+ saveRoutes({ step, folderPath: routesPath }, logger);
536
584
  } else {
537
- console.log("Saving routes in project directory:", projectDir);
538
585
  if (existsSync(routesPath)) {
539
- // remove the folder
540
586
  try {
541
587
  rmSync(routesPath, { recursive: true });
542
- console.log("Removed temp_routes_folder:", routesPath);
543
588
  } catch (error) {
544
- console.error("Error removing temp_routes folder", error);
589
+ logger.error(`Error removing temp routes folder: ${getErrorMessage(error)}`);
545
590
  }
546
591
  }
547
592
  routesPath = path.join(projectDir, "data", "routes");
548
- console.log("Saving routes to:", routesPath);
549
593
  if (!existsSync(routesPath)) {
550
594
  mkdirSync(routesPath, { recursive: true });
551
595
  }
552
- saveRoutes({ step, folderPath: routesPath });
596
+ saveRoutes({ step, folderPath: routesPath }, logger);
553
597
  }
554
598
 
555
599
  cucumberStep.text = step.text;
@@ -605,7 +649,7 @@ export async function saveRecording({ step, cucumberStep, codePage, projectDir,
605
649
  stepsDefinitions
606
650
  );
607
651
 
608
- if (!step.isImplemented) {
652
+ if (!renamedUtil && !(step.isImplemented && step.shouldOverride)) {
609
653
  stepsDefinitions.addStep({
610
654
  name: step.text,
611
655
  file: result.codePage.sourceFileName,
@@ -617,13 +661,16 @@ export async function saveRecording({ step, cucumberStep, codePage, projectDir,
617
661
  return result.codePage;
618
662
  } else {
619
663
  const generateCodeResult = generateCode(recording, codePage, userData, projectDir, methodName);
620
- if (generateCodeResult.noCode === true) {
621
- logger.log("No code generated for step: " + step.text);
664
+ console.log("Generated code for step:", step.text);
665
+ if (!isUtilStep && generateCodeResult.noCode === true) {
622
666
  return generateCodeResult.page;
623
667
  }
624
668
  codePage = generateCodeResult.page;
625
669
  methodName = generateCodeResult.methodName;
626
- codePage.insertElements(generateCodeResult.elements);
670
+
671
+ if (!renamedUtil) {
672
+ codePage.insertElements(generateCodeResult.elements);
673
+ }
627
674
 
628
675
  const description = cucumberStep.text;
629
676
  let path = null;
@@ -636,15 +683,43 @@ export async function saveRecording({ step, cucumberStep, codePage, projectDir,
636
683
  protect = true;
637
684
  }
638
685
  }
639
- const infraResult = codePage.addInfraCommand(
640
- methodName,
641
- description,
642
- cucumberStep.getVariablesList(),
643
- generateCodeResult.codeLines,
644
- protect,
645
- "recorder",
646
- path
647
- );
686
+
687
+ if (renamedUtil) {
688
+ if (isChangedUtilStepName) {
689
+ const newFileContent = codePage.fileContent
690
+ .replace(`async function ${step.renamedText}`, `async function ${methodName}`)
691
+ .replace(`Then("${step.renamedText}"`, `// Then("${step.renamedText}"`)
692
+ .replace(`When("${step.renamedText}"`, `// When("${step.renamedText}"`)
693
+ .replace(`Given("${step.renamedText}"`, `// Given("${step.renamedText}"`);
694
+
695
+ codePage._init();
696
+ codePage.generateModel(newFileContent);
697
+ } else {
698
+ codePage.addInfraCommandUtil(
699
+ methodName,
700
+ description,
701
+ cucumberStep.parameters,
702
+ generateCodeResult.codeLines,
703
+ step.renamedText,
704
+ step.text,
705
+ parametersMap,
706
+ protect,
707
+ "recorder",
708
+ path
709
+ );
710
+ }
711
+ } else {
712
+ codePage.addInfraCommand(
713
+ methodName,
714
+ description,
715
+ cucumberStep.getVariablesList(),
716
+ generateCodeResult.codeLines,
717
+ protect,
718
+ "recorder",
719
+ path
720
+ );
721
+ }
722
+
648
723
  const keyword = (cucumberStep.keywordAlias ?? cucumberStep.keyword).trim();
649
724
  const stepResult = codePage.addCucumberStep(
650
725
  keyword,
@@ -654,7 +729,7 @@ export async function saveRecording({ step, cucumberStep, codePage, projectDir,
654
729
  step.finalTimeout
655
730
  );
656
731
 
657
- if (!step.isImplemented) {
732
+ if (!renamedUtil && !(step.isImplemented && step.shouldOverride)) {
658
733
  stepsDefinitions.addStep({
659
734
  name: step.text,
660
735
  file: codePage.sourceFileName,
@@ -689,7 +764,7 @@ const getLocatorsJson = (file) => {
689
764
  return {};
690
765
  }
691
766
  try {
692
- const locatorsJson = readFileSync(locatorsFilePath, "utf8");
767
+ const locatorsJson = readFileSync(locatorsFilePath, UTF8_ENCODING);
693
768
  return JSON.parse(locatorsJson);
694
769
  } catch (error) {
695
770
  console.error(error);
@@ -707,7 +782,7 @@ export const getCommandsForImplementedStep = (stepName, stepsDefinitions, stepPa
707
782
  const file = step?.file;
708
783
  const locatorsJson = getLocatorsJson(file);
709
784
  if (!step) {
710
- throw new Error("Step definition not found" + stepName);
785
+ throw new Error(`Step definition not found: ${stepName}`);
711
786
  }
712
787
  isImplemented = true;
713
788
  const { codeCommands, codePage, elements, parametersNames, error } =
@@ -715,26 +790,35 @@ export const getCommandsForImplementedStep = (stepName, stepsDefinitions, stepPa
715
790
  if (error) {
716
791
  throw new Error(error);
717
792
  }
793
+ isUtilStep = codePage.sourceFileName.endsWith("utils.mjs") || codePage.sourceFileName.endsWith("renamed_util.mjs");
718
794
 
719
795
  if (parametersNames.length !== stepParams.length) {
720
796
  // console.log("Parameters mismatch", parametersNames, stepParams);
721
797
  throw new Error("Parameters mismatch");
722
798
  }
723
- for (let i = 0; i < parametersNames.length; i++) {
724
- stepParams[i].argumentName = parametersNames[i];
725
- }
726
799
 
727
- isUtilStep = codePage.sourceFileName.endsWith("utils.mjs");
728
- for (const { code } of codeCommands) {
729
- const command = invertCodeToCommand(code, elements, stepParams, stepsDefinitions, codePage, stepName)[0];
730
- if (command === undefined || command.type === null) continue;
731
- if (command.element) {
732
- const key = command.element.key;
733
- if (key && locatorsJson[key]) {
734
- command.allStrategyLocators = locatorsJson[key];
800
+ const pattern = step.name;
801
+ if (isUtilStep && pattern === "Verify the file {string} exists") {
802
+ commands.push({
803
+ type: "verify_file_exists",
804
+ parameters: [stepParams[0].text],
805
+ });
806
+ } else {
807
+ for (let i = 0; i < parametersNames.length; i++) {
808
+ stepParams[i].argumentName = parametersNames[i];
809
+ }
810
+
811
+ for (const { code } of codeCommands) {
812
+ const command = invertCodeToCommand(code, elements, stepParams, stepsDefinitions, codePage, stepName)[0];
813
+ if (command === undefined || command.type === null) continue;
814
+ if (command.element) {
815
+ const key = command.element.key;
816
+ if (key && locatorsJson[key]) {
817
+ command.allStrategyLocators = locatorsJson[key];
818
+ }
735
819
  }
820
+ commands.push(command);
736
821
  }
737
- commands.push(command);
738
822
  }
739
823
  } catch (error) {
740
824
  console.error(error);
@@ -782,7 +866,7 @@ export async function executeStep({ stepsDefinitions, cucumberStep, context, cod
782
866
  }
783
867
  }
784
868
 
785
- export async function updateStepDefinitions({ scenario, featureName, projectDir }) {
869
+ export async function updateStepDefinitionsOld({ scenario, featureName, projectDir, logger }) {
786
870
  // set the candidate step definition file name
787
871
  // set the utils file path
788
872
  const utilsFilePath = path.join(projectDir, "features", "step_definitions", "utils.mjs");
@@ -813,42 +897,37 @@ export async function updateStepDefinitions({ scenario, featureName, projectDir
813
897
  step.isImplementedWhileRecording = true;
814
898
  }
815
899
  }
816
- if ((step.isImplemented && !step.shouldOverride) || step.commands.length === 0) {
900
+ if (!step.isUtilStep && ((step.isImplemented && !step.shouldOverride) || step.commands.length === 0)) {
817
901
  let routesPath = path.join(tmpdir(), `blinq_temp_routes`);
818
902
  if (process.env.TEMP_RUN === "true") {
819
- console.log("Save routes in temp folder for running:", routesPath);
820
903
  if (existsSync(routesPath)) {
821
- console.log("Removing existing temp_routes_folder:", routesPath);
822
904
  routesPath = path.join(tmpdir(), `blinq_temp_routes`);
823
905
  rmSync(routesPath, { recursive: true });
824
906
  }
825
907
  mkdirSync(routesPath, { recursive: true });
826
- console.log("Created temp_routes_folder:", routesPath);
827
- saveRoutes({ step, folderPath: routesPath });
908
+ saveRoutes({ step, folderPath: routesPath }, logger);
828
909
  } else {
829
- console.log("Saving routes in project directory:", projectDir);
830
910
  if (existsSync(routesPath)) {
831
- // remove the folder
832
911
  try {
833
912
  rmSync(routesPath, { recursive: true });
834
- console.log("Removed temp_routes_folder:", routesPath);
835
913
  } catch (error) {
836
- console.error("Error removing temp_routes folder", error);
914
+ logger.error(`Error removing temp routes folder: ${getErrorMessage(error)}`);
837
915
  }
838
916
  }
839
917
  routesPath = path.join(projectDir, "data", "routes");
840
- console.log("Saving routes to:", routesPath);
841
918
  if (!existsSync(routesPath)) {
842
919
  mkdirSync(routesPath, { recursive: true });
843
920
  }
844
- saveRoutes({ step, folderPath: routesPath });
921
+ saveRoutes({ step, folderPath: routesPath }, logger);
922
+ }
923
+ if (step.commands && Array.isArray(step.commands)) {
924
+ step.commands = step.commands.map((cmd) => toRecordingStep(cmd, scenario.parametersMap));
845
925
  }
846
926
  continue;
847
927
  }
848
928
  const cucumberStep = getCucumberStep({ step });
849
- const pageName = generatePageName(step.startFrame?.url ?? "default");
929
+ const pageName = generatePageName(step.startFrame?.url ?? "default", step.isUtilStep);
850
930
  const stepDefsFilePath = locateDefinitionPath(featureFolder, pageName);
851
- // path.join(stepDefinitionFolderPath, pageName + "_page.mjs");
852
931
  let codePage = getCodePage(stepDefsFilePath);
853
932
  codePage = await saveRecording({
854
933
  step,
@@ -869,7 +948,133 @@ export async function updateStepDefinitions({ scenario, featureName, projectDir
869
948
  writeFileSync(utilsFilePath, utilsContent, "utf8");
870
949
  }
871
950
 
872
- export function saveRoutes({ step, folderPath }) {
951
+ export async function updateStepDefinitions({ scenario, featureName, projectDir, logger, bvtSnapshotsDir }) {
952
+ try {
953
+ const { featureFolder, utilsFilePath, utilsContent } = handleFileInitOps(projectDir);
954
+ const steps = scenario.steps;
955
+
956
+ const stepsDefinitions = new StepsDefinitions(projectDir);
957
+ await potentialErrorWrapper(
958
+ () => stepsDefinitions.load(false),
959
+ "Failed to load step definitions",
960
+ SaveJobErrorType.STEP_DEFINITIONS_LOADING_FAILED,
961
+ logger
962
+ );
963
+
964
+ for (let index = 0; index < steps.length; index++) {
965
+ const step = steps[index];
966
+ try {
967
+ if (step.internalImplementedStepId) {
968
+ const si = steps.findIndex((s) => s.id === step.internalImplementedStepId);
969
+ if (si !== -1) {
970
+ step.isImplementedWhileRecording = true;
971
+ }
972
+ }
973
+
974
+ const processed = await potentialErrorWrapper(
975
+ () => {
976
+ if (!step.isUtilStep && ((step.isImplemented && !step.shouldOverride) || step.commands.length === 0)) {
977
+ let routesPath = path.join(tmpdir(), FIXED_FOLDER_NAMES.BLINQ_TEMP_ROUTES);
978
+ if (process.env.TEMP_RUN === "true") {
979
+ if (existsSync(routesPath)) rmSync(routesPath, { recursive: true });
980
+ mkdirSync(routesPath, { recursive: true });
981
+ saveRoutes({ step, folderPath: routesPath }, logger);
982
+ } else {
983
+ if (existsSync(routesPath)) {
984
+ try {
985
+ rmSync(routesPath, { recursive: true });
986
+ } catch (error) {
987
+ logger.error(`❌ Error removing temp routes folder: ${getErrorMessage(error)}`);
988
+ }
989
+ }
990
+ routesPath = path.join(projectDir, FIXED_FOLDER_NAMES.DATA, FIXED_FOLDER_NAMES.ROUTES);
991
+ if (!existsSync(routesPath)) mkdirSync(routesPath, { recursive: true });
992
+ saveRoutes({ step, folderPath: routesPath }, logger);
993
+ }
994
+
995
+ if (step.commands && Array.isArray(step.commands)) {
996
+ step.commands = step.commands.map((cmd) => toRecordingStep(cmd, scenario.parametersMap));
997
+ }
998
+
999
+ return true;
1000
+ } else {
1001
+ return false;
1002
+ }
1003
+ },
1004
+ "Failed to process step commands and routes",
1005
+ SaveJobErrorType.STEP_COMMANDS_PROCESSING_FAILED,
1006
+ logger
1007
+ );
1008
+ if (processed) continue;
1009
+
1010
+ const { cucumberStep, stepDefsFilePath } = await potentialErrorWrapper(
1011
+ () => {
1012
+ const cucumberStep = getCucumberStep({ step });
1013
+ const pageName = generatePageName(step.startFrame?.url ?? "default", step.isUtilStep);
1014
+ const stepDefsFilePath = locateDefinitionPath(featureFolder, pageName);
1015
+ return { cucumberStep, stepDefsFilePath };
1016
+ },
1017
+ "Failed to initialize cucumber step and locate definition path",
1018
+ SaveJobErrorType.STEP_INITIALIZATION_FAILED,
1019
+ logger
1020
+ );
1021
+
1022
+ const codePage = await potentialErrorWrapper(
1023
+ async () => {
1024
+ let codePage = getCodePage(stepDefsFilePath);
1025
+ codePage = await saveRecording({
1026
+ step,
1027
+ cucumberStep,
1028
+ codePage,
1029
+ projectDir,
1030
+ stepsDefinitions,
1031
+ parametersMap: scenario.parametersMap,
1032
+ bvtSnapshotsDir,
1033
+ });
1034
+ return codePage;
1035
+ },
1036
+ "Failed to generate and save step definition",
1037
+ SaveJobErrorType.STEP_DEFINITION_UPDATE_FAILED,
1038
+ logger
1039
+ );
1040
+
1041
+ if (!codePage) continue;
1042
+
1043
+ const res = await codePage.save();
1044
+ if (!res) {
1045
+ throw new UpdateStepDefinitionsError({
1046
+ type: SaveJobErrorType.STEP_DEFINITION_UPDATE_FAILED,
1047
+ message: `Failed to save step definition for "${cucumberStep.text}" in "${codePage.sourceFileName}"`,
1048
+ });
1049
+ }
1050
+ } catch (error) {
1051
+ throw error instanceof UpdateStepDefinitionsError
1052
+ ? error
1053
+ : new UpdateStepDefinitionsError({
1054
+ type: SaveJobErrorType.STEP_DEFINITION_UPDATE_FAILED,
1055
+ message: "Failed to update step definition",
1056
+ meta: {
1057
+ stepIndex: index,
1058
+ stepText: step.text,
1059
+ reason: getErrorMessage(error),
1060
+ },
1061
+ });
1062
+ }
1063
+ }
1064
+
1065
+ writeFileSync(utilsFilePath, utilsContent, UTF8_ENCODING);
1066
+ } catch (error) {
1067
+ logger.error(`❌ updateStepDefinitions() failed: ${error?.message ?? String(error)}`);
1068
+ throw error instanceof UpdateStepDefinitionsError
1069
+ ? error
1070
+ : new UpdateStepDefinitionsError({
1071
+ type: SaveJobErrorType.INTERNAL_ERROR,
1072
+ message: error?.message ?? "Unknown error",
1073
+ });
1074
+ }
1075
+ }
1076
+
1077
+ export function saveRoutes({ step, folderPath }, logger) {
873
1078
  const routeItems = step.routeItems;
874
1079
  if (!routeItems || routeItems.length === 0) {
875
1080
  return;
@@ -892,21 +1097,18 @@ export function saveRoutes({ step, folderPath }) {
892
1097
  };
893
1098
  });
894
1099
 
895
- const routesFilePath = path.join(folderPath, stepNameHash + ".json");
896
- console.log("Routes file path:", routesFilePath);
1100
+ const routesFilePath = path.join(folderPath, `${stepNameHash}.json`);
897
1101
  const routesData = {
898
1102
  template,
899
1103
  routes: routeItemsWithFilters,
900
1104
  };
901
- console.log("Routes data to save:", routesData);
902
1105
 
903
1106
  if (!existsSync(folderPath)) {
904
1107
  mkdirSync(folderPath, { recursive: true });
905
1108
  }
906
1109
  try {
907
- writeFileSync(routesFilePath, JSON.stringify(routesData, null, 2), "utf8");
908
- console.log("Saved routes to", routesFilePath);
1110
+ writeFileSync(routesFilePath, JSON.stringify(routesData, null, 2), UTF8_ENCODING);
909
1111
  } catch (error) {
910
- console.error("Failed to save routes to", routesFilePath, "Error:", error);
1112
+ logger.error(`❌ Error saving routes to ${routesFilePath}: ${getErrorMessage(error)}`);
911
1113
  }
912
1114
  }