@dev-blinq/cucumber_client 1.0.1475-dev → 1.0.1475-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 (47) hide show
  1. package/bin/assets/bundled_scripts/recorder.js +49 -49
  2. package/bin/assets/scripts/recorder.js +87 -34
  3. package/bin/assets/scripts/snapshot_capturer.js +10 -17
  4. package/bin/assets/scripts/unique_locators.js +78 -28
  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/codemod/find_harcoded_locators.js +173 -0
  8. package/bin/client/code_cleanup/codemod/fix_hardcoded_locators.js +247 -0
  9. package/bin/client/code_cleanup/utils.js +16 -7
  10. package/bin/client/code_gen/code_inversion.js +125 -1
  11. package/bin/client/code_gen/duplication_analysis.js +2 -1
  12. package/bin/client/code_gen/function_signature.js +8 -0
  13. package/bin/client/code_gen/index.js +4 -0
  14. package/bin/client/code_gen/page_reflection.js +90 -9
  15. package/bin/client/code_gen/playwright_codeget.js +173 -77
  16. package/bin/client/codemod/find_harcoded_locators.js +173 -0
  17. package/bin/client/codemod/fix_hardcoded_locators.js +247 -0
  18. package/bin/client/codemod/index.js +8 -0
  19. package/bin/client/codemod/locators_array/find_misstructured_elements.js +148 -0
  20. package/bin/client/codemod/locators_array/fix_misstructured_elements.js +144 -0
  21. package/bin/client/codemod/locators_array/index.js +114 -0
  22. package/bin/client/codemod/types.js +1 -0
  23. package/bin/client/cucumber/feature.js +4 -17
  24. package/bin/client/cucumber/steps_definitions.js +17 -12
  25. package/bin/client/recorderv3/bvt_init.js +310 -0
  26. package/bin/client/recorderv3/bvt_recorder.js +1560 -1183
  27. package/bin/client/recorderv3/constants.js +45 -0
  28. package/bin/client/recorderv3/implemented_steps.js +2 -0
  29. package/bin/client/recorderv3/index.js +3 -293
  30. package/bin/client/recorderv3/mixpanel.js +39 -0
  31. package/bin/client/recorderv3/services.js +839 -142
  32. package/bin/client/recorderv3/step_runner.js +36 -7
  33. package/bin/client/recorderv3/step_utils.js +316 -98
  34. package/bin/client/recorderv3/update_feature.js +85 -37
  35. package/bin/client/recorderv3/utils.js +80 -0
  36. package/bin/client/recorderv3/wbr_entry.js +61 -0
  37. package/bin/client/recording.js +1 -0
  38. package/bin/client/types/locators.js +2 -0
  39. package/bin/client/upload-service.js +2 -0
  40. package/bin/client/utils/app_dir.js +21 -0
  41. package/bin/client/utils/socket_logger.js +100 -125
  42. package/bin/index.js +5 -0
  43. package/package.json +21 -6
  44. package/bin/client/recorderv3/app_dir.js +0 -23
  45. package/bin/client/recorderv3/network.js +0 -299
  46. package/bin/client/recorderv3/scriptTest.js +0 -5
  47. 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,8 @@ export async function executeStep({ stepsDefinitions, cucumberStep, context, cod
782
866
  }
783
867
  }
784
868
 
