@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.
- package/bin/assets/bundled_scripts/recorder.js +220 -0
- package/bin/assets/preload/accessibility.js +1 -1
- package/bin/assets/preload/find_context.js +1 -1
- package/bin/assets/preload/generateSelector.js +24 -0
- package/bin/assets/preload/locators.js +18 -0
- package/bin/assets/preload/recorderv3.js +80 -9
- 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 +844 -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 +65 -12
- package/bin/client/cli_helpers.js +0 -1
- package/bin/client/code_cleanup/utils.js +43 -14
- package/bin/client/code_gen/code_inversion.js +45 -13
- 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 +151 -45
- package/bin/client/cucumber/feature.js +96 -42
- package/bin/client/cucumber/project_to_document.js +8 -7
- package/bin/client/cucumber/steps_definitions.js +49 -16
- package/bin/client/local_agent.js +9 -7
- package/bin/client/operations/dump_tree.js +159 -5
- package/bin/client/playground/playground.js +1 -1
- package/bin/client/project.js +6 -2
- package/bin/client/recorderv3/bvt_recorder.js +279 -81
- package/bin/client/recorderv3/cli.js +1 -0
- package/bin/client/recorderv3/implemented_steps.js +111 -11
- package/bin/client/recorderv3/index.js +48 -4
- package/bin/client/recorderv3/network.js +299 -0
- package/bin/client/recorderv3/step_runner.js +183 -13
- package/bin/client/recorderv3/step_utils.js +159 -14
- package/bin/client/recorderv3/update_feature.js +53 -28
- package/bin/client/recording.js +8 -0
- package/bin/client/run_cucumber.js +116 -4
- package/bin/client/scenario_report.js +112 -50
- package/bin/client/test_scenario.js +0 -1
- package/bin/index.js +1 -0
- package/package.json +15 -8
- 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 ===
|
|
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:
|
|
@@ -240,30 +283,32 @@ const _generateCodeFromCommand = (step, elements, userData) => {
|
|
|
240
283
|
}
|
|
241
284
|
codeLines.push(line);
|
|
242
285
|
break;
|
|
243
|
-
case Types.
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
input = `"${
|
|
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
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
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
|
-
|
|
299
|
+
options = "null";
|
|
257
300
|
}
|
|
258
|
-
line
|
|
301
|
+
line = `await context.web.extractAttribute(elements["${elementIdentifier}"], "${attribute}", ${input}, _params,${options}, this);`;
|
|
302
|
+
|
|
259
303
|
if (element_name) {
|
|
260
|
-
comment = `//
|
|
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
|
-
|
|
266
|
-
|
|
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.
|
|
325
|
+
line = `await context.web.extractProperty(elements["${elementIdentifier}"], "${property}", ${input}, _params,${options}, this);`;
|
|
281
326
|
|
|
282
327
|
if (element_name) {
|
|
283
|
-
comment = `// Extract
|
|
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.
|
|
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.
|
|
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
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
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
|
-
|
|
610
|
-
//
|
|
611
|
-
//
|
|
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
|
|
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
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
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
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
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
|
-
|
|
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));
|
|
@@ -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
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
|
76
|
-
|
|
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
|
-
|
|
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 (
|
|
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 };
|