@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.
- 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 +85 -11
- 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 +112 -18
- 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 +152 -48
- 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 +59 -16
- package/bin/client/local_agent.js +9 -7
- package/bin/client/operations/dump_tree.js +159 -8
- package/bin/client/playground/playground.js +1 -1
- package/bin/client/project.js +6 -2
- package/bin/client/recorderv3/bvt_recorder.js +236 -79
- package/bin/client/recorderv3/cli.js +1 -0
- package/bin/client/recorderv3/implemented_steps.js +111 -11
- package/bin/client/recorderv3/index.js +45 -4
- package/bin/client/recorderv3/network.js +299 -0
- package/bin/client/recorderv3/step_runner.js +179 -13
- package/bin/client/recorderv3/step_utils.js +159 -14
- package/bin/client/recorderv3/update_feature.js +55 -30
- 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
|
@@ -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 ===
|
|
58
|
-
step.type ===
|
|
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
|
-
|
|
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
|
-
|
|
187
|
-
|
|
188
|
-
|
|
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
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
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.
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
input = `"${
|
|
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
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
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
|
-
|
|
299
|
+
options = "null";
|
|
259
300
|
}
|
|
260
|
-
line
|
|
301
|
+
line = `await context.web.extractAttribute(elements["${elementIdentifier}"], "${attribute}", ${input}, _params,${options}, this);`;
|
|
302
|
+
|
|
261
303
|
if (element_name) {
|
|
262
|
-
comment = `//
|
|
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
|
-
|
|
268
|
-
|
|
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.
|
|
325
|
+
line = `await context.web.extractProperty(elements["${elementIdentifier}"], "${property}", ${input}, _params,${options}, this);`;
|
|
283
326
|
|
|
284
327
|
if (element_name) {
|
|
285
|
-
comment = `// Extract
|
|
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.
|
|
339
|
+
line = `const frameLocator = ${JSON.stringify(step.selectors ?? null)}`;
|
|
297
340
|
codeLines.push(line);
|
|
298
|
-
line = `await context.web.snapshotValidation(frameLocator
|
|
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
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
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
|
-
|
|
612
|
-
//
|
|
613
|
-
//
|
|
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
|
|
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
|
}
|
|
@@ -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
|
-
|
|
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 (
|
|
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 };
|