@dev-blinq/cucumber_client 1.0.1265-dev → 1.0.1265-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 (38) hide show
  1. package/bin/assets/bundled_scripts/recorder.js +159 -14068
  2. package/bin/assets/preload/recorderv3.js +3 -1
  3. package/bin/assets/scripts/dom_parent.js +26 -0
  4. package/bin/assets/scripts/recorder.js +8 -4
  5. package/bin/assets/scripts/unique_locators.js +847 -547
  6. package/bin/assets/templates/_hooks_template.txt +37 -0
  7. package/bin/assets/templates/page_template.txt +2 -16
  8. package/bin/assets/templates/utils_template.txt +44 -71
  9. package/bin/client/apiTest/apiTest.js +6 -0
  10. package/bin/client/cli_helpers.js +11 -13
  11. package/bin/client/code_cleanup/utils.js +5 -1
  12. package/bin/client/code_gen/code_inversion.js +61 -4
  13. package/bin/client/code_gen/page_reflection.js +838 -899
  14. package/bin/client/code_gen/playwright_codeget.js +52 -13
  15. package/bin/client/cucumber/feature.js +89 -27
  16. package/bin/client/cucumber/project_to_document.js +1 -1
  17. package/bin/client/cucumber/steps_definitions.js +84 -76
  18. package/bin/client/cucumber_selector.js +17 -1
  19. package/bin/client/local_agent.js +5 -4
  20. package/bin/client/project.js +186 -196
  21. package/bin/client/recorderv3/bvt_recorder.js +290 -80
  22. package/bin/client/recorderv3/implemented_steps.js +74 -16
  23. package/bin/client/recorderv3/index.js +47 -25
  24. package/bin/client/recorderv3/network.js +299 -0
  25. package/bin/client/recorderv3/services.js +4 -16
  26. package/bin/client/recorderv3/step_runner.js +331 -67
  27. package/bin/client/recorderv3/step_utils.js +153 -5
  28. package/bin/client/recorderv3/update_feature.js +32 -30
  29. package/bin/client/recording.js +3 -0
  30. package/bin/client/run_cucumber.js +5 -1
  31. package/bin/client/scenario_report.js +0 -5
  32. package/bin/client/test_scenario.js +0 -1
  33. package/bin/client/utils/socket_logger.js +132 -0
  34. package/bin/index.js +1 -0
  35. package/bin/logger.js +3 -2
  36. package/bin/min/consoleApi.min.cjs +2 -3
  37. package/bin/min/injectedScript.min.cjs +16 -16
  38. package/package.json +24 -14
@@ -4,6 +4,7 @@ import { StepsDefinitions } from "../cucumber/steps_definitions.js";
4
4
  import path from "path";
5
5
  import { CodePage } from "./page_reflection.js";
6
6
  import { convertToIdentifier, escapeNonPrintables } from "./utils.js";
