@dev-blinq/cucumber_client 1.0.1180-dev → 1.0.1180-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 +220 -0
  2. package/bin/assets/preload/accessibility.js +1 -1
  3. package/bin/assets/preload/find_context.js +1 -1
  4. package/bin/assets/preload/generateSelector.js +24 -0
  5. package/bin/assets/preload/locators.js +18 -0
  6. package/bin/assets/preload/recorderv3.js +80 -9
  7. package/bin/assets/preload/unique_locators.js +24 -3
  8. package/bin/assets/scripts/aria_snapshot.js +235 -0
  9. package/bin/assets/scripts/dom_attr.js +372 -0
  10. package/bin/assets/scripts/dom_element.js +0 -0
  11. package/bin/assets/scripts/dom_parent.js +185 -0
  12. package/bin/assets/scripts/event_utils.js +105 -0
  13. package/bin/assets/scripts/pw.js +7886 -0
  14. package/bin/assets/scripts/recorder.js +1147 -0
  15. package/bin/assets/scripts/snapshot_capturer.js +155 -0
  16. package/bin/assets/scripts/unique_locators.js +844 -0
  17. package/bin/assets/scripts/yaml.js +4770 -0
  18. package/bin/assets/templates/page_template.txt +2 -16
  19. package/bin/assets/templates/utils_template.txt +65 -12
  20. package/bin/client/cli_helpers.js +0 -1
  21. package/bin/client/code_cleanup/utils.js +43 -14
  22. package/bin/client/code_gen/code_inversion.js +45 -13
  23. package/bin/client/code_gen/index.js +3 -0
  24. package/bin/client/code_gen/page_reflection.js +37 -20
  25. package/bin/client/code_gen/playwright_codeget.js +151 -45
  26. package/bin/client/cucumber/feature.js +96 -42
  27. package/bin/client/cucumber/project_to_document.js +8 -7
  28. package/bin/client/cucumber/steps_definitions.js +49 -16
  29. package/bin/client/local_agent.js +9 -7
  30. package/bin/client/operations/dump_tree.js +159 -5
  31. package/bin/client/playground/playground.js +1 -1
  32. package/bin/client/project.js +6 -2
  33. package/bin/client/recorderv3/bvt_recorder.js +279 -81
  34. package/bin/client/recorderv3/cli.js +1 -0
  35. package/bin/client/recorderv3/implemented_steps.js +111 -11
  36. package/bin/client/recorderv3/index.js +48 -4
  37. package/bin/client/recorderv3/network.js +299 -0
  38. package/bin/client/recorderv3/step_runner.js +183 -13
  39. package/bin/client/recorderv3/step_utils.js +159 -14
  40. package/bin/client/recorderv3/update_feature.js +53 -28
  41. package/bin/client/recording.js +8 -0
  42. package/bin/client/run_cucumber.js +116 -4
  43. package/bin/client/scenario_report.js +112 -50
  44. package/bin/client/test_scenario.js +0 -1
  45. package/bin/index.js +1 -0
  46. package/package.json +15 -8
  47. package/bin/client/code_gen/get_implemented_steps.js +0 -27
@@ -47,21 +47,30 @@ const _isCodeGenerationStep = (step) => {
47
47
  step.type === Types.SELECT ||
48
48
  step.type === Types.HOVER ||
49
49
  step.type === Types.EXTRACT_ATTRIBUTE ||
50
+ step.type === Types.EXTRACT_PROPERTY ||
50
51
  step.type === Types.SET_DATE_TIME ||
51
52
  step.type === Types.CHECK ||
52
53
  step.type === Types.PRESS ||
53
54
  step.type === Types.SET_INPUT ||
54
55
  step.type === Types.FILL_UNKNOWN ||
55
- step.type === "context_click" ||
56
- step.type === "parameterized_click" ||
56
+ step.type === Types.CONTEXT_CLICK ||
57
+ step.type === Types.PARAMETERIZED_CLICK ||
57
58
  step.type === Types.VERIFY_ATTRIBUTE ||
59
+ step.type === Types.VERIFY_PROPERTY ||
58
60
  step.type === Types.SET_INPUT_FILES ||
59
- step.type === Types.VERIFY_PAGE_SNAPSHOT
61
+ step.type === Types.VERIFY_PAGE_SNAPSHOT ||
62
+ step.type === Types.CONDITIONAL_WAIT
60
63
  ) {
61
64
  return true;
62
65
  }
