@dev-blinq/cucumber_client 1.0.1429-dev → 1.0.1429-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 (43) hide show
  1. package/bin/assets/bundled_scripts/recorder.js +73 -73
  2. package/bin/assets/preload/css_gen.js +10 -10
  3. package/bin/assets/preload/toolbar.js +27 -29
  4. package/bin/assets/preload/unique_locators.js +1 -1
  5. package/bin/assets/preload/yaml.js +288 -275
  6. package/bin/assets/scripts/aria_snapshot.js +223 -220
  7. package/bin/assets/scripts/dom_attr.js +329 -329
  8. package/bin/assets/scripts/dom_parent.js +169 -174
  9. package/bin/assets/scripts/event_utils.js +94 -94
  10. package/bin/assets/scripts/pw.js +2050 -1949
  11. package/bin/assets/scripts/recorder.js +70 -45
  12. package/bin/assets/scripts/snapshot_capturer.js +147 -147
  13. package/bin/assets/scripts/unique_locators.js +170 -49
  14. package/bin/assets/scripts/yaml.js +796 -783
  15. package/bin/assets/templates/_hooks_template.txt +6 -2
  16. package/bin/assets/templates/utils_template.txt +16 -16
  17. package/bin/client/code_cleanup/find_step_definition_references.js +0 -1
  18. package/bin/client/code_gen/api_codegen.js +2 -2
  19. package/bin/client/code_gen/code_inversion.js +63 -2
  20. package/bin/client/code_gen/function_signature.js +4 -0
  21. package/bin/client/code_gen/page_reflection.js +52 -11
  22. package/bin/client/code_gen/playwright_codeget.js +28 -22
  23. package/bin/client/cucumber/feature_data.js +2 -2
  24. package/bin/client/cucumber/project_to_document.js +8 -2
  25. package/bin/client/cucumber/steps_definitions.js +19 -3
  26. package/bin/client/local_agent.js +3 -2
  27. package/bin/client/parse_feature_file.js +23 -26
  28. package/bin/client/playground/projects/env.json +2 -2
  29. package/bin/client/recorderv3/bvt_init.js +363 -0
  30. package/bin/client/recorderv3/bvt_recorder.js +1009 -47
  31. package/bin/client/recorderv3/implemented_steps.js +2 -0
  32. package/bin/client/recorderv3/index.js +3 -283
  33. package/bin/client/recorderv3/scriptTest.js +1 -1
  34. package/bin/client/recorderv3/services.js +818 -142
  35. package/bin/client/recorderv3/step_runner.js +28 -8
  36. package/bin/client/recorderv3/step_utils.js +514 -39
  37. package/bin/client/recorderv3/update_feature.js +32 -13
  38. package/bin/client/recorderv3/wbr_entry.js +61 -0
  39. package/bin/client/recording.js +1 -0
  40. package/bin/client/upload-service.js +4 -2
  41. package/bin/client/utils/socket_logger.js +1 -1
  42. package/bin/index.js +4 -1
  43. package/package.json +6 -4
@@ -1,3 +1,7 @@
1
+ import dotenv from "dotenv";
2
+ // Load .env into process.env
3
+ dotenv.config();
4
+
1
5
  import {
2
6
  After,
3
7
  setDefaultTimeout,
@@ -30,8 +34,8 @@ BeforeStep(async function (step) {
30
34
  }
31
35
  });
32
36
 
