@dev-blinq/cucumber_client 1.0.1173-dev → 1.0.1173-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 +85 -11
  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 +112 -18
  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 +152 -48
  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 +59 -16
  29. package/bin/client/local_agent.js +9 -7
  30. package/bin/client/operations/dump_tree.js +159 -8
  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 +236 -79
  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 +45 -4
  37. package/bin/client/recorderv3/network.js +299 -0
  38. package/bin/client/recorderv3/step_runner.js +179 -13
  39. package/bin/client/recorderv3/step_utils.js +159 -14
  40. package/bin/client/recorderv3/update_feature.js +55 -30
  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
@@ -1,11 +1,9 @@
1
1
  import { Types } from "../recording.js";
2
-
3
2
  import logger from "../../logger.js";
4
3
  import { StepsDefinitions } from "../cucumber/steps_definitions.js";
5
4
  import path from "path";
6
5
  import { CodePage } from "./page_reflection.js";
7
6
  import { convertToIdentifier, escapeNonPrintables } from "./utils.js";
8
- import { all } from "axios";
9
7
  const findElementIdentifier = (node, step, userData, elements) => {
10
8
  if (node.key) {
11
9
  // incase of rerunning implemented steps
@@ -49,21 +47,30 @@ const _isCodeGenerationStep = (step) => {
49
47
  step.type === Types.SELECT ||
50
48
  step.type === Types.HOVER ||
51
49
  step.type === Types.EXTRACT_ATTRIBUTE ||
50
+ step.type === Types.EXTRACT_PROPERTY ||
52
51
  step.type === Types.SET_DATE_TIME ||
53
52
  step.type === Types.CHECK ||
54
53
  step.type === Types.PRESS ||
55
54
  step.type === Types.SET_INPUT ||
56
55
  step.type === Types.FILL_UNKNOWN ||
57
- step.type === "context_click" ||
58
- step.type === "parameterized_click" ||
56
+ step.type === Types.CONTEXT_CLICK ||
57
+ step.type === Types.PARAMETERIZED_CLICK ||
59
58
  step.type === Types.VERIFY_ATTRIBUTE ||
59
+ step.type === Types.VERIFY_PROPERTY ||
60
60
  step.type === Types.SET_INPUT_FILES ||
61
- step.type === Types.VERIFY_PAGE_SNAPSHOT
61
+ step.type === Types.VERIFY_PAGE_SNAPSHOT ||
62
+ step.type === Types.CONDITIONAL_WAIT
62
63
  ) {
63
64
  return true;
64
65
  }
65
66
  return false;
66
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
+ }
67
74
  const splitToLocatorsGroups = (locators) => {
68
75
  const no_text = locators.locators.filter((locator) => locator.mode === "NO_TEXT");
69
76
  const ignore_digit = locators.locators.filter((locator) => locator.mode === "IGNORE_DIGIT");
@@ -133,7 +140,8 @@ const _generateCodeFromCommand = (step, elements, userData) => {
133
140
  if (!elementIdentifier) {
134
141
  elementIdentifier = findElementIdentifier(node, step, userData, elements);
135
142
  }
136
- elements[elementIdentifier] = locatorObject;
143
+ const withoutAllStrategyLocators = excludeKey(locatorObject, "allStrategyLocators");
144
+ elements[elementIdentifier] = withoutAllStrategyLocators;
137
145
  elementsChanged = true;
138
146
  }
139
147
  let optionElement = null;
@@ -159,6 +167,7 @@ const _generateCodeFromCommand = (step, elements, userData) => {
159
167
  let variable = null;
160
168
  let format = null;
161
169
  let options = null;
170
+ let property = null;
162
171
  switch (step.type) {
163
172
  case Types.SET_INPUT:
164
173
  line = `await context.web.setInputValue(elements["${elementIdentifier}"], `;
@@ -183,19 +192,51 @@ const _generateCodeFromCommand = (step, elements, userData) => {
183
192
  line = `await context.web.setCheck(elements["${elementIdentifier}"], ${step.check}, _params, null, this);`;
184
193
  codeLines.push(line);
185
194
  } else {
186
- if (element_name) {
187
- comment = `// Click on ${element_name}`;
188
- 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;
189
220
  }
190
-
191
- line = `await context.web.click(elements["${elementIdentifier}"], _params, null, this);`;
192
- codeLines.push(line);
193
221
  }
194
222
  break;
195
- case "parameterized_click":
196
- comment = `// Parameterized click on ${step.value}`;
197
- codeLines.push(escapeNonPrintables(comment));
198
- 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
+ }
199
240
  codeLines.push(line);
200
241
  break;
201
242
  case Types.SET_DATE_TIME:
@@ -242,30 +283,32 @@ const _generateCodeFromCommand = (step, elements, userData) => {
242
283
  }
243
284
  codeLines.push(line);
244
285
  break;
245
- case Types.FILL_UNKNOWN:
246
- line = `await context.web.clickType(elements["${elementIdentifier}"], `;
247
- // eslint-disable-next-line
248
- 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}"`;
249
290
  if (step.dataSource === "userData" || step.dataSource === "parameters") {
250
- //parameters.push(step.dataKey);
251
291
  input = "_" + step.dataKey;
252
292
  }
253
- if (step.parameters[1] === true) {
254
- enter = "true";
255
- } else if (step.parameters[1] === false || step.parameters[1] === undefined) {
256
- 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}}`;
257
298
  } else {
258
- enter = '"' + step.parameters[1] + '"';
299
+ options = "null";
259
300
  }
260
- line += `${input}, ${enter}, _params, null, this);`;
301
+ line = `await context.web.extractAttribute(elements["${elementIdentifier}"], "${attribute}", ${input}, _params,${options}, this);`;
302
+
261
303
  if (element_name) {
262
- comment = `// Fill ${element_name} with "${input}"`;
304
+ comment = `// Extract attribute ${attribute} from ${element_name} to ${variable}`;
263
305
  codeLines.push(escapeNonPrintables(comment));