7
+ import socketLogger from "../utils/socket_logger.js";
7
8
  const findElementIdentifier = (node, step, userData, elements) => {
8
9
  if (node.key) {
9
10
  // incase of rerunning implemented steps
@@ -12,6 +13,13 @@ const findElementIdentifier = (node, step, userData, elements) => {
12
13
 
13
14
  let elementIdentifier = null;
14
15
  const keys = Object.keys(elements);
16
+
17
+ for (let i = 0; i < keys.length; i++) {
18
+ const element_key = keys[i];
19
+ const element = elements[element_key];
20
+ element["element_key"] = element_key;
21
+ }
22
+
15
23
  let name = node.name;
16
24
  if (!name && step.dataKey) {
17
25
  name = step.dataKey;
@@ -58,7 +66,8 @@ const _isCodeGenerationStep = (step) => {
58
66
  step.type === Types.VERIFY_ATTRIBUTE ||
59
67
  step.type === Types.VERIFY_PROPERTY ||
60
68
  step.type === Types.SET_INPUT_FILES ||
61
- step.type === Types.VERIFY_PAGE_SNAPSHOT
69
+ step.type === Types.VERIFY_PAGE_SNAPSHOT ||
70
+ step.type === Types.CONDITIONAL_WAIT
62
71
  ) {
63
72
  return true;
64
73
  }
@@ -91,7 +100,7 @@ const splitToLocatorsGroups = (locators) => {
91
100
  const _generateCodeFromCommand = (step, elements, userData) => {
92
101
  let elementsChanged = false;
93
102
 
94
- let elementIdentifier = null;
103
+ let elementIdentifier = step.locators?.element_key;
95
104
  let locatorObject = step.locators;
96
105
  // handle element
97
106
  let element_name = null;
@@ -129,18 +138,20 @@ const _generateCodeFromCommand = (step, elements, userData) => {
129
138
  let locatorObjectClone = JSON.parse(JSON.stringify(locatorObject));
130
139
  element_name = locatorObjectClone.element_name;
131
140
  delete locatorObjectClone.element_name;
132
- keys.forEach((key) => {
133
- let elementClone = JSON.parse(JSON.stringify(elements[key]));
134
- delete elementClone.element_name;
135
- if (JSON.stringify(elementClone) === JSON.stringify(locatorObjectClone)) {
136
- elementIdentifier = key;
137
- }
138
- });
141
+ if (!elementIdentifier) {
142
+ keys.forEach((key) => {
143
+ let elementClone = JSON.parse(JSON.stringify(elements[key]));
144
+ delete elementClone.element_name;
145
+ if (JSON.stringify(elementClone) === JSON.stringify(locatorObjectClone)) {
146
+ elementIdentifier = key;
147
+ }
148
+ });
149
+ }
139
150
  if (!elementIdentifier) {
140
151
  elementIdentifier = findElementIdentifier(node, step, userData, elements);
141
152
  }
142
153
  const withoutAllStrategyLocators = excludeKey(locatorObject, "allStrategyLocators");
143
- elements[elementIdentifier] = withoutAllStrategyLocators;
154
+ elements[elementIdentifier] = { ...withoutAllStrategyLocators, element_key: elementIdentifier };
144
155
  elementsChanged = true;
145
156
  }
146
157
  let optionElement = null;
@@ -641,6 +652,13 @@ const _generateCodeFromCommand = (step, elements, userData) => {
641
652
  codeLines.push(line);
642
653
  break;
643
654
  }
655
+ case Types.CONDITIONAL_WAIT: {
656
+ line = `await context.web.conditionalWait(elements["${elementIdentifier}"], `;
657
+ input = "_param_0";
658
+ line += `"${step.parameters[1]}", ${input}, _params, null, this);`;
659
+ codeLines.push(line);
660
+ break;
661
+ }
644
662
  case Types.SET_INPUT_FILES: {
645
663
  line = `await context.web.setInputFiles(elements["${elementIdentifier}"], `;
646
664
  let files = step.parameters[0];
@@ -666,6 +684,22 @@ const _generateCodeFromCommand = (step, elements, userData) => {
666
684
  return { codeLines, elements: elementsChanged ? elements : null, elementIdentifier, allStrategyLocators };
667
685
  };
668
686
 
687
+ /**
688
+ * Generates a report command based on the given position.
689
+ * @param {"start"|"end"} position
690
+ */
691
+ const generateReportCommand = (position, cmdId) => {
692
+ const codeLines = [];
693
+ if (position === "start") {
694
+ const line = `await context.web.addCommandToReport("${cmdId}", "PASSED", '{"status":"start","cmdId":"${cmdId}"}', {type:"cmdReport"},this);`;
695
+ codeLines.push(line);
696
+ } else if (position === "end") {
697
+ const line = `await context.web.addCommandToReport("${cmdId}", "PASSED", '{"status":"end","cmdId":"${cmdId}"}', {type:"cmdReport"},this);`;
698
+ codeLines.push(line);
699
+ }
700
+ return codeLines;
701
+ };
702
+
669
703
  const generateCode = (recording, codePage, userData, projectDir, methodName) => {
670
704
  const stepsDefinitions = new StepsDefinitions(projectDir);
671
705
  stepsDefinitions.load(false);
@@ -692,6 +726,7 @@ const generateCode = (recording, codePage, userData, projectDir, methodName) =>
692
726
  }
693
727
  let elements = {};
694
728
  if (!codePage) {
729
+ socketLogger.info("CodePage is null");
695
730
  console.log("codePage is null");
696
731
  } else {
697
732
  elements = codePage.getVariableDeclarationAsObject("elements");
@@ -706,12 +741,16 @@ const generateCode = (recording, codePage, userData, projectDir, methodName) =>
706
741
  if (recordingStep.type === Types.COMPLETE) {
707
742
  return;
708
743
  }
709
- // if (!recordingStep.element) {
710
- // logger.info(`Step ${recordingStep.type} doesn't have an element, ignoring`);
711
- // return;
744
+
745
+ // if (process.env.TEMP_RUN === "true") {
746
+ // codeLines.push(...generateReportCommand("start", recordingStep.cmdId));
712
747
  // }
713
748
  const result = _generateCodeFromCommand(recordingStep, elements, userData);
749
+
714
750
  codeLines.push(...result.codeLines);
751
+ // if (process.env.TEMP_RUN === "true") {
752
+ // codeLines.push(...generateReportCommand("end", recordingStep.cmdId));
753
+ // }
715
754
  if (result.elements) {
716
755
  elements = result.elements;
717
756
  }
@@ -1,11 +1,14 @@
1
+ import { IdGenerator } from "@cucumber/messages";
2
+ import { AstBuilder, GherkinClassicTokenMatcher, Parser, compile } from "@cucumber/gherkin";
1
3
  import { loadConfiguration, loadSupport, runCucumber } from "@dev-blinq/cucumber-js/api";
2
4
  import fs from "fs";
3
-
5
+ import os from "os";
6
+ import path from "path";
4
7
  import { parseStepTextParameters, toCucumberExpression, unEscapeNonPrintables } from "./utils.js";
5
- import { AstBuilder, GherkinClassicTokenMatcher, Parser, compile } from "@cucumber/gherkin";
6
- import { IdGenerator } from "@cucumber/messages";
8
+ import stream from "stream";
7
9
  import { testStringForRegex } from "../recorderv3/update_feature.js";
8
- import path from 'path';
10
+ import { generateTestData } from "./feature_data.js";
11
+ import socketLogger from "../utils/socket_logger.js";
9
12
  class DataTable {
10
13
  constructor(dataTableDocument) {
11
14
  if (!dataTableDocument) {
@@ -372,6 +375,11 @@ class Examples {
372
375
  return null;
373
376
  }
374
377
  let value = this.tableBody[0].cells[parameterIndex].value;
378
+ if (!value) {
379
+ console.warn(`Parameter ${parameterName} not found in examples table body, or it is empty`);
380
+ return null;
381
+ }
382
+
375
383
  return unEscapeNonPrintables(value); // While editing in recorder, we need to remove backslashes
376
384
  }
377
385
  }
@@ -449,29 +457,7 @@ class Scenario {
449
457
  return this.scenarioText;
450
458
  }
451
459
  }
452
- const scenarioResolution = async (featureFilePath) => {
453
- if (!fs.existsSync(featureFilePath)) {
454
- throw new Error(`Feature file ${featureFilePath} does not exist`);
455
- }
456
- const newId = IdGenerator.uuid();
457
- const builder = new AstBuilder(newId);
458
- const matcher = new GherkinClassicTokenMatcher();
459
-
460
- const parser = new Parser(builder, matcher);
461
-
462
- // normalize the path to start with featuers/ if it's not cut the featureFielPath to only the part after features/
463
- let uri = featureFilePath.startsWith("features/")
464
- ? featureFilePath
465
- : "features/" + featureFilePath.split("features/")[1];
466
-
467
- const featureFileContent = fs.readFileSync(featureFilePath, "utf8");
468
- const gherkinDocument = parser.parse(featureFileContent, newId);
469
460
 
470
- const feature = new Feature(gherkinDocument, featureFileContent);
471
- //const scenario = feature.getScenario(scenarioName);
472
- //return scenario;
473
- return feature;
474
- };
475
461
  const getDocumentAndPickel = (featureName, featureContent, scenarioName) => {
476
462
  const newId = IdGenerator.uuid();
477
463
  const builder = new AstBuilder(newId);
@@ -484,12 +470,88 @@ const getDocumentAndPickel = (featureName, featureContent, scenarioName) => {
484
470
 
485
471
  const gherkinDocument = parser.parse(featureContent, newId);
486
472
  const pickles = compile(gherkinDocument, uri, newId);
487
-
488
473
  // Step 3: Find the specific scenario Pickle
489
474
  const pickle = pickles.find((pickle) => {
490
475
  return pickle.name === scenarioName;
491
476
  });
492
477
  return { gherkinDocument, pickle };
493
478
  };
479
+ const scenarioResolution = async (featureFilePath) => {
480
+ if (!fs.existsSync(featureFilePath)) {
481
+ throw new Error(`Feature file ${featureFilePath} does not exist`);
482
+ }
483
+ const featureFileContent = fs.readFileSync(featureFilePath, "utf8");
484
+ let tmpDir = null;
485
+ try {
486
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "ai-qa"));
487
+ const tmpFeaturePath = path.join(tmpDir, "features");
488
+ fs.mkdirSync(tmpFeaturePath);
489
+ const tmpFeatureFilePath = path.join(tmpFeaturePath, path.basename(featureFilePath));
490
+ let result = generateTestData(featureFilePath);
491
+ if (result.changed) {
492
+ fs.writeFileSync(tmpFeatureFilePath, result.newContent);
493
+ console.log("Fake data was generated for this scenario");
494
+ console.log("Variables:");
495
+ for (let key in result.variables) {
496
+ console.log(`${key}: ${result.variables[key].fake}`);
497
+ }
498
+ console.log("Other fake data:");
499
+ for (let i = 0; i < result.otherFakeData.length; i++) {
500
+ console.log(`${result.otherFakeData[i].var}: ${result.otherFakeData[i].fake}`);
501
+ }
502
+ } else {
503
+ fs.copyFileSync(featureFilePath, tmpFeatureFilePath);
504
+ }
505
+ const writable = new stream.Writable({
506
+ write: function (chunk, encoding, next) {
507
+ //console.log(chunk.toString());
508
+ next();
509
+ },
510
+ });
511
+ const environment = { cwd: tmpDir, stdout: writable, stderr: writable };
512
+ // load configuration from a particular file, and override a specific option
513
+ const provided = {
514
+ default: "--publish-quiet",
515
+ require: [tmpDir],
516
+ failFast: false,
517
+ };
494
518
 
519
+ let gherkinDocument = null;
520
+ const { runConfiguration } = await loadConfiguration({ provided }, environment);
521
+ // load the support code upfront
522
+ const support = await loadSupport(runConfiguration, environment);
523
+ // run cucumber, using the support code we loaded already
524
+ await runCucumber({ ...runConfiguration, support }, environment, function (event) {
525
+ // if (event.source) {
526
+ // scenarioInfo.source = event.source.data;
527
+ // }
528
+ // if (event.pickle && event.pickle.name === scenarioName) {
529
+ // scenarioInfo.pickle = event.pickle;
530
+ // }
531
+ if (event.gherkinDocument) {
532
+ gherkinDocument = event.gherkinDocument;
533
+ }
534
+ //console.log(event);
535
+ //console.log(JSON.stringify(event, null, 2));
536
+ // console.log("");
537
+ });
538
+ const feature = new Feature(gherkinDocument, featureFileContent);
539
+ //const scenario = feature.getScenario(scenarioName);
540
+ //return scenario;
541
+ return feature;
542
+ } finally {
543
+ try {
544
+ if (tmpDir) {
545
+ fs.rmSync(tmpDir, { recursive: true });
546
+ }
547
+ } catch (e) {
548
+ socketLogger.error(
549
+ `An error has occurred while removing the temp folder at ${tmpDir}. Please remove it manually. Error: ${e}`
550
+ );
551
+ console.error(
552
+ `An error has occurred while removing the temp folder at ${tmpDir}. Please remove it manually. Error: ${e}`
553
+ );
554
+ }
555
+ }
556
+ };
495
557
  export { Feature, Scenario, Step, DataTable, Examples, scenarioResolution, getDocumentAndPickel };
@@ -15,7 +15,7 @@ export const projectDocument = (folder, featureName = null, scenarioName = null,
15
15
  // read all the feature files in the project folder under features folder
16
16
  let featureFiles = [];
17
17
  const featuresDir = path.join(folder, "features");
18
- featureFiles = fs.readdirSync(featuresDir).filter((file) => {
18
+ featureFiles = fs.readdirSync(featuresDir, { recursive: true }).filter((file) => {
19
19
  return file.endsWith(".feature");
20
20
  });
21
21
  const documents = [];
@@ -523,86 +523,94 @@ class StepsDefinitions {
523
523
  // }
524
524
  // );
525
525
  };
526
+ //! Deprecated, not removing until stepRunner.executeStepRemote is stable
527
+ // executeStepRemote = async ({ feature_file_path, scenario, tempFolderPath, stepText, onCommandResult }, options) => {
528
+ // // const { skipAfter = true, skipBefore = true } = options || {};
529
+ // const environment = {
530
+ // ...process.env,
531
+ // };
526
532
 
527
- executeStepRemote = async ({ feature_file_path, scenario, tempFolderPath, stepText }, options) => {
528
- if (!options) {
529
- options = {
530
- skipAfter: true,
531
- };
532
- }
533
- const environment = {
534
- ...process.env,
535
- };
533
+ // try {
534
+ // const { loadConfiguration, loadSupport, runCucumber } = await import("@dev-blinq/cucumber-js/api");
535
+ // const { runConfiguration } = await loadConfiguration(
536
+ // {
537
+ // provided: {
538
+ // name: [scenario],
539
+ // paths: [feature_file_path],
540
+ // import: [path.join(tempFolderPath, "step_definitions", "**", "*.mjs")],
541
+ // // format: ["bvt"],
542
+ // },
543
+ // },
544
+ // { cwd: process.cwd(), env: environment }
545
+ // );
546
+ // // const files = glob.sync(path.join(tempFolderPath, "step_definitions", "**", "*.mjs"));
547
+ // // console.log("Files found:", files);
548
+ // const support = await loadSupport(runConfiguration, { cwd: process.cwd(), env: environment });
549
+ // // console.log("found ", support.stepDefinitions.length, "step definitions");
550
+ // // support.stepDefinitions.map((step) => {
551
+ // // console.log("step", step.pattern);
552
+ // // });
536
553
 
537
- try {
538
- const { loadConfiguration, loadSupport, runCucumber } = await import("@dev-blinq/cucumber-js/api");
539
- const { runConfiguration } = await loadConfiguration(
540
- {
541
- provided: {
542
- name: [scenario],
543
- paths: [feature_file_path],
544
- import: [path.join(tempFolderPath, "step_definitions", "**", "*.mjs")],
545
- // format: ["bvt"],
546
- },
547
- },
548
- { cwd: process.cwd(), env: environment }
549
- );
550
- // const files = glob.sync(path.join(tempFolderPath, "step_definitions", "**", "*.mjs"));
551
- // console.log("Files found:", files);
552
- const support = await loadSupport(runConfiguration, { cwd: process.cwd(), env: environment });
553
- // console.log("found ", support.stepDefinitions.length, "step definitions");
554
- // support.stepDefinitions.map((step) => {
555
- // console.log("step", step.pattern);
556
- // });
554
+ // if (skipAfter) {
555
+ // // ignore afterAll/after hooks
556
+ // support.afterTestCaseHookDefinitions = [];
557
+ // support.afterTestRunHookDefinitions = [];
558
+ // }
559
+ // // if (skipBefore) {
560
+ // // // ignore beforeAll/before hooks
561
+ // // support.beforeTestCaseHookDefinitions = support.beforeTestCaseHookDefinitions.filter((hook) => {
562
+ // // return hook.uri.endsWith("utils.mjs")
563
+ // // });
564
+ // // support.beforeTestRunHookDefinitions = [];
565
+ // // }
557
566
 
558
- if (options.skipAfter) {
559
- // ignore afterAll/after hooks
560
- support.afterTestCaseHookDefinitions = [];
561
- support.afterTestRunHookDefinitions = [];
562
- }
567
+ // let errorMesssage = null;
568
+ // let info = null;
569
+ // let errInfo = null;
570
+ // const result = await runCucumber({ ...runConfiguration, support }, environment, (message) => {
571
+ // if (message.testStepFinished) {
572
+ // const { testStepFinished } = message;
573
+ // const { testStepResult } = testStepFinished;
574
+ // if (testStepResult.status === "FAILED" || testStepResult.status === "AMBIGUOUS") {
575
+ // if (!errorMesssage) {
576
+ // errorMesssage = testStepResult.message;
577
+ // if (info) {
578
+ // errInfo = info;
579
+ // }
580
+ // }
581
+ // }
582
+ // if (testStepResult.status === "UNDEFINED") {
583
+ // if (!errorMesssage) {
584
+ // errorMesssage = `step ${JSON.stringify(stepText)} is ${testStepResult.status}`;
585
+ // if (info) {
586
+ // errInfo = info;
587
+ // }
588
+ // }
589
+ // }
590
+ // }
591
+ // if (message.attachment) {
592
+ // const attachment = message.attachment;
593
+ // if (attachment.mediaType === "application/json" && attachment.body) {
594
+ // const body = JSON.parse(attachment.body);
595
+ // info = body.info;
596
+ // }
597
+ // }
598
+ // });
599
+ // if (errorMesssage) {
600
+ // const bvtError = new Error(errorMesssage);
601
+ // Object.assign(bvtError, { info: errInfo });
602
+ // throw bvtError;
603
+ // }
563
604
 
564
- let errorMesssage = null;
565
- let info = null;
566
- let errInfo = null;
567
- const result = await runCucumber({ ...runConfiguration, support }, environment, (message) => {
568
- if (message.testStepFinished) {
569
- const { testStepFinished } = message;
570
- const { testStepResult } = testStepFinished;
571
- if (testStepResult.status === "FAILED" || testStepResult.status === "AMBIGUOUS") {
572
- if (!errorMesssage) {
573
- errorMesssage = testStepResult.message;
574
- if (info) {
575
- errInfo = info;
576
- }
577
- }
578
- }
579
- if (testStepResult.status === "UNDEFINED") {
580
- if (!errorMesssage) {
581
- errorMesssage = `step ${JSON.stringify(stepText)} is ${testStepResult.status}`;
582
- if (info) {
583
- errInfo = info;
584
- }
585
- }
586
- }
587
- }
588
- if (message.attachment) {
589
- const attachment = message.attachment;
590
- if (attachment.mediaType === "application/json" && attachment.body) {
591
- const body = JSON.parse(attachment.body);
592
- info = body.info;
593
- }
594
- }
595
- });
596
- if (errorMesssage) {
597
- const bvtError = new Error(errorMesssage);
598
- Object.assign(bvtError, { info: errInfo });
599
- throw bvtError;
600
- }
601
- } catch (error) {
602
- console.error("Error running cucumber-js", error);
603
- throw error;
604
- }
605
- };
605
+ // return {
606
+ // result,
607
+ // info,
608
+ // };
609
+ // } catch (error) {
610
+ // console.error("Error running cucumber-js", error);
611
+ // throw error;
612
+ // }
613
+ // };
606
614
  }
607
615
  function findFilesWithExtension(folderPath, extension, rootFolder = null) {
608
616
  let result = [];
@@ -6,6 +6,7 @@ import crypto from "crypto";
6
6
  import { findFilesWithExtension } from "./cucumber/steps_definitions.js";
7
7
  import fs from "fs";
8
8
  //import { spawn } from "child_process";
9
+ const __dirname = path.dirname(new URL(import.meta.url).pathname);
9
10
 
10
11
  if (process.argv.length < 3) {
11
12
  console.log("Usage: node cucumber_selector.js <projectsDir> --all");
@@ -100,8 +101,8 @@ if (process.argv.includes("--scenario-name")) {
100
101
  }
101
102
  const projectName = selectedScenario.featureFile.split(path.sep)[0];
102
103
  let deleteMjsFiles = readlineSync.keyInYN("Delete mjs file?");
104
+ let stepsPath = path.join(selectedScenario.projectsDir, projectName, "features", "step_definitions");
103
105
  if (deleteMjsFiles) {
104
- let stepsPath = path.join(selectedScenario.projectsDir, projectName, "features", "step_definitions");
105
106
  if (fs.existsSync(stepsPath)) {
106
107
  // delete all the *.mjs files
107
108
  let files = fs.readdirSync(stepsPath);
@@ -112,6 +113,21 @@ if (deleteMjsFiles) {
112
113
  }
113
114
  }
114
115
  }
116
+ // copy utils file to the steps directory
117
+ const utilsPath = path.join(__dirname, "../assets/templates/utils_template.txt");
118
+ const utilsContent = fs.readFileSync(utilsPath, "utf8");
119
+ const utilsFileName = stepsPath + "/utils.mjs";
120
+ // if stepsPath directory doesn't exist create it
121
+ if (!fs.existsSync(stepsPath)) {
122
+ fs.mkdirSync(stepsPath, { recursive: true });
123
+ }
124
+ fs.writeFileSync(utilsFileName, utilsContent, "utf8");
125
+ // copy hooks file to the steps directory
126
+ const hooksTemplateFilePath = path.join(__dirname, "../assets/templates/_hooks_template.txt");
127
+ const hooksContent = fs.readFileSync(hooksTemplateFilePath, "utf8");
128
+ const hooksFilePath = path.join(stepsPath, "_hooks.mjs");
129
+ fs.writeFileSync(hooksFilePath, hooksContent, "utf8");
130
+
115
131
  process.env.PROJECT_PATH = path.join(selectedScenario.projectsDir, projectName);
116
132
  //chanks[currentChank][index];
117
133
  console.log("Running scenario:");
@@ -267,7 +267,7 @@ class LocalAgent {
267
267
  null,
268
268
  true,
269
269
  null,
270
- 450,
270
+ -1,
271
271
  null,
272
272
  initScript
273
273
  );
@@ -696,6 +696,7 @@ class LocalAgent {
696
696
  stepDiff: stepResult,
697
697
  });
698
698
  page.removeUnusedElements();
699
+ page.mergeSimilarElements();
699
700
  agent.scenarioReport.updateLastStep({
700
701
  code: {
701
702
  cleanFileNames: page.cleanFileNames,
@@ -971,7 +972,7 @@ class LocalAgent {
971
972
  async createNewStepLocal(
972
973
  featureName,
973
974
  cucumberStep,
974
- feature,
975
+ comments,
975
976
  userData,
976
977
  firstStep,
977
978
  previousTasks,
@@ -1087,7 +1088,7 @@ class LocalAgent {
1087
1088
  previousTasks,
1088
1089
  scenarioDocument,
1089
1090
  scenarioStepIndex,
1090
- comments: feature.comments,
1091
+ comments,
1091
1092
  featureFileText,
1092
1093
  recover,
1093
1094
  dumpConfig: this.dumpConfig,
@@ -1099,7 +1100,7 @@ class LocalAgent {
1099
1100
  if (this.context && this.context.web) {
1100
1101
  await this.context.web.afterStep(
1101
1102
  {
1102
- attach: (data, mimeType) => {},
1103
+ attach: (data, mimeType) => { },
1103
1104
  },
1104
1105
  this.cucumberStep
1105
1106
  );