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