@dev-blinq/cucumber_client 1.0.1456-dev → 1.0.1456-stage

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