63
66
  return false;
64
67
  };
68
+ // Note: this function is used to exclude a key from an object
69
+ // Please move it to utils and use it as a reusable function ...
70
+ function excludeKey(obj, keyToRemove) {
71
+ const { [keyToRemove]: _, ...rest } = obj;
72
+ return rest;
73
+ }
65
74
  const splitToLocatorsGroups = (locators) => {
66
75
  const no_text = locators.locators.filter((locator) => locator.mode === "NO_TEXT");
67
76
  const ignore_digit = locators.locators.filter((locator) => locator.mode === "IGNORE_DIGIT");
@@ -131,7 +140,8 @@ const _generateCodeFromCommand = (step, elements, userData) => {
131
140
  if (!elementIdentifier) {
132
141
  elementIdentifier = findElementIdentifier(node, step, userData, elements);
133
142
  }
134
- elements[elementIdentifier] = locatorObject;
143
+ const withoutAllStrategyLocators = excludeKey(locatorObject, "allStrategyLocators");
144
+ elements[elementIdentifier] = withoutAllStrategyLocators;
135
145
  elementsChanged = true;
136
146
  }
137
147
  let optionElement = null;
@@ -157,6 +167,7 @@ const _generateCodeFromCommand = (step, elements, userData) => {
157
167
  let variable = null;
158
168
  let format = null;
159
169
  let options = null;
170
+ let property = null;
160
171
  switch (step.type) {
161
172
  case Types.SET_INPUT:
162
173
  line = `await context.web.setInputValue(elements["${elementIdentifier}"], `;
@@ -181,19 +192,51 @@ const _generateCodeFromCommand = (step, elements, userData) => {
181
192
  line = `await context.web.setCheck(elements["${elementIdentifier}"], ${step.check}, _params, null, this);`;
182
193
  codeLines.push(line);
183
194
  } else {
184
- if (element_name) {
185
- comment = `// Click on ${element_name}`;
186
- codeLines.push(escapeNonPrintables(comment));
195
+ switch (step.count) {
196
+ case 1:
197
+ if (element_name) {
198
+ comment = `// Click on ${element_name}`;
199
+ codeLines.push(escapeNonPrintables(comment));
200
+ }
201
+ line = `await context.web.click(elements["${elementIdentifier}"], _params, null, this);`;
202
+ codeLines.push(line);
203
+ break;
204
+ case 2:
205
+ if (element_name) {
206
+ comment = `// Double click on ${element_name}`;
207
+ codeLines.push(escapeNonPrintables(comment));
208
+ }
209
+ line = `await context.web.click(elements["${elementIdentifier}"], _params, {clickCount:2}, this);`;
210
+ codeLines.push(line);
211
+ break;
212
+ default:
213
+ if (element_name) {
214
+ comment = `// Click on ${element_name}`;
215
+ codeLines.push(escapeNonPrintables(comment));
216
+ }
217
+ line = `await context.web.click(elements["${elementIdentifier}"], _params, null, this);`;
218
+ codeLines.push(line);
219
+ break;
187
220
  }
188
-
189
- line = `await context.web.click(elements["${elementIdentifier}"], _params, null, this);`;
190
- codeLines.push(line);
191
221
  }
192
222
  break;
193
- case "parameterized_click":
194
- comment = `// Parameterized click on ${step.value}`;
195
- codeLines.push(escapeNonPrintables(comment));
196
- line = `await context.web.click(elements["${elementIdentifier}"], _params, null, this);`;
223
+ case Types.PARAMETERIZED_CLICK:
224
+ switch (step.count) {
225
+ case 1:
226
+ comment = `// Parameterized click on ${step.value}`;
227
+ codeLines.push(escapeNonPrintables(comment));
228
+ line = `await context.web.click(elements["${elementIdentifier}"], _params, null, this);`;
229
+ break;
230
+ case 2:
231
+ comment = `// Parameterized double click on ${step.value}`;
232
+ codeLines.push(escapeNonPrintables(comment));
233
+ line = `await context.web.click(elements["${elementIdentifier}"], _params, {clickCount:2}, this);`;
234
+ break;
235
+ default:
236
+ comment = `// Parameterized click on ${step.value}`;
237
+ codeLines.push(escapeNonPrintables(comment));
238
+ line = `await context.web.click(elements["${elementIdentifier}"], _params, null, this);`;
239
+ }
197
240
  codeLines.push(line);
198
241
  break;
199
242
  case Types.SET_DATE_TIME:
@@ -240,30 +283,32 @@ const _generateCodeFromCommand = (step, elements, userData) => {
240
283
  }
241
284
  codeLines.push(line);
242
285
  break;
243
- case Types.FILL_UNKNOWN:
244
- line = `await context.web.clickType(elements["${elementIdentifier}"], `;
245
- // eslint-disable-next-line
246
- input = `"${escapeNonPrintables(step.parameters[0])}"`;
286
+ case Types.EXTRACT_ATTRIBUTE: {
287
+ attribute = escapeNonPrintables(step.parameters[0]);
288
+ variable = escapeNonPrintables(step.parameters[1]);
289
+ input = `"${variable}"`;
247
290
  if (step.dataSource === "userData" || step.dataSource === "parameters") {
248
- //parameters.push(step.dataKey);
249
291
  input = "_" + step.dataKey;
250
292
  }
251
- if (step.parameters[1] === true) {
252
- enter = "true";
253
- } else if (step.parameters[1] === false || step.parameters[1] === undefined) {
254
- enter = "false";
293
+ let options = "null";
294
+ if (step.regex !== "") {
295
+ options = `{regex: ${JSON.stringify(step.regex)},trimSpaces: ${step.trimSpaces}}`;
296
+ } else if (step.trimSpaces) {
297
+ options = `{trimSpaces: ${step.trimSpaces}}`;
255
298
  } else {
256
- enter = '"' + step.parameters[1] + '"';
299
+ options = "null";
257
300
  }
258
- line += `${input}, ${enter}, _params, null, this);`;
301
+ line = `await context.web.extractAttribute(elements["${elementIdentifier}"], "${attribute}", ${input}, _params,${options}, this);`;
302
+
259
303
  if (element_name) {
260
- comment = `// Fill ${element_name} with "${input}"`;
304
+ comment = `// Extract attribute ${attribute} from ${element_name} to ${variable}`;
261
305
  codeLines.push(escapeNonPrintables(comment));
262
306
  }
263
307
  codeLines.push(line);
264
308
  break;
265
- case Types.EXTRACT_ATTRIBUTE: {
266
- attribute = escapeNonPrintables(step.parameters[0]);
309
+ }
310
+ case Types.EXTRACT_PROPERTY: {
311
+ property = escapeNonPrintables(step.parameters[0]);
267
312
  variable = escapeNonPrintables(step.parameters[1]);
268
313
  input = `"${variable}"`;
269
314
  if (step.dataSource === "userData" || step.dataSource === "parameters") {
@@ -277,10 +322,10 @@ const _generateCodeFromCommand = (step, elements, userData) => {
277
322
  } else {
278
323
  options = "null";
279
324
  }
280
- line = `await context.web.extractAttribute(elements["${elementIdentifier}"], "${attribute}", ${input}, _params,${options}, this);`;
325
+ line = `await context.web.extractProperty(elements["${elementIdentifier}"], "${property}", ${input}, _params,${options}, this);`;
281
326
 
282
327
  if (element_name) {
283
- comment = `// Extract attribute ${attribute} from ${element_name} to ${variable}`;
328
+ comment = `// Extract property ${property} from ${element_name} to ${variable}`;
284
329
  codeLines.push(escapeNonPrintables(comment));
285
330
  }
286
331
  codeLines.push(line);
@@ -288,10 +333,10 @@ const _generateCodeFromCommand = (step, elements, userData) => {
288
333
  }
289
334
  case Types.VERIFY_PAGE_SNAPSHOT:
290
335
  comment = step.nestFrmLoc
291
- ? `// Verify page snapshot ${step.parameters[0]} in the frame ${step.nestFrmLoc}`
336
+ ? `// Verify page snapshot ${step.parameters[0]} in the frame ${step.selectors.iframe_src}`
292
337
  : `// Verify page snapshot ${step.parameters[0]}`;
293
338
  codeLines.push(escapeNonPrintables(comment));
294
- line = `const frameLocator = ${JSON.stringify(step.nestFrmLoc ?? null)}`;
339
+ line = `const frameLocator = ${JSON.stringify(step.selectors ?? null)}`;
295
340
  codeLines.push(line);
296
341
  line = `await context.web.snapshotValidation(frameLocator, _param_0 , _params, ${JSON.stringify(options)}, this);`;
297
342
  codeLines.push(line);
@@ -396,7 +441,6 @@ const _generateCodeFromCommand = (step, elements, userData) => {
396
441
  throw new Error(`Unknown select mode ${step.selectMode}`);
397
442
  }
398
443
  break;
399
-
400
444
  case Types.COMPLETE:
401
445
  break;
402
446
  case Types.RELOAD:
@@ -507,15 +551,39 @@ const _generateCodeFromCommand = (step, elements, userData) => {
507
551
  line = `await context.web.clickType( elements["${elementIdentifier}"] ,"${step.key}",null, _params, {"press":true}, this);`;
508
552
  codeLines.push(line);
509
553
  break;
510
- case "context_click":
511
- comment = `// Click on ${elementIdentifier ? elementIdentifier : step.label} in the context of ${escapeNonPrintables(step.value)}`;
512
- codeLines.push(escapeNonPrintables(comment));
513
- input = `"${escapeNonPrintables(step.value)}"`;
514
- if (step.dataSource === "userData" || step.dataSource === "parameters") {
515
- input = "_" + step.dataKey;
554
+ case Types.CONTEXT_CLICK:
555
+ switch (step.count) {
556
+ case 1:
557
+ comment = `// Click on ${elementIdentifier ? elementIdentifier : step.label} in the context of ${escapeNonPrintables(step.value)}`;
558
+ codeLines.push(escapeNonPrintables(comment));
559
+ input = `"${escapeNonPrintables(step.value.replaceAll('"', '\\"'))}"`;
560
+ if (step.dataSource === "userData" || step.dataSource === "parameters") {
561
+ input = "_" + step.dataKey;
562
+ }
563
+ line = `await context.web.click(elements["${elementIdentifier}"], _params, {"context": ${input}}, this);`;
564
+ codeLines.push(line);
565
+ break;
566
+ case 2:
567
+ comment = `// Double click on ${elementIdentifier ? elementIdentifier : step.label} in the context of ${escapeNonPrintables(step.value)}`;
568
+ codeLines.push(escapeNonPrintables(comment));
569
+ input = `"${escapeNonPrintables(step.value.replaceAll('"', '\\"'))}"`;
570
+ if (step.dataSource === "userData" || step.dataSource === "parameters") {
571
+ input = "_" + step.dataKey;
572
+ }
573
+ line = `await context.web.click(elements["${elementIdentifier}"], _params, {"context": ${input}, clickCount:2}, this);`;
574
+ codeLines.push(line);
575
+ break;
576
+ default:
577
+ comment = `// Click on ${elementIdentifier ? elementIdentifier : step.label} in the context of ${escapeNonPrintables(step.value)}`;
578
+ codeLines.push(escapeNonPrintables(comment));
579
+ input = `"${escapeNonPrintables(step.value.replaceAll('"', '\\"'))}"`;
580
+ if (step.dataSource === "userData" || step.dataSource === "parameters") {
581
+ input = "_" + step.dataKey;
582
+ }
583
+ line = `await context.web.click(elements["${elementIdentifier}"], _params, {"context": ${input}}, this);`;
584
+ codeLines.push(line);
585
+ break;
516
586
  }
517
- line = `await context.web.click(elements["${elementIdentifier}"], _params, {"context": ${input}}, this);`;
518
- codeLines.push(line);
519
587
  break;
520
588
  case "popup_close":
521
589
  break;
@@ -541,6 +609,24 @@ const _generateCodeFromCommand = (step, elements, userData) => {
541
609
  codeLines.push(line);
542
610
  break;
543
611
  }
612
+ case Types.VERIFY_PROPERTY: {
613
+ // Only execute if codeLine is empty/undefined
614
+ line = `await context.web.verifyProperty(elements["${elementIdentifier}"], `;
615
+ input = "_param_0";
616
+ if (step.dataSource === "userData" || step.dataSource === "parameters") {
617
+ input = "_" + step.dataKey;
618
+ }
619
+ line += `"${step.parameters[0]}", ${input}, _params, null, this);`;
620
+ codeLines.push(line);
621
+ break;
622
+ }
623
+ case Types.CONDITIONAL_WAIT: {
624
+ line = `await context.web.conditionalWait(elements["${elementIdentifier}"], `;
625
+ input = "_param_0";
626
+ line += `"${step.parameters[1]}", ${input}, _params, null, this);`;
627
+ codeLines.push(line);
628
+ break;
629
+ }
544
630
  case Types.SET_INPUT_FILES: {
545
631
  line = `await context.web.setInputFiles(elements["${elementIdentifier}"], `;
546
632
  let files = step.parameters[0];
@@ -566,6 +652,22 @@ const _generateCodeFromCommand = (step, elements, userData) => {
566
652
  return { codeLines, elements: elementsChanged ? elements : null, elementIdentifier, allStrategyLocators };
567
653
  };
568
654
 
655
+ /**
656
+ * Generates a report command based on the given position.
657
+ * @param {"start"|"end"} position
658
+ */
659
+ const generateReportCommand = (position, cmdId) => {
660
+ const codeLines = [];
661
+ if (position === "start") {
662
+ const line = `await context.web.addCommandToReport("${cmdId}", "PASSED", '{"status":"start","cmdId":"${cmdId}"}', {type:"cmdReport"},this);`;
663
+ codeLines.push(line);
664
+ } else if (position === "end") {
665
+ const line = `await context.web.addCommandToReport("${cmdId}", "PASSED", '{"status":"end","cmdId":"${cmdId}"}', {type:"cmdReport"},this);`;
666
+ codeLines.push(line);
667
+ }
668
+ return codeLines;
669
+ };
670
+
569
671
  const generateCode = (recording, codePage, userData, projectDir, methodName) => {
570
672
  const stepsDefinitions = new StepsDefinitions(projectDir);
571
673
  stepsDefinitions.load(false);
@@ -590,6 +692,7 @@ const generateCode = (recording, codePage, userData, projectDir, methodName) =>
590
692
  }
591
693
  //console.log("step found");
592
694
  }
695
+
593
696
  let elements = {};
594
697
  if (!codePage) {
595
698
  console.log("codePage is null");
@@ -606,12 +709,15 @@ const generateCode = (recording, codePage, userData, projectDir, methodName) =>
606
709
  if (recordingStep.type === Types.COMPLETE) {
607
710
  return;
608
711
  }
609
- // if (!recordingStep.element) {
610
- // logger.info(`Step ${recordingStep.type} doesn't have an element, ignoring`);
611
- // return;
712
+
713
+ // if (process.env.TEMP_RUN === "true") {
714
+ // codeLines.push(...generateReportCommand("start", recordingStep.cmdId));
612
715
  // }
613
716
  const result = _generateCodeFromCommand(recordingStep, elements, userData);
614
717
  codeLines.push(...result.codeLines);
718
+ // if (process.env.TEMP_RUN === "true") {
719
+ // codeLines.push(...generateReportCommand("end", recordingStep.cmdId));
720
+ // }
615
721
  if (result.elements) {
616
722
  elements = result.elements;
617
723
  }
@@ -656,4 +762,4 @@ const generatePageName = (url) => {
656
762
  return "default";
657
763
  }
658
764
  };
659
- export { generateCode, generatePageName };
765
+ export { generateCode, generatePageName };
@@ -1,11 +1,11 @@
1
1
  import { loadConfiguration, loadSupport, runCucumber } from "@dev-blinq/cucumber-js/api";
2
2
  import fs from "fs";
3
-
3
+ import os from "os";
4
+ import path from "path";
4
5
  import { parseStepTextParameters, toCucumberExpression, unEscapeNonPrintables } from "./utils.js";
5
- import { AstBuilder, GherkinClassicTokenMatcher, Parser, compile } from "@cucumber/gherkin";
6
- import { IdGenerator } from "@cucumber/messages";
6
+ import stream from "stream";
7
7
  import { testStringForRegex } from "../recorderv3/update_feature.js";
8
-
8
+ import { generateTestData } from "./feature_data.js";
9
9
  class DataTable {
10
10
  constructor(dataTableDocument) {
11
11
  if (!dataTableDocument) {
@@ -289,7 +289,7 @@ class Feature {
289
289
  }
290
290
  return scenarios;
291
291
  }
292
- generateStracture(stepsDefinitions, fileName, scenarioName = null) {
292
+ generateStracture(stepsDefinitions, fileName, scenarioName = null, errorsInParsing = [], parseFailedFiles = false) {
293
293
  const document = {};
294
294
  document.featureName = this.feature.name;
295
295
  document.fileName = fileName;
@@ -317,6 +317,28 @@ class Feature {
317
317
  stepInfo.functionName = stepDef.functionName;
318
318
  stepInfo.implemented_at = stepDef.implemented_at;
319
319
  }
320
+ if (!stepDef && parseFailedFiles) {
321
+ //Check if the stepName exists in one of the failedToParseFiles
322
+ try {
323
+ const stepName = stepsDefinitions._stepNameToTemplate(stepInfo.template);
324
+ if (errorsInParsing && errorsInParsing.length > 0) {
325
+ for (let k = 0; k < errorsInParsing.length; k++) {
326
+ const failedFile = errorsInParsing[k].file;
327
+ //Read File content, and check if the stepName exists in the file
328
+ const fileContent = fs.readFileSync(failedFile, "utf8");
329
+ if (fileContent.includes(stepName)) {
330
+ stepInfo.implemented = true;
331
+ stepInfo.mjsFile = failedFile.slice(path.normalize(failedFile).indexOf("features/step_definitions"));
332
+ stepInfo.functionName = stepName;
333
+ stepInfo.error = errorsInParsing[k].error;
334
+ break;
335
+ }
336
+ }
337
+ }
338
+ } catch (e) {
339
+ console.error(`Error while checking step ${stepInfo.template} in failed files:`, e);
340
+ }
341
+ }
320
342
  }
321
343
  document.scenarios.push(scenarioInfo);
322
344
  }
@@ -431,43 +453,75 @@ const scenarioResolution = async (featureFilePath) => {
431
453
  if (!fs.existsSync(featureFilePath)) {
432
454
  throw new Error(`Feature file ${featureFilePath} does not exist`);
433
455
  }
434
- const newId = IdGenerator.uuid();
435
- const builder = new AstBuilder(newId);
436
- const matcher = new GherkinClassicTokenMatcher();
437
-
438
- const parser = new Parser(builder, matcher);
439
-
440
- // normalize the path to start with featuers/ if it's not cut the featureFielPath to only the part after features/
441
- let uri = featureFilePath.startsWith("features/")
442
- ? featureFilePath
443
- : "features/" + featureFilePath.split("features/")[1];
444
-
445
456
  const featureFileContent = fs.readFileSync(featureFilePath, "utf8");
446
- const gherkinDocument = parser.parse(featureFileContent, newId);
447
-
448
- const feature = new Feature(gherkinDocument, featureFileContent);
449
- //const scenario = feature.getScenario(scenarioName);
450
- //return scenario;
451
- return feature;
452
- };
453
- const getDocumentAndPickel = (featureName, featureContent, scenarioName) => {
454
- const newId = IdGenerator.uuid();
455
- const builder = new AstBuilder(newId);
456
- const matcher = new GherkinClassicTokenMatcher();
457
-
458
- const parser = new Parser(builder, matcher);
459
-
460
- // normalize the path to start with featuers/ if it's not cut the featureFielPath to only the part after features/
461
- let uri = `features/${featureName}.feature`;
462
-
463
- const gherkinDocument = parser.parse(featureContent, newId);
464
- const pickles = compile(gherkinDocument, uri, newId);
457
+ let tmpDir = null;
458
+ try {
459
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "ai-qa"));
460
+ const tmpFeaturePath = path.join(tmpDir, "features");
461
+ fs.mkdirSync(tmpFeaturePath);
462
+ const tmpFeatureFilePath = path.join(tmpFeaturePath, path.basename(featureFilePath));
463
+ let result = generateTestData(featureFilePath);
464
+ if (result.changed) {
465
+ fs.writeFileSync(tmpFeatureFilePath, result.newContent);
466
+ console.log("Fake data was generated for this scenario");
467
+ console.log("Variables:");
468
+ for (let key in result.variables) {
469
+ console.log(`${key}: ${result.variables[key].fake}`);
470
+ }
471
+ console.log("Other fake data:");
472
+ for (let i = 0; i < result.otherFakeData.length; i++) {
473
+ console.log(`${result.otherFakeData[i].var}: ${result.otherFakeData[i].fake}`);
474
+ }
475
+ } else {
476
+ fs.copyFileSync(featureFilePath, tmpFeatureFilePath);
477
+ }
478
+ const writable = new stream.Writable({
479
+ write: function (chunk, encoding, next) {
480
+ //console.log(chunk.toString());
481
+ next();
482
+ },
483
+ });
484
+ const environment = { cwd: tmpDir, stdout: writable, stderr: writable };
485
+ // load configuration from a particular file, and override a specific option
486
+ const provided = {
487
+ default: "--publish-quiet",
488
+ require: [tmpDir],
489
+ failFast: false,
490
+ };
465
491
 
466
- // Step 3: Find the specific scenario Pickle
467
- const pickle = pickles.find((pickle) => {
468
- return pickle.name === scenarioName;
469
- });
470
- return { gherkinDocument, pickle };
492
+ let gherkinDocument = null;
493
+ const { runConfiguration } = await loadConfiguration({ provided }, environment);
494
+ // load the support code upfront
495
+ const support = await loadSupport(runConfiguration, environment);
496
+ // run cucumber, using the support code we loaded already
497
+ await runCucumber({ ...runConfiguration, support }, environment, function (event) {
498
+ // if (event.source) {
499
+ // scenarioInfo.source = event.source.data;
500
+ // }
501
+ // if (event.pickle && event.pickle.name === scenarioName) {
502
+ // scenarioInfo.pickle = event.pickle;
503
+ // }
504
+ if (event.gherkinDocument) {
505
+ gherkinDocument = event.gherkinDocument;
506
+ }
507
+ //console.log(event);
508
+ //console.log(JSON.stringify(event, null, 2));
509
+ // console.log("");
510
+ });
511
+ const feature = new Feature(gherkinDocument, featureFileContent);
512
+ //const scenario = feature.getScenario(scenarioName);
513
+ //return scenario;
514
+ return feature;
515
+ } finally {
516
+ try {
517
+ if (tmpDir) {
518
+ fs.rmSync(tmpDir, { recursive: true });
519
+ }
520
+ } catch (e) {
521
+ console.error(
522
+ `An error has occurred while removing the temp folder at ${tmpDir}. Please remove it manually. Error: ${e}`
523
+ );
524
+ }
525
+ }
471
526
  };
472
-
473
- export { Feature, Scenario, Step, DataTable, Examples, scenarioResolution, getDocumentAndPickel };
527
+ export { Feature, Scenario, Step, DataTable, Examples, scenarioResolution };
@@ -4,14 +4,14 @@ 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) => {
7
+ export const projectDocument = (folder, featureName = null, scenarioName = null, supressErrors = false, errors = []) => {
8
8
  const uuidFn = () => (23212).toString(16);
9
9
  const builder = new AstBuilder(uuidFn);
10
10
  const matcher = new GherkinClassicTokenMatcher();
11
11
  const parser = new Parser(builder, matcher);
12
- const stepDefinition = new StepsDefinitions(folder);
13
- stepDefinition.load(false);
14
-
12
+ // load the step definitions from the project folder under steps folder
13
+ const stepDefinition = new StepsDefinitions(folder, false);
14
+ stepDefinition.load(false, supressErrors, errors);
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");
@@ -27,7 +27,7 @@ export const projectDocument = (folder, featureName = null, scenarioName = null)
27
27
  if (featureName && feature.feature.name !== featureName && featureFile !== featureName) {
28
28
  continue;
29
29
  }
30
- const document = feature.generateStracture(stepDefinition, featureFile, scenarioName);
30
+ const document = feature.generateStracture(stepDefinition, featureFile, scenarioName, errors, supressErrors);
31
31
  if (scenarioName && document.scenarios.length === 0) {
32
32
  continue;
33
33
  }
@@ -55,7 +55,8 @@ export const projectDocument = (folder, featureName = null, scenarioName = null)
55
55
  // scenarioName = arg.substring(9);
56
56
  // }
57
57
  // }
58
- // const documents = projectDocument(projectDir, featureName, scenarioName);
58
+ // let errors = [];
59
+ // const documents = projectDocument(projectDir, featureName, scenarioName, true, errors);
59
60
  // const args = process.argv.slice(2);
60
61
 
61
- // console.log(JSON.stringify(projectDocument(args[0])));
62
+ // console.log(JSON.stringify(documents));
@@ -54,7 +54,7 @@ class StepsDefinitions {
54
54
  }
55
55
  }
56
56
  }
57
- load(print = true) {
57
+ load(print = true, supressErrors = false, errors = []) {
58
58
  const mjsFiles = findFilesWithExtension(
59
59
  path.join(this.baseFolder, this.isTemp ? process.env.tempFeaturesFolderPath ?? "__temp_features" : "features"),
60
60
  "mjs"
@@ -62,18 +62,24 @@ class StepsDefinitions {
62
62
  // console.log({ mjsFiles });
63
63
  for (let i = 0; i < mjsFiles.length; i++) {
64
64
  const mjsFile = mjsFiles[i];
65
- const codePage = new CodePage(
66
- path.join(
67
- this.baseFolder,
68
- this.isTemp ? process.env.tempFeaturesFolderPath ?? "__temp_features" : "features",
69
- mjsFile
70
- )
65
+ const filePath = path.join(
66
+ this.baseFolder,
67
+ this.isTemp ? process.env.tempFeaturesFolderPath ?? "__temp_features" : "features",
68
+ mjsFile
71
69
  );
70
+ const codePage = new CodePage(filePath);
72
71
  try {
73
72
  codePage.generateModel();
74
73
  } catch (error) {
75
- logger.info("unable to generate model for file", mjsFile);
76
- throw error;
74
+ logger.info("unable to generate model for", mjsFile, error);
75
+ if (supressErrors) {
76
+ errors.push({
77
+ file: filePath,
78
+ error: error.message,
79
+ });
80
+ } else {
81
+ continue;
82
+ }
77
83
  }
78
84
  this.initPage(codePage, mjsFile);
79
85
  }
@@ -518,12 +524,8 @@ class StepsDefinitions {
518
524
  // );
519
525
  };
520
526
 
521
- executeStepRemote = async ({ feature_file_path, scenario, tempFolderPath, stepText }, options) => {
522
- if (!options) {
523
- options = {
524
- skipAfter: true,
525
- };
526
- }
527
+ executeStepRemote = async ({ feature_file_path, scenario, tempFolderPath, stepText, onCommandResult }, options) => {
528
+ const { skipAfter = true, skipBefore = true } = options || {};
527
529
  const environment = {
528
530
  ...process.env,
529
531
  };
@@ -549,11 +551,18 @@ class StepsDefinitions {
549
551
  // console.log("step", step.pattern);
550
552
  // });
551
553
 
552
- if (options.skipAfter) {
554
+ if (skipAfter) {
553
555
  // ignore afterAll/after hooks
554
556
  support.afterTestCaseHookDefinitions = [];
555
557
  support.afterTestRunHookDefinitions = [];
556
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
567
  let errorMesssage = null;
559
568
  let info = null;
@@ -592,6 +601,11 @@ class StepsDefinitions {
592
601
  Object.assign(bvtError, { info: errInfo });
593
602
  throw bvtError;
594
603
  }
604
+
605
+ return {
606
+ result,
607
+ info,
608
+ };
595
609
  } catch (error) {
596
610
  console.error("Error running cucumber-js", error);
597
611
  throw error;
@@ -626,4 +640,23 @@ function findFilesWithExtension(folderPath, extension, rootFolder = null) {
626
640
 
627
641
  return result;
628
642
  }
643
+ export function locateDefinitionPath(featureFolder, pageName) {
644
+ if (!pageName) {
645
+ return null;
646
+ }
647
+ if (!pageName.endsWith("_page.mjs")) {
648
+ pageName = pageName + "_page.mjs";
649
+ }
650
+ // search for files with the pageName
651
+ const stepDefinitionFolderPath = path.join(featureFolder, "step_definitions");
652
+ const files = findFilesWithExtension(stepDefinitionFolderPath, "mjs");
653
+ for (let i = 0; i < files.length; i++) {
654
+ const file = files[i];
655
+ if (file.includes(pageName)) {
656
+ return path.join(stepDefinitionFolderPath, file);
657
+ }
658
+ }
659
+ return path.join(stepDefinitionFolderPath, pageName);
660
+ }
661
+
629
662
  export { StepsDefinitions, findFilesWithExtension };