264
306
  }
265
307
  codeLines.push(line);
266
308
  break;
267
- case Types.EXTRACT_ATTRIBUTE: {
268
- attribute = escapeNonPrintables(step.parameters[0]);
309
+ }
310
+ case Types.EXTRACT_PROPERTY: {
311
+ property = escapeNonPrintables(step.parameters[0]);
269
312
  variable = escapeNonPrintables(step.parameters[1]);
270
313
  input = `"${variable}"`;
271
314
  if (step.dataSource === "userData" || step.dataSource === "parameters") {
@@ -279,10 +322,10 @@ const _generateCodeFromCommand = (step, elements, userData) => {
279
322
  } else {
280
323
  options = "null";
281
324
  }
282
- 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);`;
283
326
 
284
327
  if (element_name) {
285
- comment = `// Extract attribute ${attribute} from ${element_name} to ${variable}`;
328
+ comment = `// Extract property ${property} from ${element_name} to ${variable}`;
286
329
  codeLines.push(escapeNonPrintables(comment));
287
330
  }
288
331
  codeLines.push(line);
@@ -290,12 +333,12 @@ const _generateCodeFromCommand = (step, elements, userData) => {
290
333
  }
291
334
  case Types.VERIFY_PAGE_SNAPSHOT:
292
335
  comment = step.nestFrmLoc
293
- ? `// Verify page snapshot ${step.parameters[0]} in the frame`
336
+ ? `// Verify page snapshot ${step.parameters[0]} in the frame ${step.selectors.iframe_src}`
294
337
  : `// Verify page snapshot ${step.parameters[0]}`;
295
338
  codeLines.push(escapeNonPrintables(comment));
296
- line = `const frameLocator = ${JSON.stringify(step.nestFrmLoc ?? null)}`;
339
+ line = `const frameLocator = ${JSON.stringify(step.selectors ?? null)}`;
297
340
  codeLines.push(line);
298
- line = `await context.web.snapshotValidation(frameLocator,${JSON.stringify(step.parameters[0])}, _params, ${JSON.stringify(options)}, this);`;
341
+ line = `await context.web.snapshotValidation(frameLocator, _param_0 , _params, ${JSON.stringify(options)}, this);`;
299
342
  codeLines.push(line);
300
343
  break;
301
344
  case Types.VERIFY_PAGE_CONTAINS_TEXT:
@@ -398,7 +441,6 @@ const _generateCodeFromCommand = (step, elements, userData) => {
398
441
  throw new Error(`Unknown select mode ${step.selectMode}`);
399
442
  }
400
443
  break;
401
-
402
444
  case Types.COMPLETE:
403
445
  break;
404
446
  case Types.RELOAD:
