@dev-blinq/cucumber_client 1.0.1196-dev → 1.0.1196-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.
- package/bin/assets/bundled_scripts/recorder.js +220 -0
- package/bin/assets/preload/find_context.js +1 -1
- package/bin/assets/preload/recorderv3.js +75 -5
- package/bin/assets/preload/unique_locators.js +24 -3
- package/bin/assets/scripts/aria_snapshot.js +235 -0
- package/bin/assets/scripts/dom_attr.js +372 -0
- package/bin/assets/scripts/dom_element.js +0 -0
- package/bin/assets/scripts/dom_parent.js +185 -0
- package/bin/assets/scripts/event_utils.js +105 -0
- package/bin/assets/scripts/pw.js +7886 -0
- package/bin/assets/scripts/recorder.js +1147 -0
- package/bin/assets/scripts/snapshot_capturer.js +155 -0
- package/bin/assets/scripts/unique_locators.js +841 -0
- package/bin/assets/scripts/yaml.js +4770 -0
- package/bin/assets/templates/page_template.txt +2 -16
- package/bin/assets/templates/utils_template.txt +59 -7
- package/bin/client/cli_helpers.js +11 -13
- package/bin/client/code_cleanup/utils.js +42 -14
- package/bin/client/code_gen/code_inversion.js +48 -11
- package/bin/client/code_gen/index.js +3 -0
- package/bin/client/code_gen/page_reflection.js +37 -20
- package/bin/client/code_gen/playwright_codeget.js +153 -25
- package/bin/client/cucumber/feature.js +108 -28
- package/bin/client/cucumber/project_to_document.js +8 -7
- package/bin/client/cucumber/steps_definitions.js +118 -85
- package/bin/client/local_agent.js +6 -2
- package/bin/client/project.js +6 -2
- package/bin/client/recorderv3/bvt_recorder.js +272 -76
- package/bin/client/recorderv3/implemented_steps.js +69 -14
- package/bin/client/recorderv3/index.js +49 -7
- package/bin/client/recorderv3/network.js +299 -0
- package/bin/client/recorderv3/step_runner.js +183 -13
- package/bin/client/recorderv3/step_utils.js +155 -8
- package/bin/client/recorderv3/update_feature.js +58 -30
- package/bin/client/recording.js +7 -0
- package/bin/client/run_cucumber.js +16 -2
- package/bin/client/scenario_report.js +35 -8
- package/bin/client/test_scenario.js +0 -1
- package/bin/index.js +1 -0
- package/package.json +15 -8
|
@@ -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 ===
|
|
56
|
-
step.type ===
|
|
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
|
-
|
|
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
|
-
|
|
185
|
-
|
|
186
|
-
|
|
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
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
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:
|
|
@@ -286,6 +329,30 @@ const _generateCodeFromCommand = (step, elements, userData) => {
|
|
|
286
329
|
codeLines.push(line);
|
|
287
330
|
break;
|
|
288
331
|
}
|
|
332
|
+
case Types.EXTRACT_PROPERTY: {
|
|
333
|
+
property = escapeNonPrintables(step.parameters[0]);
|
|
334
|
+
variable = escapeNonPrintables(step.parameters[1]);
|
|
335
|
+
input = `"${variable}"`;
|
|
336
|
+
if (step.dataSource === "userData" || step.dataSource === "parameters") {
|
|
337
|
+
input = "_" + step.dataKey;
|
|
338
|
+
}
|
|
339
|
+
let options = "null";
|
|
340
|
+
if (step.regex !== "") {
|
|
341
|
+
options = `{regex: ${JSON.stringify(step.regex)},trimSpaces: ${step.trimSpaces}}`;
|
|
342
|
+
} else if (step.trimSpaces) {
|
|
343
|
+
options = `{trimSpaces: ${step.trimSpaces}}`;
|
|
344
|
+
} else {
|
|
345
|
+
options = "null";
|
|
346
|
+
}
|
|
347
|
+
line = `await context.web.extractProperty(elements["${elementIdentifier}"], "${property}", ${input}, _params,${options}, this);`;
|
|
348
|
+
|
|
349
|
+
if (element_name) {
|
|
350
|
+
comment = `// Extract property ${property} from ${element_name} to ${variable}`;
|
|
351
|
+
codeLines.push(escapeNonPrintables(comment));
|
|
352
|
+
}
|
|
353
|
+
codeLines.push(line);
|
|
354
|
+
break;
|
|
355
|
+
}
|
|
289
356
|
case Types.VERIFY_PAGE_SNAPSHOT:
|
|
290
357
|
comment = step.nestFrmLoc
|
|
291
358
|
? `// Verify page snapshot ${step.parameters[0]} in the frame ${step.selectors.iframe_src}`
|
|
@@ -507,15 +574,39 @@ const _generateCodeFromCommand = (step, elements, userData) => {
|
|
|
507
574
|
line = `await context.web.clickType( elements["${elementIdentifier}"] ,"${step.key}",null, _params, {"press":true}, this);`;
|
|
508
575
|
codeLines.push(line);
|
|
509
576
|
break;
|
|
510
|
-
case
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
577
|
+
case Types.CONTEXT_CLICK:
|
|
578
|
+
switch (step.count) {
|
|
579
|
+
case 1:
|
|
580
|
+
comment = `// Click on ${elementIdentifier ? elementIdentifier : step.label} in the context of ${escapeNonPrintables(step.value)}`;
|
|
581
|
+
codeLines.push(escapeNonPrintables(comment));
|
|
582
|
+
input = `"${escapeNonPrintables(step.value.replaceAll('"', '\\"'))}"`;
|
|
583
|
+
if (step.dataSource === "userData" || step.dataSource === "parameters") {
|
|
584
|
+
input = "_" + step.dataKey;
|
|
585
|
+
}
|
|
586
|
+
line = `await context.web.click(elements["${elementIdentifier}"], _params, {"context": ${input}}, this);`;
|
|
587
|
+
codeLines.push(line);
|
|
588
|
+
break;
|
|
589
|
+
case 2:
|
|
590
|
+
comment = `// Double click on ${elementIdentifier ? elementIdentifier : step.label} in the context of ${escapeNonPrintables(step.value)}`;
|
|
591
|
+
codeLines.push(escapeNonPrintables(comment));
|
|
592
|
+
input = `"${escapeNonPrintables(step.value.replaceAll('"', '\\"'))}"`;
|
|
593
|
+
if (step.dataSource === "userData" || step.dataSource === "parameters") {
|
|
594
|
+
input = "_" + step.dataKey;
|
|
595
|
+
}
|
|
596
|
+
line = `await context.web.click(elements["${elementIdentifier}"], _params, {"context": ${input}, clickCount:2}, this);`;
|
|
597
|
+
codeLines.push(line);
|
|
598
|
+
break;
|
|
599
|
+
default:
|
|
600
|
+
comment = `// Click on ${elementIdentifier ? elementIdentifier : step.label} in the context of ${escapeNonPrintables(step.value)}`;
|
|
601
|
+
codeLines.push(escapeNonPrintables(comment));
|
|
602
|
+
input = `"${escapeNonPrintables(step.value.replaceAll('"', '\\"'))}"`;
|
|
603
|
+
if (step.dataSource === "userData" || step.dataSource === "parameters") {
|
|
604
|
+
input = "_" + step.dataKey;
|
|
605
|
+
}
|
|
606
|
+
line = `await context.web.click(elements["${elementIdentifier}"], _params, {"context": ${input}}, this);`;
|
|
607
|
+
codeLines.push(line);
|
|
608
|
+
break;
|
|
516
609
|
}
|
|
517
|
-
line = `await context.web.click(elements["${elementIdentifier}"], _params, {"context": ${input}}, this);`;
|
|
518
|
-
codeLines.push(line);
|
|
519
610
|
break;
|
|
520
611
|
case "popup_close":
|
|
521
612
|
break;
|
|
@@ -541,6 +632,23 @@ const _generateCodeFromCommand = (step, elements, userData) => {
|
|
|
541
632
|
codeLines.push(line);
|
|
542
633
|
break;
|
|
543
634
|
}
|
|
635
|
+
case Types.VERIFY_PROPERTY: {
|
|
636
|
+
line = `await context.web.verifyProperty(elements["${elementIdentifier}"], `;
|
|
637
|
+
input = "_param_0";
|
|
638
|
+
if (step.dataSource === "userData" || step.dataSource === "parameters") {
|
|
639
|
+
input = "_" + step.dataKey;
|
|
640
|
+
}
|
|
641
|
+
line += `"${step.parameters[0]}", ${input}, _params, null, this);`;
|
|
642
|
+
codeLines.push(line);
|
|
643
|
+
break;
|
|
644
|
+
}
|
|
645
|
+
case Types.CONDITIONAL_WAIT: {
|
|
646
|
+
line = `await context.web.conditionalWait(elements["${elementIdentifier}"], `;
|
|
647
|
+
input = "_param_0";
|
|
648
|
+
line += `"${step.parameters[1]}", ${input}, _params, null, this);`;
|
|
649
|
+
codeLines.push(line);
|
|
650
|
+
break;
|
|
651
|
+
}
|
|
544
652
|
case Types.SET_INPUT_FILES: {
|
|
545
653
|
line = `await context.web.setInputFiles(elements["${elementIdentifier}"], `;
|
|
546
654
|
let files = step.parameters[0];
|
|
@@ -566,6 +674,22 @@ const _generateCodeFromCommand = (step, elements, userData) => {
|
|
|
566
674
|
return { codeLines, elements: elementsChanged ? elements : null, elementIdentifier, allStrategyLocators };
|
|
567
675
|
};
|
|
568
676
|
|
|
677
|
+
/**
|
|
678
|
+
* Generates a report command based on the given position.
|
|
679
|
+
* @param {"start"|"end"} position
|
|
680
|
+
*/
|
|
681
|
+
const generateReportCommand = (position, cmdId) => {
|
|
682
|
+
const codeLines = [];
|
|
683
|
+
if (position === "start") {
|
|
684
|
+
const line = `await context.web.addCommandToReport("${cmdId}", "PASSED", '{"status":"start","cmdId":"${cmdId}"}', {type:"cmdReport"},this);`;
|
|
685
|
+
codeLines.push(line);
|
|
686
|
+
} else if (position === "end") {
|
|
687
|
+
const line = `await context.web.addCommandToReport("${cmdId}", "PASSED", '{"status":"end","cmdId":"${cmdId}"}', {type:"cmdReport"},this);`;
|
|
688
|
+
codeLines.push(line);
|
|
689
|
+
}
|
|
690
|
+
return codeLines;
|
|
691
|
+
};
|
|
692
|
+
|
|
569
693
|
const generateCode = (recording, codePage, userData, projectDir, methodName) => {
|
|
570
694
|
const stepsDefinitions = new StepsDefinitions(projectDir);
|
|
571
695
|
stepsDefinitions.load(false);
|
|
@@ -606,12 +730,16 @@ const generateCode = (recording, codePage, userData, projectDir, methodName) =>
|
|
|
606
730
|
if (recordingStep.type === Types.COMPLETE) {
|
|
607
731
|
return;
|
|
608
732
|
}
|
|
609
|
-
|
|
610
|
-
//
|
|
611
|
-
//
|
|
733
|
+
|
|
734
|
+
// if (process.env.TEMP_RUN === "true") {
|
|
735
|
+
// codeLines.push(...generateReportCommand("start", recordingStep.cmdId));
|
|
612
736
|
// }
|
|
613
737
|
const result = _generateCodeFromCommand(recordingStep, elements, userData);
|
|
738
|
+
|
|
614
739
|
codeLines.push(...result.codeLines);
|
|
740
|
+
// if (process.env.TEMP_RUN === "true") {
|
|
741
|
+
// codeLines.push(...generateReportCommand("end", recordingStep.cmdId));
|
|
742
|
+
// }
|
|
615
743
|
if (result.elements) {
|
|
616
744
|
elements = result.elements;
|
|
617
745
|
}
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
+
import { IdGenerator } from "@cucumber/messages";
|
|
2
|
+
import { AstBuilder, GherkinClassicTokenMatcher, Parser, compile } from "@cucumber/gherkin";
|
|
1
3
|
import { loadConfiguration, loadSupport, runCucumber } from "@dev-blinq/cucumber-js/api";
|
|
2
4
|
import fs from "fs";
|
|
3
|
-
|
|
5
|
+
import os from "os";
|
|
6
|
+
import path from "path";
|
|
4
7
|
import { parseStepTextParameters, toCucumberExpression, unEscapeNonPrintables } from "./utils.js";
|
|
5
|
-
import
|
|
6
|
-
import { IdGenerator } from "@cucumber/messages";
|
|
8
|
+
import stream from "stream";
|
|
7
9
|
import { testStringForRegex } from "../recorderv3/update_feature.js";
|
|
8
|
-
|
|
10
|
+
import { generateTestData } from "./feature_data.js";
|
|
9
11
|
class DataTable {
|
|
10
12
|
constructor(dataTableDocument) {
|
|
11
13
|
if (!dataTableDocument) {
|
|
@@ -289,7 +291,7 @@ class Feature {
|
|
|
289
291
|
}
|
|
290
292
|
return scenarios;
|
|
291
293
|
}
|
|
292
|
-
generateStracture(stepsDefinitions, fileName, scenarioName = null) {
|
|
294
|
+
generateStracture(stepsDefinitions, fileName, scenarioName = null, errorsInParsing = [], parseFailedFiles = false) {
|
|
293
295
|
const document = {};
|
|
294
296
|
document.featureName = this.feature.name;
|
|
295
297
|
document.fileName = fileName;
|
|
@@ -317,6 +319,28 @@ class Feature {
|
|
|
317
319
|
stepInfo.functionName = stepDef.functionName;
|
|
318
320
|
stepInfo.implemented_at = stepDef.implemented_at;
|
|
319
321
|
}
|
|
322
|
+
if (!stepDef && parseFailedFiles) {
|
|
323
|
+
//Check if the stepName exists in one of the failedToParseFiles
|
|
324
|
+
try {
|
|
325
|
+
const stepName = stepsDefinitions._stepNameToTemplate(stepInfo.template);
|
|
326
|
+
if (errorsInParsing && errorsInParsing.length > 0) {
|
|
327
|
+
for (let k = 0; k < errorsInParsing.length; k++) {
|
|
328
|
+
const failedFile = errorsInParsing[k].file;
|
|
329
|
+
//Read File content, and check if the stepName exists in the file
|
|
330
|
+
const fileContent = fs.readFileSync(failedFile, "utf8");
|
|
331
|
+
if (fileContent.includes(stepName)) {
|
|
332
|
+
stepInfo.implemented = true;
|
|
333
|
+
stepInfo.mjsFile = failedFile.slice(path.normalize(failedFile).indexOf("features/step_definitions"));
|
|
334
|
+
stepInfo.functionName = stepName;
|
|
335
|
+
stepInfo.error = errorsInParsing[k].error;
|
|
336
|
+
break;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
} catch (e) {
|
|
341
|
+
console.error(`Error while checking step ${stepInfo.template} in failed files:`, e);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
320
344
|
}
|
|
321
345
|
document.scenarios.push(scenarioInfo);
|
|
322
346
|
}
|
|
@@ -350,6 +374,11 @@ class Examples {
|
|
|
350
374
|
return null;
|
|
351
375
|
}
|
|
352
376
|
let value = this.tableBody[0].cells[parameterIndex].value;
|
|
377
|
+
if (!value) {
|
|
378
|
+
console.warn(`Parameter ${parameterName} not found in examples table body, or it is empty`);
|
|
379
|
+
return null;
|
|
380
|
+
}
|
|
381
|
+
|
|
353
382
|
return unEscapeNonPrintables(value); // While editing in recorder, we need to remove backslashes
|
|
354
383
|
}
|
|
355
384
|
}
|
|
@@ -427,29 +456,7 @@ class Scenario {
|
|
|
427
456
|
return this.scenarioText;
|
|
428
457
|
}
|
|
429
458
|
}
|
|
430
|
-
const scenarioResolution = async (featureFilePath) => {
|
|
431
|
-
if (!fs.existsSync(featureFilePath)) {
|
|
432
|
-
throw new Error(`Feature file ${featureFilePath} does not exist`);
|
|
433
|
-
}
|
|
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
|
-
const featureFileContent = fs.readFileSync(featureFilePath, "utf8");
|
|
446
|
-
const gherkinDocument = parser.parse(featureFileContent, newId);
|
|
447
459
|
|
|
448
|
-
const feature = new Feature(gherkinDocument, featureFileContent);
|
|
449
|
-
//const scenario = feature.getScenario(scenarioName);
|
|
450
|
-
//return scenario;
|
|
451
|
-
return feature;
|
|
452
|
-
};
|
|
453
460
|
const getDocumentAndPickel = (featureName, featureContent, scenarioName) => {
|
|
454
461
|
const newId = IdGenerator.uuid();
|
|
455
462
|
const builder = new AstBuilder(newId);
|
|
@@ -462,12 +469,85 @@ const getDocumentAndPickel = (featureName, featureContent, scenarioName) => {
|
|
|
462
469
|
|
|
463
470
|
const gherkinDocument = parser.parse(featureContent, newId);
|
|
464
471
|
const pickles = compile(gherkinDocument, uri, newId);
|
|
465
|
-
|
|
466
472
|
// Step 3: Find the specific scenario Pickle
|
|
467
473
|
const pickle = pickles.find((pickle) => {
|
|
468
474
|
return pickle.name === scenarioName;
|
|
469
475
|
});
|
|
470
476
|
return { gherkinDocument, pickle };
|
|
471
477
|
};
|
|
478
|
+
const scenarioResolution = async (featureFilePath) => {
|
|
479
|
+
if (!fs.existsSync(featureFilePath)) {
|
|
480
|
+
throw new Error(`Feature file ${featureFilePath} does not exist`);
|
|
481
|
+
}
|
|
482
|
+
const featureFileContent = fs.readFileSync(featureFilePath, "utf8");
|
|
483
|
+
let tmpDir = null;
|
|
484
|
+
try {
|
|
485
|
+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "ai-qa"));
|
|
486
|
+
const tmpFeaturePath = path.join(tmpDir, "features");
|
|
487
|
+
fs.mkdirSync(tmpFeaturePath);
|
|
488
|
+
const tmpFeatureFilePath = path.join(tmpFeaturePath, path.basename(featureFilePath));
|
|
489
|
+
let result = generateTestData(featureFilePath);
|
|
490
|
+
if (result.changed) {
|
|
491
|
+
fs.writeFileSync(tmpFeatureFilePath, result.newContent);
|
|
492
|
+
console.log("Fake data was generated for this scenario");
|
|
493
|
+
console.log("Variables:");
|
|
494
|
+
for (let key in result.variables) {
|
|
495
|
+
console.log(`${key}: ${result.variables[key].fake}`);
|
|
496
|
+
}
|
|
497
|
+
console.log("Other fake data:");
|
|
498
|
+
for (let i = 0; i < result.otherFakeData.length; i++) {
|
|
499
|
+
console.log(`${result.otherFakeData[i].var}: ${result.otherFakeData[i].fake}`);
|
|
500
|
+
}
|
|
501
|
+
} else {
|
|
502
|
+
fs.copyFileSync(featureFilePath, tmpFeatureFilePath);
|
|
503
|
+
}
|
|
504
|
+
const writable = new stream.Writable({
|
|
505
|
+
write: function (chunk, encoding, next) {
|
|
506
|
+
//console.log(chunk.toString());
|
|
507
|
+
next();
|
|
508
|
+
},
|
|
509
|
+
});
|
|
510
|
+
const environment = { cwd: tmpDir, stdout: writable, stderr: writable };
|
|
511
|
+
// load configuration from a particular file, and override a specific option
|
|
512
|
+
const provided = {
|
|
513
|
+
default: "--publish-quiet",
|
|
514
|
+
require: [tmpDir],
|
|
515
|
+
failFast: false,
|
|
516
|
+
};
|
|
472
517
|
|
|
518
|
+
let gherkinDocument = null;
|
|
519
|
+
const { runConfiguration } = await loadConfiguration({ provided }, environment);
|
|
520
|
+
// load the support code upfront
|
|
521
|
+
const support = await loadSupport(runConfiguration, environment);
|
|
522
|
+
// run cucumber, using the support code we loaded already
|
|
523
|
+
await runCucumber({ ...runConfiguration, support }, environment, function (event) {
|
|
524
|
+
// if (event.source) {
|
|
525
|
+
// scenarioInfo.source = event.source.data;
|
|
526
|
+
// }
|
|
527
|
+
// if (event.pickle && event.pickle.name === scenarioName) {
|
|
528
|
+
// scenarioInfo.pickle = event.pickle;
|
|
529
|
+
// }
|
|
530
|
+
if (event.gherkinDocument) {
|
|
531
|
+
gherkinDocument = event.gherkinDocument;
|
|
532
|
+
}
|
|
533
|
+
//console.log(event);
|
|
534
|
+
//console.log(JSON.stringify(event, null, 2));
|
|
535
|
+
// console.log("");
|
|
536
|
+
});
|
|
537
|
+
const feature = new Feature(gherkinDocument, featureFileContent);
|
|
538
|
+
//const scenario = feature.getScenario(scenarioName);
|
|
539
|
+
//return scenario;
|
|
540
|
+
return feature;
|
|
541
|
+
} finally {
|
|
542
|
+
try {
|
|
543
|
+
if (tmpDir) {
|
|
544
|
+
fs.rmSync(tmpDir, { recursive: true });
|
|
545
|
+
}
|
|
546
|
+
} catch (e) {
|
|
547
|
+
console.error(
|
|
548
|
+
`An error has occurred while removing the temp folder at ${tmpDir}. Please remove it manually. Error: ${e}`
|
|
549
|
+
);
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
};
|
|
473
553
|
export { Feature, Scenario, Step, DataTable, Examples, scenarioResolution, getDocumentAndPickel };
|
|
@@ -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
|
-
|
|
13
|
-
stepDefinition
|
|
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
|
-
//
|
|
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(
|
|
62
|
+
// console.log(JSON.stringify(documents));
|