785
- export async function updateStepDefinitions({ scenario, featureName, projectDir }) {
869
+ // deprecated
870
+ export async function updateStepDefinitionsOld({ scenario, featureName, projectDir, logger }) {
786
871
  // set the candidate step definition file name
787
872
  // set the utils file path
788
873
  const utilsFilePath = path.join(projectDir, "features", "step_definitions", "utils.mjs");
@@ -813,42 +898,37 @@ export async function updateStepDefinitions({ scenario, featureName, projectDir
813
898
  step.isImplementedWhileRecording = true;
814
899
  }
815
900
  }
816
- if ((step.isImplemented && !step.shouldOverride) || step.commands.length === 0) {
901
+ if (!step.isUtilStep && ((step.isImplemented && !step.shouldOverride) || step.commands.length === 0)) {
817
902
  let routesPath = path.join(tmpdir(), `blinq_temp_routes`);
818
903
  if (process.env.TEMP_RUN === "true") {
819
- console.log("Save routes in temp folder for running:", routesPath);
820
904
  if (existsSync(routesPath)) {
821
- console.log("Removing existing temp_routes_folder:", routesPath);
822
905
  routesPath = path.join(tmpdir(), `blinq_temp_routes`);
823
906
  rmSync(routesPath, { recursive: true });
824
907
  }
825
908
  mkdirSync(routesPath, { recursive: true });
826
- console.log("Created temp_routes_folder:", routesPath);
827
- saveRoutes({ step, folderPath: routesPath });
909
+ saveRoutes({ step, folderPath: routesPath }, logger);
828
910
  } else {
829
- console.log("Saving routes in project directory:", projectDir);
830
911
  if (existsSync(routesPath)) {
831
- // remove the folder
832
912
  try {
833
913
  rmSync(routesPath, { recursive: true });
834
- console.log("Removed temp_routes_folder:", routesPath);
835
914
  } catch (error) {
836
- console.error("Error removing temp_routes folder", error);
915
+ logger.error(`Error removing temp routes folder: ${getErrorMessage(error)}`);
837
916
  }
838
917
  }
839
918
  routesPath = path.join(projectDir, "data", "routes");
840
- console.log("Saving routes to:", routesPath);
841
919
  if (!existsSync(routesPath)) {
842
920
  mkdirSync(routesPath, { recursive: true });
843
921
  }
844
- saveRoutes({ step, folderPath: routesPath });
922
+ saveRoutes({ step, folderPath: routesPath }, logger);
923
+ }
924
+ if (step.commands && Array.isArray(step.commands)) {
925
+ step.commands = step.commands.map((cmd) => toRecordingStep(cmd, scenario.parametersMap));
845
926
  }
846
927
  continue;
847
928
  }
848
929
  const cucumberStep = getCucumberStep({ step });
849
- const pageName = generatePageName(step.startFrame?.url ?? "default");
930
+ const pageName = generatePageName(step.startFrame?.url ?? "default", step.isUtilStep);
850
931
  const stepDefsFilePath = locateDefinitionPath(featureFolder, pageName);
851
- // path.join(stepDefinitionFolderPath, pageName + "_page.mjs");
852
932
  let codePage = getCodePage(stepDefsFilePath);
853
933
  codePage = await saveRecording({
854
934
  step,
@@ -869,7 +949,148 @@ export async function updateStepDefinitions({ scenario, featureName, projectDir
869
949
  writeFileSync(utilsFilePath, utilsContent, "utf8");
870
950
  }
871
951
 
872
- export function saveRoutes({ step, folderPath }) {
952
+ export async function updateStepDefinitions({ scenario, featureName, projectDir, logger, bvtSnapshotsDir }) {
953
+ try {
954
+ logger.info("🔵 Starting updateStepDefinitions()");
955
+ const { featureFolder, utilsFilePath, utilsContent } = handleFileInitOps(projectDir, logger);
956
+ const steps = scenario.steps;
957
+
958
+ const stepsDefinitions = new StepsDefinitions(projectDir);
959
+ await potentialErrorWrapper(
960
+ () => stepsDefinitions.load(false),
961
+ "Failed to load step definitions",
962
+ SaveJobErrorType.STEP_DEFINITIONS_LOADING_FAILED,
963
+ logger
964
+ );
965
+
966
+ for (let index = 0; index < steps.length; index++) {
967
+ const step = steps[index];
968
+ try {
969
+ if (step.internalImplementedStepId) {
970
+ const si = steps.findIndex((s) => s.id === step.internalImplementedStepId);
971
+ if (si !== -1) {
972
+ step.isImplementedWhileRecording = true;
973
+ }
974
+ }
975
+
976
+ const processed = await potentialErrorWrapper(
977
+ () => {
978
+ if (!step.isUtilStep && ((step.isImplemented && !step.shouldOverride) || step.commands.length === 0)) {
979
+ let routesPath = path.join(tmpdir(), FIXED_FOLDER_NAMES.BLINQ_TEMP_ROUTES);
980
+ if (process.env.TEMP_RUN === "true") {
981
+ if (existsSync(routesPath)) rmSync(routesPath, { recursive: true });
982
+ mkdirSync(routesPath, { recursive: true });
983
+ saveRoutes({ step, folderPath: routesPath }, logger);
984
+ } else {
985
+ if (existsSync(routesPath)) {
986
+ try {
987
+ rmSync(routesPath, { recursive: true });
988
+ } catch (error) {
989
+ logger.error(`❌ Error removing temp routes folder: ${getErrorMessage(error)}`);
990
+ }
991
+ }
992
+ routesPath = path.join(projectDir, FIXED_FOLDER_NAMES.DATA, FIXED_FOLDER_NAMES.ROUTES);
993
+ if (!existsSync(routesPath)) mkdirSync(routesPath, { recursive: true });
994
+ saveRoutes({ step, folderPath: routesPath }, logger);
995
+ }
996
+
997
+ if (step.commands && Array.isArray(step.commands)) {
998
+ step.commands = step.commands.map((cmd) => toRecordingStep(cmd, scenario.parametersMap));
999
+ }
1000
+
1001
+ return true;
1002
+ } else {
1003
+ return false;
1004
+ }
1005
+ },
1006
+ "Failed to process step commands and routes",
1007
+ SaveJobErrorType.STEP_COMMANDS_PROCESSING_FAILED,
1008
+ logger
1009
+ );
1010
+ if (processed) continue;
1011
+
1012
+ const { cucumberStep, stepDefsFilePath } = await potentialErrorWrapper(
1013
+ () => {
1014
+ const cucumberStep = getCucumberStep({ step });
1015
+ const pageName = generatePageName(step.startFrame?.url ?? "default", step.isUtilStep);
1016
+ const stepDefsFilePath = locateDefinitionPath(featureFolder, pageName);
1017
+ return { cucumberStep, stepDefsFilePath };
1018
+ },
1019
+ "Failed to initialize cucumber step and locate definition path",
1020
+ SaveJobErrorType.STEP_INITIALIZATION_FAILED,
1021
+ logger
1022
+ );
1023
+
1024
+ const codePage = await potentialErrorWrapper(
1025
+ async () => {
1026
+ let codePage = getCodePage(stepDefsFilePath);
1027
+ codePage = await saveRecording({
1028
+ step,
1029
+ cucumberStep,
1030
+ codePage,
1031
+ projectDir,
1032
+ stepsDefinitions,
1033
+ parametersMap: scenario.parametersMap,
1034
+ bvtSnapshotsDir,
1035
+ });
1036
+ return codePage;
1037
+ },
1038
+ "Failed to generate and save step definition",
1039
+ SaveJobErrorType.STEP_DEFINITION_UPDATE_FAILED,
1040
+ logger
1041
+ );
1042
+
1043
+ if (!codePage) continue;
1044
+
1045
+ const res = await codePage.save();
1046
+ if (!res) {
1047
+ throw new UpdateStepDefinitionsError({
1048
+ type: SaveJobErrorType.STEP_DEFINITION_UPDATE_FAILED,
1049
+ message: `Failed to save step definition for "${cucumberStep.text}" in "${codePage.sourceFileName}"`,
1050
+ });
1051
+ }
1052
+ } catch (error) {
1053
+ throw error instanceof UpdateStepDefinitionsError
1054
+ ? error
1055
+ : new UpdateStepDefinitionsError({
1056
+ type: SaveJobErrorType.STEP_DEFINITION_UPDATE_FAILED,
1057
+ message: "Failed to update step definition",
1058
+ meta: {
1059
+ stepIndex: index,
1060
+ stepText: step.text,
1061
+ reason: getErrorMessage(error),
1062
+ },
1063
+ });
1064
+ }
1065
+ }
1066
+
1067
+ writeFileSync(utilsFilePath, utilsContent, UTF8_ENCODING);
1068
+ } catch (error) {
1069
+ logger.error(
1070
+ "❌ updateStepDefinitions() failed" +
1071
+ JSON.stringify(
1072
+ {
1073
+ message: error?.message,
1074
+ meta: error?.meta,
1075
+ stack: error?.stack,
1076
+ },
1077
+ null,
1078
+ 2
1079
+ ) +
1080
+ "\nStringified error: ❌" +
1081
+ getErrorMessage(error)
1082
+ );
1083
+ throw error instanceof UpdateStepDefinitionsError
1084
+ ? error
1085
+ : new UpdateStepDefinitionsError({
1086
+ type: SaveJobErrorType.INTERNAL_ERROR,
1087
+ message: error?.message ?? "Unknown error",
1088
+ meta: error.meta,
1089
+ });
1090
+ }
1091
+ }
1092
+
1093
+ export function saveRoutes({ step, folderPath }, logger) {
873
1094
  const routeItems = step.routeItems;
874
1095
  if (!routeItems || routeItems.length === 0) {
875
1096
  return;
@@ -892,21 +1113,18 @@ export function saveRoutes({ step, folderPath }) {
892
1113
  };
893
1114
  });
894
1115
 
895
- const routesFilePath = path.join(folderPath, stepNameHash + ".json");
896
- console.log("Routes file path:", routesFilePath);
1116
+ const routesFilePath = path.join(folderPath, `${stepNameHash}.json`);
897
1117
  const routesData = {
898
1118
  template,
899
1119
  routes: routeItemsWithFilters,
900
1120
  };
901
- console.log("Routes data to save:", routesData);
902
1121
 
903
1122
  if (!existsSync(folderPath)) {
904
1123
  mkdirSync(folderPath, { recursive: true });
905
1124
  }
906
1125
  try {
907
- writeFileSync(routesFilePath, JSON.stringify(routesData, null, 2), "utf8");
908
- console.log("Saved routes to", routesFilePath);
1126
+ writeFileSync(routesFilePath, JSON.stringify(routesData, null, 2), UTF8_ENCODING);
909
1127
  } catch (error) {
910
- console.error("Failed to save routes to", routesFilePath, "Error:", error);
1128
+ logger.error(`❌ Error saving routes to ${routesFilePath}: ${getErrorMessage(error)}`);
911
1129
  }
912
1130
  }