@dev-blinq/cucumber_client 1.0.1197-dev → 1.0.1197-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 +92 -35
- package/bin/client/cucumber/steps_definitions.js +109 -83
- 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) {
|
|
@@ -311,6 +313,12 @@ class Feature {
|
|
|
311
313
|
stepInfo.template = step.getTemplate();
|
|
312
314
|
scenarioInfo.steps.push(stepInfo);
|
|
313
315
|
const stepDef = stepsDefinitions.findMatchingStep(stepInfo.template);
|
|
316
|
+
stepInfo.implemented = stepDef ? true : false;
|
|
317
|
+
if (stepDef) {
|
|
318
|
+
stepInfo.mjsFile = stepDef.file;
|
|
319
|
+
stepInfo.functionName = stepDef.functionName;
|
|
320
|
+
stepInfo.implemented_at = stepDef.implemented_at;
|
|
321
|
+
}
|
|
314
322
|
if (!stepDef && parseFailedFiles) {
|
|
315
323
|
//Check if the stepName exists in one of the failedToParseFiles
|
|
316
324
|
try {
|
|
@@ -322,10 +330,9 @@ class Feature {
|
|
|
322
330
|
const fileContent = fs.readFileSync(failedFile, "utf8");
|
|
323
331
|
if (fileContent.includes(stepName)) {
|
|
324
332
|
stepInfo.implemented = true;
|
|
325
|
-
stepInfo.mjsFile = failedFile;
|
|
333
|
+
stepInfo.mjsFile = failedFile.slice(path.normalize(failedFile).indexOf("features/step_definitions"));
|
|
326
334
|
stepInfo.functionName = stepName;
|
|
327
335
|
stepInfo.error = errorsInParsing[k].error;
|
|
328
|
-
console.log(JSON.stringify(stepInfo));
|
|
329
336
|
break;
|
|
330
337
|
}
|
|
331
338
|
}
|
|
@@ -334,12 +341,6 @@ class Feature {
|
|
|
334
341
|
console.error(`Error while checking step ${stepInfo.template} in failed files:`, e);
|
|
335
342
|
}
|
|
336
343
|
}
|
|
337
|
-
stepInfo.implemented = stepDef ? true : false;
|
|
338
|
-
if (stepDef) {
|
|
339
|
-
stepInfo.mjsFile = stepDef.file;
|
|
340
|
-
stepInfo.functionName = stepDef.functionName;
|
|
341
|
-
stepInfo.implemented_at = stepDef.implemented_at;
|
|
342
|
-
}
|
|
343
344
|
}
|
|
344
345
|
document.scenarios.push(scenarioInfo);
|
|
345
346
|
}
|
|
@@ -373,6 +374,11 @@ class Examples {
|
|
|
373
374
|
return null;
|
|
374
375
|
}
|
|
375
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
|
+
|
|
376
382
|
return unEscapeNonPrintables(value); // While editing in recorder, we need to remove backslashes
|
|
377
383
|
}
|
|
378
384
|
}
|
|
@@ -450,29 +456,7 @@ class Scenario {
|
|
|
450
456
|
return this.scenarioText;
|
|
451
457
|
}
|
|
452
458
|
}
|
|
453
|
-
const scenarioResolution = async (featureFilePath) => {
|
|
454
|
-
if (!fs.existsSync(featureFilePath)) {
|
|
455
|
-
throw new Error(`Feature file ${featureFilePath} does not exist`);
|
|
456
|
-
}
|
|
457
|
-
const newId = IdGenerator.uuid();
|
|
458
|
-
const builder = new AstBuilder(newId);
|
|
459
|
-
const matcher = new GherkinClassicTokenMatcher();
|
|
460
|
-
|
|
461
|
-
const parser = new Parser(builder, matcher);
|
|
462
|
-
|
|
463
|
-
// normalize the path to start with featuers/ if it's not cut the featureFielPath to only the part after features/
|
|
464
|
-
let uri = featureFilePath.startsWith("features/")
|
|
465
|
-
? featureFilePath
|
|
466
|
-
: "features/" + featureFilePath.split("features/")[1];
|
|
467
|
-
|
|
468
|
-
const featureFileContent = fs.readFileSync(featureFilePath, "utf8");
|
|
469
|
-
const gherkinDocument = parser.parse(featureFileContent, newId);
|
|
470
459
|
|
|
471
|
-
const feature = new Feature(gherkinDocument, featureFileContent);
|
|
472
|
-
//const scenario = feature.getScenario(scenarioName);
|
|
473
|
-
//return scenario;
|
|
474
|
-
return feature;
|
|
475
|
-
};
|
|
476
460
|
const getDocumentAndPickel = (featureName, featureContent, scenarioName) => {
|
|
477
461
|
const newId = IdGenerator.uuid();
|
|
478
462
|
const builder = new AstBuilder(newId);
|
|
@@ -485,12 +469,85 @@ const getDocumentAndPickel = (featureName, featureContent, scenarioName) => {
|
|
|
485
469
|
|
|
486
470
|
const gherkinDocument = parser.parse(featureContent, newId);
|
|
487
471
|
const pickles = compile(gherkinDocument, uri, newId);
|
|
488
|
-
|
|
489
472
|
// Step 3: Find the specific scenario Pickle
|
|
490
473
|
const pickle = pickles.find((pickle) => {
|
|
491
474
|
return pickle.name === scenarioName;
|
|
492
475
|
});
|
|
493
476
|
return { gherkinDocument, pickle };
|
|
494
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
|
+
};
|
|
495
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
|
+
};
|
|
496
553
|
export { Feature, Scenario, Step, DataTable, Examples, scenarioResolution, getDocumentAndPickel };
|
|
@@ -62,24 +62,23 @@ class StepsDefinitions {
|
|
|
62
62
|
// console.log({ mjsFiles });
|
|
63
63
|
for (let i = 0; i < mjsFiles.length; i++) {
|
|
64
64
|
const mjsFile = mjsFiles[i];
|
|
65
|
-
console.log("loading step definitions from file", mjsFile);
|
|
66
65
|
const filePath = path.join(
|
|
67
66
|
this.baseFolder,
|
|
68
67
|
this.isTemp ? process.env.tempFeaturesFolderPath ?? "__temp_features" : "features",
|
|
69
|
-
mjsFile
|
|
68
|
+
mjsFile
|
|
69
|
+
);
|
|
70
70
|
const codePage = new CodePage(filePath);
|
|
71
71
|
try {
|
|
72
72
|
codePage.generateModel();
|
|
73
73
|
} catch (error) {
|
|
74
|
-
logger.info("unable to generate model for
|
|
75
|
-
if(supressErrors) {
|
|
74
|
+
logger.info("unable to generate model for", mjsFile, error);
|
|
75
|
+
if (supressErrors) {
|
|
76
76
|
errors.push({
|
|
77
77
|
file: filePath,
|
|
78
78
|
error: error.message,
|
|
79
79
|
});
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
throw error;
|
|
80
|
+
} else {
|
|
81
|
+
continue;
|
|
83
82
|
}
|
|
84
83
|
}
|
|
85
84
|
this.initPage(codePage, mjsFile);
|
|
@@ -524,86 +523,94 @@ class StepsDefinitions {
|
|
|
524
523
|
// }
|
|
525
524
|
// );
|
|
526
525
|
};
|
|
526
|
+
//! Deprecated, not removing until stepRunner.executeStepRemote is stable
|
|
527
|
+
// executeStepRemote = async ({ feature_file_path, scenario, tempFolderPath, stepText, onCommandResult }, options) => {
|
|
528
|
+
// // const { skipAfter = true, skipBefore = true } = options || {};
|
|
529
|
+
// const environment = {
|
|
530
|
+
// ...process.env,
|
|
531
|
+
// };
|
|
527
532
|
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
533
|
+
// try {
|
|
534
|
+
// const { loadConfiguration, loadSupport, runCucumber } = await import("@dev-blinq/cucumber-js/api");
|
|
535
|
+
// const { runConfiguration } = await loadConfiguration(
|
|
536
|
+
// {
|
|
537
|
+
// provided: {
|
|
538
|
+
// name: [scenario],
|
|
539
|
+
// paths: [feature_file_path],
|
|
540
|
+
// import: [path.join(tempFolderPath, "step_definitions", "**", "*.mjs")],
|
|
541
|
+
// // format: ["bvt"],
|
|
542
|
+
// },
|
|
543
|
+
// },
|
|
544
|
+
// { cwd: process.cwd(), env: environment }
|
|
545
|
+
// );
|
|
546
|
+
// // const files = glob.sync(path.join(tempFolderPath, "step_definitions", "**", "*.mjs"));
|
|
547
|
+
// // console.log("Files found:", files);
|
|
548
|
+
// const support = await loadSupport(runConfiguration, { cwd: process.cwd(), env: environment });
|
|
549
|
+
// // console.log("found ", support.stepDefinitions.length, "step definitions");
|
|
550
|
+
// // support.stepDefinitions.map((step) => {
|
|
551
|
+
// // console.log("step", step.pattern);
|
|
552
|
+
// // });
|
|
537
553
|
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
);
|
|
551
|
-
// const files = glob.sync(path.join(tempFolderPath, "step_definitions", "**", "*.mjs"));
|
|
552
|
-
// console.log("Files found:", files);
|
|
553
|
-
const support = await loadSupport(runConfiguration, { cwd: process.cwd(), env: environment });
|
|
554
|
-
// console.log("found ", support.stepDefinitions.length, "step definitions");
|
|
555
|
-
// support.stepDefinitions.map((step) => {
|
|
556
|
-
// console.log("step", step.pattern);
|
|
557
|
-
// });
|
|
554
|
+
// if (skipAfter) {
|
|
555
|
+
// // ignore afterAll/after hooks
|
|
556
|
+
// support.afterTestCaseHookDefinitions = [];
|
|
557
|
+
// support.afterTestRunHookDefinitions = [];
|
|
558
|
+
// }
|
|
559
|
+
// // if (skipBefore) {
|
|
560
|
+
// // // ignore beforeAll/before hooks
|
|
561
|
+
// // support.beforeTestCaseHookDefinitions = support.beforeTestCaseHookDefinitions.filter((hook) => {
|
|
562
|
+
// // return hook.uri.endsWith("utils.mjs")
|
|
563
|
+
// // });
|
|
564
|
+
// // support.beforeTestRunHookDefinitions = [];
|
|
565
|
+
// // }
|
|
558
566
|
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
567
|
+
// let errorMesssage = null;
|
|
568
|
+
// let info = null;
|
|
569
|
+
// let errInfo = null;
|
|
570
|
+
// const result = await runCucumber({ ...runConfiguration, support }, environment, (message) => {
|
|
571
|
+
// if (message.testStepFinished) {
|
|
572
|
+
// const { testStepFinished } = message;
|
|
573
|
+
// const { testStepResult } = testStepFinished;
|
|
574
|
+
// if (testStepResult.status === "FAILED" || testStepResult.status === "AMBIGUOUS") {
|
|
575
|
+
// if (!errorMesssage) {
|
|
576
|
+
// errorMesssage = testStepResult.message;
|
|
577
|
+
// if (info) {
|
|
578
|
+
// errInfo = info;
|
|
579
|
+
// }
|
|
580
|
+
// }
|
|
581
|
+
// }
|
|
582
|
+
// if (testStepResult.status === "UNDEFINED") {
|
|
583
|
+
// if (!errorMesssage) {
|
|
584
|
+
// errorMesssage = `step ${JSON.stringify(stepText)} is ${testStepResult.status}`;
|
|
585
|
+
// if (info) {
|
|
586
|
+
// errInfo = info;
|
|
587
|
+
// }
|
|
588
|
+
// }
|
|
589
|
+
// }
|
|
590
|
+
// }
|
|
591
|
+
// if (message.attachment) {
|
|
592
|
+
// const attachment = message.attachment;
|
|
593
|
+
// if (attachment.mediaType === "application/json" && attachment.body) {
|
|
594
|
+
// const body = JSON.parse(attachment.body);
|
|
595
|
+
// info = body.info;
|
|
596
|
+
// }
|
|
597
|
+
// }
|
|
598
|
+
// });
|
|
599
|
+
// if (errorMesssage) {
|
|
600
|
+
// const bvtError = new Error(errorMesssage);
|
|
601
|
+
// Object.assign(bvtError, { info: errInfo });
|
|
602
|
+
// throw bvtError;
|
|
603
|
+
// }
|
|
564
604
|
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
errorMesssage = testStepResult.message;
|
|
575
|
-
if (info) {
|
|
576
|
-
errInfo = info;
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
|
-
}
|
|
580
|
-
if (testStepResult.status === "UNDEFINED") {
|
|
581
|
-
if (!errorMesssage) {
|
|
582
|
-
errorMesssage = `step ${JSON.stringify(stepText)} is ${testStepResult.status}`;
|
|
583
|
-
if (info) {
|
|
584
|
-
errInfo = info;
|
|
585
|
-
}
|
|
586
|
-
}
|
|
587
|
-
}
|
|
588
|
-
}
|
|
589
|
-
if (message.attachment) {
|
|
590
|
-
const attachment = message.attachment;
|
|
591
|
-
if (attachment.mediaType === "application/json" && attachment.body) {
|
|
592
|
-
const body = JSON.parse(attachment.body);
|
|
593
|
-
info = body.info;
|
|
594
|
-
}
|
|
595
|
-
}
|
|
596
|
-
});
|
|
597
|
-
if (errorMesssage) {
|
|
598
|
-
const bvtError = new Error(errorMesssage);
|
|
599
|
-
Object.assign(bvtError, { info: errInfo });
|
|
600
|
-
throw bvtError;
|
|
601
|
-
}
|
|
602
|
-
} catch (error) {
|
|
603
|
-
console.error("Error running cucumber-js", error);
|
|
604
|
-
throw error;
|
|
605
|
-
}
|
|
606
|
-
};
|
|
605
|
+
// return {
|
|
606
|
+
// result,
|
|
607
|
+
// info,
|
|
608
|
+
// };
|
|
609
|
+
// } catch (error) {
|
|
610
|
+
// console.error("Error running cucumber-js", error);
|
|
611
|
+
// throw error;
|
|
612
|
+
// }
|
|
613
|
+
// };
|
|
607
614
|
}
|
|
608
615
|
function findFilesWithExtension(folderPath, extension, rootFolder = null) {
|
|
609
616
|
let result = [];
|
|
@@ -633,4 +640,23 @@ function findFilesWithExtension(folderPath, extension, rootFolder = null) {
|
|
|
633
640
|
|
|
634
641
|
return result;
|
|
635
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
|
+
|
|
636
662
|
export { StepsDefinitions, findFilesWithExtension };
|