@@ -509,15 +551,39 @@ const _generateCodeFromCommand = (step, elements, userData) => {
509
551
  line = `await context.web.clickType( elements["${elementIdentifier}"] ,"${step.key}",null, _params, {"press":true}, this);`;
510
552
  codeLines.push(line);
511
553
  break;
512
- case "context_click":
513
- comment = `// Click on ${elementIdentifier ? elementIdentifier : step.label} in the context of ${escapeNonPrintables(step.value)}`;
514
- codeLines.push(escapeNonPrintables(comment));
515
- input = `"${escapeNonPrintables(step.value)}"`;
516
- if (step.dataSource === "userData" || step.dataSource === "parameters") {
517
- 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;
518
586
  }
519
- line = `await context.web.click(elements["${elementIdentifier}"], _params, {"context": ${input}}, this);`;
520
- codeLines.push(line);
521
587
  break;
522
588
  case "popup_close":
523
589
  break;
@@ -543,6 +609,24 @@ const _generateCodeFromCommand = (step, elements, userData) => {
543
609
  codeLines.push(line);
544
610
  break;
545
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
+ }
546
630
  case Types.SET_INPUT_FILES: {
547
631
  line = `await context.web.setInputFiles(elements["${elementIdentifier}"], `;
548
632
  let files = step.parameters[0];
@@ -568,6 +652,22 @@ const _generateCodeFromCommand = (step, elements, userData) => {
568
652
  return { codeLines, elements: elementsChanged ? elements : null, elementIdentifier, allStrategyLocators };
569
653
  };
570
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
+
571
671
  const generateCode = (recording, codePage, userData, projectDir, methodName) => {
572
672
  const stepsDefinitions = new StepsDefinitions(projectDir);
573
673
  stepsDefinitions.load(false);
@@ -592,6 +692,7 @@ const generateCode = (recording, codePage, userData, projectDir, methodName) =>
592
692
  }
593
693
  //console.log("step found");
594
694
  }
695
+
595
696
  let elements = {};
596
697
  if (!codePage) {
597
698
  console.log("codePage is null");
@@ -608,12 +709,15 @@ const generateCode = (recording, codePage, userData, projectDir, methodName) =>
608
709
  if (recordingStep.type === Types.COMPLETE) {
609
710
  return;
610
711
  }
611
- // if (!recordingStep.element) {
612
- // logger.info(`Step ${recordingStep.type} doesn't have an element, ignoring`);
613
- // return;
712
+
713
+ // if (process.env.TEMP_RUN === "true") {
714
+ // codeLines.push(...generateReportCommand("start", recordingStep.cmdId));
614
715
  // }
615
716
  const result = _generateCodeFromCommand(recordingStep, elements, userData);
616
717
  codeLines.push(...result.codeLines);
718
+ // if (process.env.TEMP_RUN === "true") {
719
+ // codeLines.push(...generateReportCommand("end", recordingStep.cmdId));
720
+ // }
617
721
  if (result.elements) {
618
722
  elements = result.elements;
619
723
  }
@@ -658,4 +762,4 @@ const generatePageName = (url) => {
658
762
  return "default";
659
763
  }
660
764
  };
661
- 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
  }
@@ -224,6 +230,16 @@ class StepsDefinitions {
224
230
  }
225
231
  }
226
232
  }
233
+ } else if (codeObj.type === "VariableDeclaration") {
234
+ if (codeObj.code.includes("context.api.")) {
235
+ codeObj.local_type = "context_api";
236
+ // set api_type to request, etc
237
+ let startIndex = codeObj.code.indexOf("context.api.") + "context.api.".length;
238
+ let endIndex = codeObj.code.indexOf("(", startIndex);
239
+ if (endIndex >= 0) {
240
+ codeObj.api_type = codeObj.code.substring(startIndex, endIndex);
241
+ }
242
+ }
227
243
  }
228
244
  }
229
245
  analyzeStepDefinitionwithStep(step) {
@@ -508,12 +524,8 @@ class StepsDefinitions {
508
524
  // );
509
525
  };
510
526
 
511
- executeStepRemote = async ({ feature_file_path, scenario, tempFolderPath, stepText }, options) => {
512
- if (!options) {
513
- options = {
514
- skipAfter: true,
515
- };
516
- }
527
+ executeStepRemote = async ({ feature_file_path, scenario, tempFolderPath, stepText, onCommandResult }, options) => {
528
+ const { skipAfter = true, skipBefore = true } = options || {};
517
529
  const environment = {
518
530
  ...process.env,
519
531
  };
@@ -539,11 +551,18 @@ class StepsDefinitions {
539
551
  // console.log("step", step.pattern);
540
552
  // });
541
553
 
542
- if (options.skipAfter) {
554
+ if (skipAfter) {
543
555
  // ignore afterAll/after hooks
544
556
  support.afterTestCaseHookDefinitions = [];
545
557
  support.afterTestRunHookDefinitions = [];
546
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
+ // }
547
566
 
548
567
  let errorMesssage = null;
549
568
  let info = null;
@@ -582,6 +601,11 @@ class StepsDefinitions {
582
601
  Object.assign(bvtError, { info: errInfo });
583
602
  throw bvtError;
584
603
  }
604
+
605
+ return {
606
+ result,
607
+ info,
608
+ };
585
609
  } catch (error) {
586
610
  console.error("Error running cucumber-js", error);
587
611
  throw error;
@@ -616,4 +640,23 @@ function findFilesWithExtension(folderPath, extension, rootFolder = null) {
616
640
 
617
641
  return result;
618
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
+
619
662
  export { StepsDefinitions, findFilesWithExtension };