33
- AfterStep(async function (step) {
37
+ AfterStep(async function ({ result, pickleStep }) {
34
38
  if (context) {
35
- await context.web.afterStep(this, step);
39
+ await context.web.afterStep(this, pickleStep, result);
36
40
  }
37
41
  });
@@ -12,7 +12,7 @@ import path from "path";
12
12
  * @param {string} text the text to verify exists in page
13
13
  * @protect
14
14
  */
15
- async function verifyTextExistsInPage(text) {
15
+ export async function verifyTextExistsInPage(text) {
16
16
  await context.web.verifyTextExistInPage(text, null, this);
17
17
  }
18
18
  Then("Verify the text {string} can be found in the page", verifyTextExistsInPage);
@@ -22,7 +22,7 @@ Then("Verify the text {string} can be found in the page", verifyTextExistsInPage
22
22
  * @param {string} elementDescription element description
23
23
  * @protect
24
24
  */
25
- async function clickOnElement(elementDescription) {
25
+ export async function clickOnElement(elementDescription) {
26
26
  await context.web.simpleClick(elementDescription, null, null, this);
27
27
  }
28
28
  When("click on {string}", clickOnElement);
@@ -36,7 +36,7 @@ When("Click {string}", clickOnElement);
36
36
  * @param {string} value value to fill the element with
37
37
  * @protect
38
38
  */
39
- async function fillElement(elementDescription, value) {
39
+ export async function fillElement(elementDescription, value) {
40
40
  await context.web.simpleClickType(elementDescription, value, null, null, this);
41
41
  }
42
42
  When("fill {string} with {string}", fillElement);
@@ -47,7 +47,7 @@ When("Fill {string} with {string}", fillElement);
47
47
  * @param {string} text the text to verify does not exist in page
48
48
  * @protect
49
49
  */
50
- async function verifyTextNotExistsInPage(text) {
50
+ export async function verifyTextNotExistsInPage(text) {
51
51
  await context.web.waitForTextToDisappear(text, null, this);
52
52
  }
53
53
  Then("Verify the text {string} cannot be found in the page", verifyTextNotExistsInPage);
@@ -57,7 +57,7 @@ Then("Verify the text {string} cannot be found in the page", verifyTextNotExists
57
57
  * @param {string} url URL to navigate
58
58
  * @protect
59
59
  */
60
- async function navigateTo(url) {
60
+ export async function navigateTo(url) {
61
61
  await context.web.goto(url, this);
62
62
  }
63
63
  When("Navigate to {string}", navigateTo);
@@ -66,7 +66,7 @@ When("Navigate to {string}", navigateTo);
66
66
  * Navigate to the current page
67
67
  * @protect
68
68
  */
69
- async function browserNavigateBack() {
69
+ export async function browserNavigateBack() {
70
70
  await context.web.goBack({}, this);
71
71
  }
72
72
  Then("Browser navigate back", browserNavigateBack);
@@ -75,7 +75,7 @@ Then("Browser navigate back", browserNavigateBack);
75
75
  * Navigate forward in browser history
76
76
  * @protect
77
77
  */
78
- async function browserNavigateForward() {
78
+ export async function browserNavigateForward() {
79
79
  await context.web.goForward({}, this);
80
80
  }
81
81
  Then("Browser navigate forward", browserNavigateForward);
@@ -85,7 +85,7 @@ Then("Browser navigate forward", browserNavigateForward);
85
85
  * @param {string} filePath the file path or empty to store in the test data file
86
86
  * @protect
87
87
  */
88
- async function storeBrowserSession(filePath) {
88
+ export async function storeBrowserSession(filePath) {
89
89
  await context.web.saveStoreState(filePath, this);
90
90
  }
91
91
  When("Store browser session {string}", storeBrowserSession);
@@ -95,7 +95,7 @@ When("Store browser session {string}", storeBrowserSession);
95
95
  * @param {string} filePath the file path or empty
96
96
  * @protect
97
97
  */
98
- async function resetBrowserSession(filePath) {
98
+ export async function resetBrowserSession(filePath) {
99
99
  await context.web.restoreSaveState(filePath, this);
100
100
  }
101
101
  When("Reset browser session {string}", resetBrowserSession);
@@ -107,7 +107,7 @@ When("Reset browser session {string}", resetBrowserSession);
107
107
  * @param {string} textToVerify the target text to verify
108
108
  * @protect
109
109
  */
110
- async function verifyTextRelatedToText(textAnchor, climb, textToVerify) {
110
+ export async function verifyTextRelatedToText(textAnchor, climb, textToVerify) {
111
111
  await context.web.verifyTextRelatedToText(textAnchor, climb, textToVerify, null, this);
112
112
  }
113
113
  Then(
@@ -120,7 +120,7 @@ Then(
120
120
  * @requestName the name of the bruno request file
121
121
  * @protect
122
122
  */
123
- async function runBrunoRequest(requestName) {
123
+ export async function runBrunoRequest(requestName) {
124
124
  await executeBrunoRequest(requestName, {}, context, this);
125
125
  }
126
126
  When("Bruno - {string}", runBrunoRequest);
@@ -131,7 +131,7 @@ When("bruno - {string}", runBrunoRequest);
131
131
  * @param {string} fileName the downloaded file to verify
132
132
  * @protect
133
133
  */
134
- async function verify_the_downloaded_file_exists(fileName) {
134
+ export async function verify_the_downloaded_file_exists(fileName) {
135
135
  const downloadFolder = path.join(context.reportFolder, "downloads");
136
136
  const downloadFile = path.join(downloadFolder, fileName);
137
137
  await verifyFileExists(downloadFile, {}, context, this);
@@ -148,7 +148,7 @@ When("Noop", async function () {});
148
148
  * @param {string} url URL to be verified against current URL
149
149
  * @protect
150
150
  */
151
- async function verify_page_url(url) {
151
+ export async function verify_page_url(url) {
152
152
  await context.web.verifyPagePath(url, {}, this);
153
153
  }
154
154
  Then("Verify the page url is {string}", verify_page_url);
@@ -158,7 +158,7 @@ Then("Verify the page url is {string}", verify_page_url);
158
158
  * @param {string} title Title to be verified against current Title
159
159
  * @protect
160
160
  */
161
- async function verify_page_title(title) {
161
+ export async function verify_page_title(title) {
162
162
  await context.web.verifyPageTitle(title, {}, this);
163
163
  }
164
164
  Then("Verify the page title is {string}", verify_page_title);
@@ -170,7 +170,7 @@ Then("Verify the page title is {string}", verify_page_title);
170
170
  * @param {world} - Optional world context
171
171
  * @returns Promise that resolves after the specified duration
172
172
  */
173
- async function sleep(duration) {
174
- await context.web.sleep(duration, {}, null);
173
+ export async function sleep(duration) {
174
+ await context.web.sleep(duration, {}, this);
175
175
  }
176
176
  Then("Sleep for {string} ms", { timeout: -1 }, sleep);
@@ -113,4 +113,3 @@ export const findStepDefinitionReferences = async (projectDir) => {
113
113
  }
114
114
  return referencesList;
115
115
  };
116
-
@@ -50,7 +50,7 @@ const generateApiCode = async (
50
50
  // create new codePage
51
51
  mjsFullPath = path.join(
52
52
  projectDir,
53
- stepsDefinitions.isTemp ? process.env.tempFeaturesFolderPath ?? "__temp_features" : "features",
53
+ stepsDefinitions.isTemp ? (process.env.tempFeaturesFolderPath ?? "__temp_features") : "features",
54
54
  "step_definitions",
55
55
  `api_tests_page.mjs`
56
56
  );
@@ -60,7 +60,7 @@ const generateApiCode = async (
60
60
  if (!codePage) {
61
61
  mjsFullPath = path.join(
62
62
  projectDir,
63
- stepsDefinitions.isTemp ? process.env.tempFeaturesFolderPath ?? "__temp_features" : "features",
63
+ stepsDefinitions.isTemp ? (process.env.tempFeaturesFolderPath ?? "__temp_features") : "features",
64
64
  "step_definitions",
65
65
  `api_tests_page.mjs`
66
66
  );
@@ -375,11 +375,13 @@ const invertStableCommand = (call, elements, stepParams) => {
375
375
  case "verifyTextRelatedToText": {
376
376
  step.type = Types.VERIFY_TEXT_RELATED_TO_TEXT;
377
377
  const textAnchorParse = parseDataSource(call.arguments[0], stepParams);
378
- const textAnchor = textAnchorParse.type === "literal" ? textAnchorParse.value : toVariableName(textAnchorParse.dataKey);
378
+ const textAnchor =
379
+ textAnchorParse.type === "literal" ? textAnchorParse.value : toVariableName(textAnchorParse.dataKey);
379
380
  const climbParse = parseDataSource(call.arguments[1], stepParams);
380
381
  const climb = climbParse.type === "literal" ? climbParse.value : toVariableName(climbParse.dataKey);
381
382
  const textToVerifyParse = parseDataSource(call.arguments[2], stepParams);
382
- const textToVerify = textToVerifyParse.type === "literal" ? textToVerifyParse.value : toVariableName(textToVerifyParse.dataKey);
383
+ const textToVerify =
384
+ textToVerifyParse.type === "literal" ? textToVerifyParse.value : toVariableName(textToVerifyParse.dataKey);
383
385
  step.parameters = [textAnchor, climb, textToVerify];
384
386
  break;
385
387
  }
@@ -473,6 +475,65 @@ const invertStableCommand = (call, elements, stepParams) => {
473
475
  }
474
476
  break;
475
477
  }
478
+
479
+ case "conditionalWait": {
480
+ step.type = Types.CONDITIONAL_WAIT;
481
+ step.element = extractElement(call.arguments[0]);
482
+ const condition = call.arguments[1].value;
483
+
484
+ const _timeout = parseDataSource(call.arguments[2], stepParams);
485
+ let timeout = 30;
486
+ if (_timeout.type === "literal") {
487
+ if (isNaN(_timeout.value)) {
488
+ throw new Error(`Timeout value must be a number, got ${_timeout.value}`);
489
+ }
490
+ timeout = Number(_timeout.value) * 1000;
491
+ } else {
492
+ step.dataSource = _timeout.dataSource;
493
+ step.dataKey = _timeout.dataKey;
494
+ timeout = toVariableName(_timeout.dataKey);
495
+ }
496
+ // step.timeout = timeout;
497
+ // step.value = "true";
498
+ step.parameters = [timeout, condition, step.value];
499
+ // step.condition = condition;
500
+ break;
501
+ }
502
+
503
+ case "sleep": {
504
+ step.type = Types.SLEEP;
505
+ const duration = parseDataSource(call.arguments[0], stepParams);
506
+ if (duration.type === "literal") {
507
+ if (isNaN(duration.value)) {
508
+ throw new Error(`Sleep duration must be a number, got ${duration.value}`);
509
+ }
510
+ step.parameters = [Number(duration.value)];
511
+ } else {
512
+ step.dataSource = duration.dataSource;
513
+ step.dataKey = duration.dataKey;
514
+ step.parameters = [toVariableName(duration.dataKey)];
515
+ }
516
+ break;
517
+ }
518
+ case "verify_file_exists": {
519
+ step.type = Types.VERIFY_FILE_EXISTS;
520
+ const filePath = parseDataSource(call.arguments[0], stepParams);
521
+ if (filePath.type === "literal") {
522
+ step.parameters = [filePath.value];
523
+ } else {
524
+ step.dataSource = filePath.dataSource;
525
+ step.dataKey = filePath.dataKey;
526
+ step.parameters = [toVariableName(filePath.dataKey)];
527
+ }
528
+ break;
529
+ }
530
+
531
+ // case "verifyPagePath":
532
+ // {
533
+ // step.type = Types.VERIFY_PAGE_PATH;
534
+ // step.parameters = [call.arguments[0].value];
535
+ // break;
536
+ // }
476
537
  default:
477
538
  return; // Skip if no matching method
478
539
  }
@@ -11,6 +11,8 @@ async function verify_the_opportunity_name_was_created(_name) {
11
11
  }
12
12
  */
13
13
 
14
+ import strip from "strip-comments";
15
+
14
16
  function generateSignature(page, functionName) {
15
17
  let functionCode = null;
16
18
  let method = null;
@@ -28,6 +30,8 @@ function generateSignature(page, functionName) {
28
30
  method.node.body.parent.params.forEach((param) => {
29
31
  parameters.push(param.name);
30
32
  });
33
+ const strippedFunctionCode = strip(functionCode);
34
+ functionCode = strippedFunctionCode !== "" ? strippedFunctionCode : functionCode;
31
35
  let lines = functionCode.split("\n");
32
36
  // trim all lines
33
37
  lines = lines.map((line) => line.trim());
@@ -170,7 +170,7 @@ export class CodePage {
170
170
  this.generateModel(fileContentNew);
171
171
  }
172
172
  catch (e) {
173
- logger.info("failed to format the code");
173
+ logger.error("failed to format the code");
174
174
  logger.debug(e);
175
175
  }
176
176
  if (!existsSync(this.sourceFileName)) {
@@ -288,6 +288,11 @@ export class CodePage {
288
288
  codePart.name = node.id.name;
289
289
  }
290
290
  }
291
+ findKey(funcString, key) {
292
+ const sourceRegex = new RegExp(`${key}:\\s*(.*)`);
293
+ const match = funcString.match(sourceRegex);
294
+ return match ? match[1] : null;
295
+ }
291
296
  collectAllTemplates() {
292
297
  const templates = [];
293
298
  if (this.cucumberCalls.length > 0) {
@@ -300,10 +305,12 @@ export class CodePage {
300
305
  const stepType = cucumberCall["stepType"];
301
306
  let firstFind = true;
302
307
  const stepPaths = [];
308
+ let source = null;
303
309
  for (let j = 0; j < this.methods.length; j++) {
304
310
  const method = this.methods[j];
305
311
  if (method.name === methodName) {
306
312
  if (firstFind) {
313
+ source = this.findKey(method.codePart, "source");
307
314
  foundMethod = true;
308
315
  const paramsObj = (method.node?.params ?? []);
309
316
  if (paramsObj && paramsObj.length > 0) {
@@ -315,7 +322,7 @@ export class CodePage {
315
322
  }
316
323
  }
317
324
  if (foundMethod) {
318
- templates.push({ pattern, methodName, params, stepType, paths: stepPaths });
325
+ templates.push({ pattern, methodName, params, stepType, paths: stepPaths, source });
319
326
  }
320
327
  }
321
328
  }
@@ -363,6 +370,39 @@ export class CodePage {
363
370
  return result;
364
371
  }
365
372
  }
373
+ addInfraCommandUtil(methodName, description, stepVariables, stepCodeLines, renamedText, previousText, parametersMap, protectStep = false, source = null, codePath = "") {
374
+ let code = "\n";
375
+ code += "/**\n";
376
+ code += ` * ${description}\n`;
377
+ const tags = [];
378
+ if (protectStep)
379
+ tags.push("@protect");
380
+ if (source)
381
+ tags.push(`@${source}`);
382
+ if (tags.length > 0)
383
+ code += ` * ${tags.join(" ")}\n`;
384
+ if (codePath !== null)
385
+ code += ` * @path=${escapeForComment(codePath)}\n`;
386
+ const matches = previousText.match(/"[^"]*"/g);
387
+ const countInMethodName = matches ? matches.length : 0;
388
+ code += " */\n";
389
+ code += `async function ${methodName} (${new Array(countInMethodName)
390
+ .fill(0)
391
+ .map((v, index) => `param${index}`)
392
+ .join(", ")}){\n`;
393
+ code += `// source: ${source}\n`;
394
+ code += `// implemented_at: ${new Date().toISOString()}\n`;
395
+ stepCodeLines.forEach((line) => (code += ` ${line}\n`));
396
+ if (renamedText === "verify_page_title" || renamedText === "verify_page_url") {
397
+ const parameters = stepVariables.map((v) => parametersMap[v.text.replace(/[<>]/g, "")]);
398
+ code += `await ${renamedText}(${parameters.map((v) => (isNaN(Number(v)) ? `"${v}"` : Number(v))).join(", ")});\n`;
399
+ }
400
+ else {
401
+ code += `await ${renamedText}(${stepVariables.map((v) => (isNaN(Number(v.text)) ? `"${v.text}"` : Number(v.text))).join(", ")});\n`;
402
+ }
403
+ code += "}\n";
404
+ return this._injectMethod(methodName, code);
405
+ }
366
406
  addInfraCommand(methodName, description, stepVarables, stepCodeLines, protectStep = false, source = null, codePath = "") {
367
407
  let code = "\n";
368
408
  code += "/**\n";
@@ -528,12 +568,13 @@ export class CodePage {
528
568
  for (let j = i - 1; j >= 0; j--) {
529
569
  const nextElement = elements[keys[j]];
530
570
  if (this._isSimilarElement(currentElement, nextElement)) {
531
- mergedElements[currentElement.element_key] = currentElement;
532
- replacedKeys[nextElement.element_key] = currentElement.element_key;
571
+ // mergedElements[currentElement.element_key!] = currentElement;
572
+ // replacedKeys[nextElement.element_key!] = currentElement.element_key!;
533
573
  foundMatch = true;
534
- keys.splice(j, 1);
535
- i--;
536
- j++;
574
+ break;
575
+ // keys.splice(j, 1);
576
+ // i--;
577
+ // j++;
537
578
  }
538
579
  }
539
580
  if (!foundMatch) {
@@ -543,7 +584,7 @@ export class CodePage {
543
584
  if (Object.keys(replacedKeys).length === 0)
544
585
  return;
545
586
  for (const key in replacedKeys) {
546
- const regexp = new RegExp(`elements\$begin:math:display$\\\\s*["']${key}["']\\\\s*\\$end:math:display$`, "g");
587
+ const regexp = new RegExp(`elements\\[\\s*["']${key}["']\\s*\\]`, "g");
547
588
  this.fileContent = this.fileContent.replace(regexp, () => `elements["${replacedKeys[key]}"]`);
548
589
  }
549
590
  this.insertElements(mergedElements);
@@ -620,7 +661,7 @@ export class CodePage {
620
661
  let locatorsMetadataFileName = this.sourceFileName.replace(".mjs", ".json");
621
662
  const config = getAiConfig();
622
663
  if (config && config.locatorsMetadataDir) {
623
- locatorsMetadataFileName = path.join(config.locatorsMetadataDir, path.basename(locatorsMetadataFileName));
664
+ locatorsMetadataFileName = locatorsMetadataFileName.replace(path.join("features", "step_definitions"), path.join(config.locatorsMetadataDir));
624
665
  if (!existsSync(path.dirname(locatorsMetadataFileName))) {
625
666
  mkdirSync(path.dirname(locatorsMetadataFileName), { recursive: true });
626
667
  }
@@ -632,7 +673,7 @@ export class CodePage {
632
673
  }
633
674
  }
634
675
  catch {
635
- logger.info("failed to read locators metadata file", locatorsMetadataFileName);
676
+ logger.error("failed to read locators metadata file", locatorsMetadataFileName);
636
677
  }
637
678
  const keys = Object.keys(locatorsMetadata);
638
679
  keys.forEach((key) => {
@@ -642,7 +683,7 @@ export class CodePage {
642
683
  writeFileSync(locatorsMetadataFileName, JSON.stringify(metadata, null, 2), "utf8");
643
684
  }
644
685
  catch {
645
- logger.info("failed to write locators metadata file", locatorsMetadataFileName);
686
+ logger.error("failed to write locators metadata file", locatorsMetadataFileName);
646
687
  }
647
688
  }
648
689
  _getVariableStartEnd(variableName) {
@@ -5,6 +5,8 @@ import path from "path";
5
5
  import { CodePage } from "./page_reflection.js";
6
6
  import { convertToIdentifier, escapeNonPrintables } from "./utils.js";
7
7
  import socketLogger from "../utils/socket_logger.js";
8
+ import fs from "fs";
9
+
8
10
  const findElementIdentifier = (node, step, userData, elements) => {
9
11
  if (node.key) {
10
12
  // incase of rerunning implemented steps
@@ -105,6 +107,11 @@ const _generateCodeFromCommand = (step, elements, userData) => {
105
107
  // handle element
106
108
  let element_name = null;
107
109
  let allStrategyLocators = null;
110
+ const codeLines = [];
111
+
112
+ if (_isCodeGenerationStep(step) === false)
113
+ return { codeLines, elements: elementsChanged ? elements : null, elementIdentifier, allStrategyLocators };
114
+
108
115
  if (_isCodeGenerationStep(step) && step.type !== Types.VERIFY_PAGE_SNAPSHOT) {
109
116
  allStrategyLocators = step.allStrategyLocators ?? splitToLocatorsGroups(step.locators);
110
117
  let node = null;
@@ -167,8 +174,6 @@ const _generateCodeFromCommand = (step, elements, userData) => {
167
174
  }
168
175
  }
169
176
 
170
- const codeLines = [];
171
-
172
177
  let line = null;
173
178
  let comment = null;
174
179
  let input = null;
@@ -363,16 +368,33 @@ const _generateCodeFromCommand = (step, elements, userData) => {
363
368
  codeLines.push(line);
364
369
  break;
365
370
  }
366
- case Types.VERIFY_PAGE_SNAPSHOT:
371
+ case Types.VERIFY_PAGE_SNAPSHOT: {
367
372
  comment = step.nestFrmLoc
368
373
  ? `// Verify page snapshot ${step.parameters[0]} in the frame ${step.selectors.iframe_src}`
369
374
  : `// Verify page snapshot ${step.parameters[0]}`;
370
375
  codeLines.push(escapeNonPrintables(comment));
371
376
  line = `const frameLocator = ${JSON.stringify(step.selectors ?? null)}`;
372
377
  codeLines.push(line);
373
- line = `await context.web.snapshotValidation(frameLocator, _param_0 , _params, ${JSON.stringify(options)}, this);`;
378
+ line = `await context.web.snapshotValidation(frameLocator,${step.valueInStepText ? "_param_0" : `"${step.parameters[0]}"`}, _params, ${JSON.stringify(options)}, this);`;
374
379
  codeLines.push(line);
380
+
381
+ const data = step.data;
382
+ if (data) {
383
+ try {
384
+ const { snapshot, fileName, filePath } = data;
385
+ const folderPath = process.env.BVT_TEMP_SNAPSHOTS_FOLDER ?? filePath;
386
+ const filePathWithName = path.join(folderPath, fileName);
387
+ if (!fs.existsSync(folderPath)) {
388
+ fs.mkdirSync(folderPath, { recursive: true });
389
+ }
390
+ fs.writeFileSync(filePathWithName, snapshot, "utf-8");
391
+ } catch (e) {
392
+ console.log(`Error saving snapshot file: ${e}`);
393
+ throw e;
394
+ }
395
+ }
375
396
  break;
397
+ }
376
398
  case Types.VERIFY_PAGE_CONTAINS_TEXT:
377
399
  line = "await context.web.verifyTextExistInPage( ";
378
400
  input = `${escapeNonPrintables(JSON.stringify(step.parameters[0]))}`;
@@ -644,7 +666,7 @@ const _generateCodeFromCommand = (step, elements, userData) => {
644
666
  }
645
667
  case Types.VERIFY_PROPERTY: {
646
668
  line = `await context.web.verifyProperty(elements["${elementIdentifier}"], `;
647
- input = "_param_0";
669
+ input = step.valueInStepText ? "_param_0" : `"${escapeNonPrintables(step.parameters[1].replaceAll('"', '\\"'))}"`;
648
670
  if (step.dataSource === "userData" || step.dataSource === "parameters") {
649
671
  input = "_" + step.dataKey;
650
672
  }
@@ -654,7 +676,7 @@ const _generateCodeFromCommand = (step, elements, userData) => {
654
676
  }
655
677
  case Types.CONDITIONAL_WAIT: {
656
678
  line = `await context.web.conditionalWait(elements["${elementIdentifier}"], `;
657
- input = "_param_0";
679
+ input = step.valueInStepText ? "_param_0" : `"${escapeNonPrintables(step.parameters[1].replaceAll('"', '\\"'))}"`;
658
680
  line += `"${step.parameters[1]}", ${input}, _params, null, this);`;
659
681
  codeLines.push(line);
660
682
  break;
@@ -684,22 +706,6 @@ const _generateCodeFromCommand = (step, elements, userData) => {
684
706
  return { codeLines, elements: elementsChanged ? elements : null, elementIdentifier, allStrategyLocators };
685
707
  };
686
708
 
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
-
703
709
  const generateCode = (recording, codePage, userData, projectDir, methodName) => {
704
710
  const stepsDefinitions = new StepsDefinitions(projectDir);
705
711
  stepsDefinitions.load(false);
@@ -41,10 +41,10 @@ const generateTestData = (featureFile) => {
41
41
  try {
42
42
  const fake = faker.helpers.fake(match[0]);
43
43
 
44
- if(fake === match[0]){
44
+ if (fake === match[0]) {
45
45
  continue;
46
46
  }
47
-
47
+
48
48
  otherFakeData.push({
49
49
  var: match[0],
50
50
  fake,
@@ -4,7 +4,13 @@ import { AstBuilder, GherkinClassicTokenMatcher, Parser } from "@cucumber/gherki
4
4
  import { Feature } from "./feature.js";
5
5
  import { StepsDefinitions } from "./steps_definitions.js";
6
6
 
7
- export const projectDocument = (folder, featureName = null, scenarioName = null, supressErrors = false, errors = []) => {
7
+ export const projectDocument = (
8
+ folder,
9
+ featureName = null,
10
+ scenarioName = null,
11
+ supressErrors = false,
12
+ errors = []
13
+ ) => {
8
14
  const uuidFn = () => (23212).toString(16);
9
15
  const builder = new AstBuilder(uuidFn);
10
16
  const matcher = new GherkinClassicTokenMatcher();
@@ -59,4 +65,4 @@ export const projectDocument = (folder, featureName = null, scenarioName = null,
59
65
  // const documents = projectDocument(projectDir, featureName, scenarioName, true, errors);
60
66
  // const args = process.argv.slice(2);
61
67
 
62
- // console.log(JSON.stringify(documents));
68
+ // console.log(JSON.stringify(documents));
@@ -36,6 +36,19 @@ class StepsDefinitions {
36
36
  // }
37
37
  // });
38
38
  const { expressions, methods } = codePage;
39
+
40
+ if (codePage.fileContent.includes('from "./utils.mjs"')) {
41
+ const filePath = path.join(
42
+ this.baseFolder,
43
+ this.isTemp ? (process.env.tempFeaturesFolderPath ?? "__temp_features") : "features",
44
+ "step_definitions",
45
+ "utils.mjs"
46
+ );
47
+ const utilsCodePage = new CodePage(filePath);
48
+ utilsCodePage.generateModel();
49
+ methods.push(...utilsCodePage.methods);
50
+ }
51
+
39
52
  for (let i = 0; i < expressions.length; i++) {
40
53
  const expression = expressions[i];
41
54
  const pattern = expression.pattern;
@@ -45,7 +58,10 @@ class StepsDefinitions {
45
58
  if (method && expression.keyword && pattern && expression.methodName) {
46
59
  this.steps[pattern] = {
47
60
  // file: path.join(this.isTemp ? "__temp_features" : "features", mjsFile),
48
- file: path.join(this.isTemp ? process.env.tempFeaturesFolderPath ?? "__temp_features" : "features", mjsFile),
61
+ file: path.join(
62
+ this.isTemp ? (process.env.tempFeaturesFolderPath ?? "__temp_features") : "features",
63
+ mjsFile
64
+ ),
49
65
  functionName: expression.methodName,
50
66
  name: pattern,
51
67
  source: this.findKey(method.codePart, "source"),
@@ -56,7 +72,7 @@ class StepsDefinitions {
56
72
  }
57
73
  load(print = true, supressErrors = false, errors = []) {
58
74
  const mjsFiles = findFilesWithExtension(
59
- path.join(this.baseFolder, this.isTemp ? process.env.tempFeaturesFolderPath ?? "__temp_features" : "features"),
75
+ path.join(this.baseFolder, this.isTemp ? (process.env.tempFeaturesFolderPath ?? "__temp_features") : "features"),
60
76
  "mjs"
61
77
  );
62
78
  // console.log({ mjsFiles });
@@ -64,7 +80,7 @@ class StepsDefinitions {
64
80
  const mjsFile = mjsFiles[i];
65
81
  const filePath = path.join(
66
82
  this.baseFolder,
67
- this.isTemp ? process.env.tempFeaturesFolderPath ?? "__temp_features" : "features",
83
+ this.isTemp ? (process.env.tempFeaturesFolderPath ?? "__temp_features") : "features",
68
84
  mjsFile
69
85
  );
70
86
  const codePage = new CodePage(filePath);
@@ -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,
@@ -809,12 +810,12 @@ class LocalAgent {
809
810
  } catch (err) {
810
811
  logger.error("Error reading memory file: " + err.message);
811
812
  }
812
- const screenshot = await agent.getScreenshot(agent.context);
813
+ //const screenshot = await agent.getScreenshot(agent.context);
813
814
  // check if the popup is open
814
815
  const popupResult = await agent.context.web.closeUnexpectedPopups(null, null);
815
816
  if (nodes) {
816
817
  nodes.memory = memory;
817
- nodes.screenshot = screenshot;
818
+ //nodes.screenshot = screenshot;
818
819
  if (popupResult.rerun === true) {
819
820
  nodes.rerun = true;
820
821
  }