@dev-blinq/cucumber_client 1.0.1224-dev → 1.0.1224-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/recorderv3.js +68 -5
- package/bin/assets/preload/unique_locators.js +1 -1
- 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/_hooks_template.txt +37 -0
- package/bin/assets/templates/page_template.txt +2 -16
- package/bin/assets/templates/utils_template.txt +48 -63
- package/bin/client/apiTest/apiTest.js +6 -0
- 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 +68 -10
- 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 +170 -33
- package/bin/client/cucumber/feature.js +85 -27
- package/bin/client/cucumber/steps_definitions.js +84 -76
- package/bin/client/local_agent.js +7 -3
- package/bin/client/project.js +7 -1
- package/bin/client/recorderv3/bvt_recorder.js +267 -80
- package/bin/client/recorderv3/implemented_steps.js +74 -12
- package/bin/client/recorderv3/index.js +58 -8
- package/bin/client/recorderv3/network.js +299 -0
- package/bin/client/recorderv3/step_runner.js +319 -67
- package/bin/client/recorderv3/step_utils.js +157 -6
- package/bin/client/recorderv3/update_feature.js +58 -30
- package/bin/client/recording.js +7 -0
- package/bin/client/run_cucumber.js +15 -2
- package/bin/client/scenario_report.js +4 -8
- package/bin/client/test_scenario.js +0 -1
- package/bin/index.js +1 -0
- package/package.json +15 -8
|
@@ -12,6 +12,13 @@ const findElementIdentifier = (node, step, userData, elements) => {
|
|
|
12
12
|
|
|
13
13
|
let elementIdentifier = null;
|
|
14
14
|
const keys = Object.keys(elements);
|
|
15
|
+
|
|
16
|
+
for (let i = 0; i < keys.length; i++) {
|
|
17
|
+
const element_key = keys[i];
|
|
18
|
+
const element = elements[element_key];
|
|
19
|
+
element["element_key"] = element_key;
|
|
20
|
+
}
|
|
21
|
+
|
|
15
22
|
let name = node.name;
|
|
16
23
|
if (!name && step.dataKey) {
|
|
17
24
|
name = step.dataKey;
|
|
@@ -47,21 +54,30 @@ const _isCodeGenerationStep = (step) => {
|
|
|
47
54
|
step.type === Types.SELECT ||
|
|
48
55
|
step.type === Types.HOVER ||
|
|
49
56
|
step.type === Types.EXTRACT_ATTRIBUTE ||
|
|
57
|
+
step.type === Types.EXTRACT_PROPERTY ||
|
|
50
58
|
step.type === Types.SET_DATE_TIME ||
|
|
51
59
|
step.type === Types.CHECK ||
|
|
52
60
|
step.type === Types.PRESS ||
|
|
53
61
|
step.type === Types.SET_INPUT ||
|
|
54
62
|
step.type === Types.FILL_UNKNOWN ||
|
|
55
|
-
step.type ===
|
|
56
|
-
step.type ===
|
|
63
|
+
step.type === Types.CONTEXT_CLICK ||
|
|
64
|
+
step.type === Types.PARAMETERIZED_CLICK ||
|
|
57
65
|
step.type === Types.VERIFY_ATTRIBUTE ||
|
|
66
|
+
step.type === Types.VERIFY_PROPERTY ||
|
|
58
67
|
step.type === Types.SET_INPUT_FILES ||
|
|
59
|
-
step.type === Types.VERIFY_PAGE_SNAPSHOT
|
|
68
|
+
step.type === Types.VERIFY_PAGE_SNAPSHOT ||
|
|
69
|
+
step.type === Types.CONDITIONAL_WAIT
|
|
60
70
|
) {
|
|
61
71
|
return true;
|
|
62
72
|
}
|
|
63
73
|
return false;
|
|
64
74
|
};
|
|
75
|
+
// Note: this function is used to exclude a key from an object
|
|
76
|
+
// Please move it to utils and use it as a reusable function ...
|
|
77
|
+
function excludeKey(obj, keyToRemove) {
|
|
78
|
+
const { [keyToRemove]: _, ...rest } = obj;
|
|
79
|
+
return rest;
|
|
80
|
+
}
|
|
65
81
|
const splitToLocatorsGroups = (locators) => {
|
|
66
82
|
const no_text = locators.locators.filter((locator) => locator.mode === "NO_TEXT");
|
|
67
83
|
const ignore_digit = locators.locators.filter((locator) => locator.mode === "IGNORE_DIGIT");
|
|
@@ -83,7 +99,7 @@ const splitToLocatorsGroups = (locators) => {
|
|
|
83
99
|
const _generateCodeFromCommand = (step, elements, userData) => {
|
|
84
100
|
let elementsChanged = false;
|
|
85
101
|
|
|
86
|
-
let elementIdentifier =
|
|
102
|
+
let elementIdentifier = step.locators?.element_key;
|
|
87
103
|
let locatorObject = step.locators;
|
|
88
104
|
// handle element
|
|
89
105
|
let element_name = null;
|
|
@@ -121,17 +137,20 @@ const _generateCodeFromCommand = (step, elements, userData) => {
|
|
|
121
137
|
let locatorObjectClone = JSON.parse(JSON.stringify(locatorObject));
|
|
122
138
|
element_name = locatorObjectClone.element_name;
|
|
123
139
|
delete locatorObjectClone.element_name;
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
140
|
+
if (!elementIdentifier) {
|
|
141
|
+
keys.forEach((key) => {
|
|
142
|
+
let elementClone = JSON.parse(JSON.stringify(elements[key]));
|
|
143
|
+
delete elementClone.element_name;
|
|
144
|
+
if (JSON.stringify(elementClone) === JSON.stringify(locatorObjectClone)) {
|
|
145
|
+
elementIdentifier = key;
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
}
|
|
131
149
|
if (!elementIdentifier) {
|
|
132
150
|
elementIdentifier = findElementIdentifier(node, step, userData, elements);
|
|
133
151
|
}
|
|
134
|
-
|
|
152
|
+
const withoutAllStrategyLocators = excludeKey(locatorObject, "allStrategyLocators");
|
|
153
|
+
elements[elementIdentifier] = { ...withoutAllStrategyLocators, element_key: elementIdentifier };
|
|
135
154
|
elementsChanged = true;
|
|
136
155
|
}
|
|
137
156
|
let optionElement = null;
|
|
@@ -157,6 +176,7 @@ const _generateCodeFromCommand = (step, elements, userData) => {
|
|
|
157
176
|
let variable = null;
|
|
158
177
|
let format = null;
|
|
159
178
|
let options = null;
|
|
179
|
+
let property = null;
|
|
160
180
|
switch (step.type) {
|
|
161
181
|
case Types.SET_INPUT:
|
|
162
182
|
line = `await context.web.setInputValue(elements["${elementIdentifier}"], `;
|
|
@@ -181,19 +201,51 @@ const _generateCodeFromCommand = (step, elements, userData) => {
|
|
|
181
201
|
line = `await context.web.setCheck(elements["${elementIdentifier}"], ${step.check}, _params, null, this);`;
|
|
182
202
|
codeLines.push(line);
|
|
183
203
|
} else {
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
204
|
+
switch (step.count) {
|
|
205
|
+
case 1:
|
|
206
|
+
if (element_name) {
|
|
207
|
+
comment = `// Click on ${element_name}`;
|
|
208
|
+
codeLines.push(escapeNonPrintables(comment));
|
|
209
|
+
}
|
|
210
|
+
line = `await context.web.click(elements["${elementIdentifier}"], _params, null, this);`;
|
|
211
|
+
codeLines.push(line);
|
|
212
|
+
break;
|
|
213
|
+
case 2:
|
|
214
|
+
if (element_name) {
|
|
215
|
+
comment = `// Double click on ${element_name}`;
|
|
216
|
+
codeLines.push(escapeNonPrintables(comment));
|
|
217
|
+
}
|
|
218
|
+
line = `await context.web.click(elements["${elementIdentifier}"], _params, {clickCount:2}, this);`;
|
|
219
|
+
codeLines.push(line);
|
|
220
|
+
break;
|
|
221
|
+
default:
|
|
222
|
+
if (element_name) {
|
|
223
|
+
comment = `// Click on ${element_name}`;
|
|
224
|
+
codeLines.push(escapeNonPrintables(comment));
|
|
225
|
+
}
|
|
226
|
+
line = `await context.web.click(elements["${elementIdentifier}"], _params, null, this);`;
|
|
227
|
+
codeLines.push(line);
|
|
228
|
+
break;
|
|
187
229
|
}
|
|
188
|
-
|
|
189
|
-
line = `await context.web.click(elements["${elementIdentifier}"], _params, null, this);`;
|
|
190
|
-
codeLines.push(line);
|
|
191
230
|
}
|
|
192
231
|
break;
|
|
193
|
-
case
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
232
|
+
case Types.PARAMETERIZED_CLICK:
|
|
233
|
+
switch (step.count) {
|
|
234
|
+
case 1:
|
|
235
|
+
comment = `// Parameterized click on ${step.value}`;
|
|
236
|
+
codeLines.push(escapeNonPrintables(comment));
|
|
237
|
+
line = `await context.web.click(elements["${elementIdentifier}"], _params, null, this);`;
|
|
238
|
+
break;
|
|
239
|
+
case 2:
|
|
240
|
+
comment = `// Parameterized double click on ${step.value}`;
|
|
241
|
+
codeLines.push(escapeNonPrintables(comment));
|
|
242
|
+
line = `await context.web.click(elements["${elementIdentifier}"], _params, {clickCount:2}, this);`;
|
|
243
|
+
break;
|
|
244
|
+
default:
|
|
245
|
+
comment = `// Parameterized click on ${step.value}`;
|
|
246
|
+
codeLines.push(escapeNonPrintables(comment));
|
|
247
|
+
line = `await context.web.click(elements["${elementIdentifier}"], _params, null, this);`;
|
|
248
|
+
}
|
|
197
249
|
codeLines.push(line);
|
|
198
250
|
break;
|
|
199
251
|
case Types.SET_DATE_TIME:
|
|
@@ -286,6 +338,30 @@ const _generateCodeFromCommand = (step, elements, userData) => {
|
|
|
286
338
|
codeLines.push(line);
|
|
287
339
|
break;
|
|
288
340
|
}
|
|
341
|
+
case Types.EXTRACT_PROPERTY: {
|
|
342
|
+
property = escapeNonPrintables(step.parameters[0]);
|
|
343
|
+
variable = escapeNonPrintables(step.parameters[1]);
|
|
344
|
+
input = `"${variable}"`;
|
|
345
|
+
if (step.dataSource === "userData" || step.dataSource === "parameters") {
|
|
346
|
+
input = "_" + step.dataKey;
|
|
347
|
+
}
|
|
348
|
+
let options = "null";
|
|
349
|
+
if (step.regex !== "") {
|
|
350
|
+
options = `{regex: ${JSON.stringify(step.regex)},trimSpaces: ${step.trimSpaces}}`;
|
|
351
|
+
} else if (step.trimSpaces) {
|
|
352
|
+
options = `{trimSpaces: ${step.trimSpaces}}`;
|
|
353
|
+
} else {
|
|
354
|
+
options = "null";
|
|
355
|
+
}
|
|
356
|
+
line = `await context.web.extractProperty(elements["${elementIdentifier}"], "${property}", ${input}, _params,${options}, this);`;
|
|
357
|
+
|
|
358
|
+
if (element_name) {
|
|
359
|
+
comment = `// Extract property ${property} from ${element_name} to ${variable}`;
|
|
360
|
+
codeLines.push(escapeNonPrintables(comment));
|
|
361
|
+
}
|
|
362
|
+
codeLines.push(line);
|
|
363
|
+
break;
|
|
364
|
+
}
|
|
289
365
|
case Types.VERIFY_PAGE_SNAPSHOT:
|
|
290
366
|
comment = step.nestFrmLoc
|
|
291
367
|
? `// Verify page snapshot ${step.parameters[0]} in the frame ${step.selectors.iframe_src}`
|
|
@@ -507,15 +583,39 @@ const _generateCodeFromCommand = (step, elements, userData) => {
|
|
|
507
583
|
line = `await context.web.clickType( elements["${elementIdentifier}"] ,"${step.key}",null, _params, {"press":true}, this);`;
|
|
508
584
|
codeLines.push(line);
|
|
509
585
|
break;
|
|
510
|
-
case
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
586
|
+
case Types.CONTEXT_CLICK:
|
|
587
|
+
switch (step.count) {
|
|
588
|
+
case 1:
|
|
589
|
+
comment = `// Click on ${elementIdentifier ? elementIdentifier : step.label} in the context of ${escapeNonPrintables(step.value)}`;
|
|
590
|
+
codeLines.push(escapeNonPrintables(comment));
|
|
591
|
+
input = `"${escapeNonPrintables(step.value.replaceAll('"', '\\"'))}"`;
|
|
592
|
+
if (step.dataSource === "userData" || step.dataSource === "parameters") {
|
|
593
|
+
input = "_" + step.dataKey;
|
|
594
|
+
}
|
|
595
|
+
line = `await context.web.click(elements["${elementIdentifier}"], _params, {"context": ${input}}, this);`;
|
|
596
|
+
codeLines.push(line);
|
|
597
|
+
break;
|
|
598
|
+
case 2:
|
|
599
|
+
comment = `// Double click on ${elementIdentifier ? elementIdentifier : step.label} in the context of ${escapeNonPrintables(step.value)}`;
|
|
600
|
+
codeLines.push(escapeNonPrintables(comment));
|
|
601
|
+
input = `"${escapeNonPrintables(step.value.replaceAll('"', '\\"'))}"`;
|
|
602
|
+
if (step.dataSource === "userData" || step.dataSource === "parameters") {
|
|
603
|
+
input = "_" + step.dataKey;
|
|
604
|
+
}
|
|
605
|
+
line = `await context.web.click(elements["${elementIdentifier}"], _params, {"context": ${input}, clickCount:2}, this);`;
|
|
606
|
+
codeLines.push(line);
|
|
607
|
+
break;
|
|
608
|
+
default:
|
|
609
|
+
comment = `// Click on ${elementIdentifier ? elementIdentifier : step.label} in the context of ${escapeNonPrintables(step.value)}`;
|
|
610
|
+
codeLines.push(escapeNonPrintables(comment));
|
|
611
|
+
input = `"${escapeNonPrintables(step.value.replaceAll('"', '\\"'))}"`;
|
|
612
|
+
if (step.dataSource === "userData" || step.dataSource === "parameters") {
|
|
613
|
+
input = "_" + step.dataKey;
|
|
614
|
+
}
|
|
615
|
+
line = `await context.web.click(elements["${elementIdentifier}"], _params, {"context": ${input}}, this);`;
|
|
616
|
+
codeLines.push(line);
|
|
617
|
+
break;
|
|
516
618
|
}
|
|
517
|
-
line = `await context.web.click(elements["${elementIdentifier}"], _params, {"context": ${input}}, this);`;
|
|
518
|
-
codeLines.push(line);
|
|
519
619
|
break;
|
|
520
620
|
case "popup_close":
|
|
521
621
|
break;
|
|
@@ -541,6 +641,23 @@ const _generateCodeFromCommand = (step, elements, userData) => {
|
|
|
541
641
|
codeLines.push(line);
|
|
542
642
|
break;
|
|
543
643
|
}
|
|
644
|
+
case Types.VERIFY_PROPERTY: {
|
|
645
|
+
line = `await context.web.verifyProperty(elements["${elementIdentifier}"], `;
|
|
646
|
+
input = "_param_0";
|
|
647
|
+
if (step.dataSource === "userData" || step.dataSource === "parameters") {
|
|
648
|
+
input = "_" + step.dataKey;
|
|
649
|
+
}
|
|
650
|
+
line += `"${step.parameters[0]}", ${input}, _params, null, this);`;
|
|
651
|
+
codeLines.push(line);
|
|
652
|
+
break;
|
|
653
|
+
}
|
|
654
|
+
case Types.CONDITIONAL_WAIT: {
|
|
655
|
+
line = `await context.web.conditionalWait(elements["${elementIdentifier}"], `;
|
|
656
|
+
input = "_param_0";
|
|
657
|
+
line += `"${step.parameters[1]}", ${input}, _params, null, this);`;
|
|
658
|
+
codeLines.push(line);
|
|
659
|
+
break;
|
|
660
|
+
}
|
|
544
661
|
case Types.SET_INPUT_FILES: {
|
|
545
662
|
line = `await context.web.setInputFiles(elements["${elementIdentifier}"], `;
|
|
546
663
|
let files = step.parameters[0];
|
|
@@ -566,6 +683,22 @@ const _generateCodeFromCommand = (step, elements, userData) => {
|
|
|
566
683
|
return { codeLines, elements: elementsChanged ? elements : null, elementIdentifier, allStrategyLocators };
|
|
567
684
|
};
|
|
568
685
|
|
|
686
|
+
/**
|
|
687
|
+
* Generates a report command based on the given position.
|
|
688
|
+
* @param {"start"|"end"} position
|
|
689
|
+
*/
|
|
690
|
+
const generateReportCommand = (position, cmdId) => {
|
|
691
|
+
const codeLines = [];
|
|
692
|
+
if (position === "start") {
|
|
693
|
+
const line = `await context.web.addCommandToReport("${cmdId}", "PASSED", '{"status":"start","cmdId":"${cmdId}"}', {type:"cmdReport"},this);`;
|
|
694
|
+
codeLines.push(line);
|
|
695
|
+
} else if (position === "end") {
|
|
696
|
+
const line = `await context.web.addCommandToReport("${cmdId}", "PASSED", '{"status":"end","cmdId":"${cmdId}"}', {type:"cmdReport"},this);`;
|
|
697
|
+
codeLines.push(line);
|
|
698
|
+
}
|
|
699
|
+
return codeLines;
|
|
700
|
+
};
|
|
701
|
+
|
|
569
702
|
const generateCode = (recording, codePage, userData, projectDir, methodName) => {
|
|
570
703
|
const stepsDefinitions = new StepsDefinitions(projectDir);
|
|
571
704
|
stepsDefinitions.load(false);
|
|
@@ -606,12 +739,16 @@ const generateCode = (recording, codePage, userData, projectDir, methodName) =>
|
|
|
606
739
|
if (recordingStep.type === Types.COMPLETE) {
|
|
607
740
|
return;
|
|
608
741
|
}
|
|
609
|
-
|
|
610
|
-
//
|
|
611
|
-
//
|
|
742
|
+
|
|
743
|
+
// if (process.env.TEMP_RUN === "true") {
|
|
744
|
+
// codeLines.push(...generateReportCommand("start", recordingStep.cmdId));
|
|
612
745
|
// }
|
|
613
746
|
const result = _generateCodeFromCommand(recordingStep, elements, userData);
|
|
747
|
+
|
|
614
748
|
codeLines.push(...result.codeLines);
|
|
749
|
+
// if (process.env.TEMP_RUN === "true") {
|
|
750
|
+
// codeLines.push(...generateReportCommand("end", recordingStep.cmdId));
|
|
751
|
+
// }
|
|
615
752
|
if (result.elements) {
|
|
616
753
|
elements = result.elements;
|
|
617
754
|
}
|
|
@@ -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
|
-
import
|
|
10
|
+
import { generateTestData } from "./feature_data.js";
|
|
9
11
|
class DataTable {
|
|
10
12
|
constructor(dataTableDocument) {
|
|
11
13
|
if (!dataTableDocument) {
|
|
@@ -372,6 +374,11 @@ class Examples {
|
|
|
372
374
|
return null;
|
|
373
375
|
}
|
|
374
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
|
+
|
|
375
382
|
return unEscapeNonPrintables(value); // While editing in recorder, we need to remove backslashes
|
|
376
383
|
}
|
|
377
384
|
}
|
|
@@ -449,29 +456,7 @@ class Scenario {
|
|
|
449
456
|
return this.scenarioText;
|
|
450
457
|
}
|
|
451
458
|
}
|
|
452
|
-
const scenarioResolution = async (featureFilePath) => {
|
|
453
|
-
if (!fs.existsSync(featureFilePath)) {
|
|
454
|
-
throw new Error(`Feature file ${featureFilePath} does not exist`);
|
|
455
|
-
}
|
|
456
|
-
const newId = IdGenerator.uuid();
|
|
457
|
-
const builder = new AstBuilder(newId);
|
|
458
|
-
const matcher = new GherkinClassicTokenMatcher();
|
|
459
|
-
|
|
460
|
-
const parser = new Parser(builder, matcher);
|
|
461
|
-
|
|
462
|
-
// normalize the path to start with featuers/ if it's not cut the featureFielPath to only the part after features/
|
|
463
|
-
let uri = featureFilePath.startsWith("features/")
|
|
464
|
-
? featureFilePath
|
|
465
|
-
: "features/" + featureFilePath.split("features/")[1];
|
|
466
|
-
|
|
467
|
-
const featureFileContent = fs.readFileSync(featureFilePath, "utf8");
|
|
468
|
-
const gherkinDocument = parser.parse(featureFileContent, newId);
|
|
469
459
|
|
|
470
|
-
const feature = new Feature(gherkinDocument, featureFileContent);
|
|
471
|
-
//const scenario = feature.getScenario(scenarioName);
|
|
472
|
-
//return scenario;
|
|
473
|
-
return feature;
|
|
474
|
-
};
|
|
475
460
|
const getDocumentAndPickel = (featureName, featureContent, scenarioName) => {
|
|
476
461
|
const newId = IdGenerator.uuid();
|
|
477
462
|
const builder = new AstBuilder(newId);
|
|
@@ -484,12 +469,85 @@ const getDocumentAndPickel = (featureName, featureContent, scenarioName) => {
|
|
|
484
469
|
|
|
485
470
|
const gherkinDocument = parser.parse(featureContent, newId);
|
|
486
471
|
const pickles = compile(gherkinDocument, uri, newId);
|
|
487
|
-
|
|
488
472
|
// Step 3: Find the specific scenario Pickle
|
|
489
473
|
const pickle = pickles.find((pickle) => {
|
|
490
474
|
return pickle.name === scenarioName;
|
|
491
475
|
});
|
|
492
476
|
return { gherkinDocument, pickle };
|
|
493
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
|
+
};
|
|
494
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
|
+
};
|
|
495
553
|
export { Feature, Scenario, Step, DataTable, Examples, scenarioResolution, getDocumentAndPickel };
|
|
@@ -523,86 +523,94 @@ class StepsDefinitions {
|
|
|
523
523
|
// }
|
|
524
524
|
// );
|
|
525
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
|
+
// };
|
|
526
532
|
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
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
|
+
// // });
|
|
536
553
|
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
);
|
|
550
|
-
// const files = glob.sync(path.join(tempFolderPath, "step_definitions", "**", "*.mjs"));
|
|
551
|
-
// console.log("Files found:", files);
|
|
552
|
-
const support = await loadSupport(runConfiguration, { cwd: process.cwd(), env: environment });
|
|
553
|
-
// console.log("found ", support.stepDefinitions.length, "step definitions");
|
|
554
|
-
// support.stepDefinitions.map((step) => {
|
|
555
|
-
// console.log("step", step.pattern);
|
|
556
|
-
// });
|
|
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
|
+
// // }
|
|
557
566
|
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
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
|
+
// }
|
|
563
604
|
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
errorMesssage = testStepResult.message;
|
|
574
|
-
if (info) {
|
|
575
|
-
errInfo = info;
|
|
576
|
-
}
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
|
-
if (testStepResult.status === "UNDEFINED") {
|
|
580
|
-
if (!errorMesssage) {
|
|
581
|
-
errorMesssage = `step ${JSON.stringify(stepText)} is ${testStepResult.status}`;
|
|
582
|
-
if (info) {
|
|
583
|
-
errInfo = info;
|
|
584
|
-
}
|
|
585
|
-
}
|
|
586
|
-
}
|
|
587
|
-
}
|
|
588
|
-
if (message.attachment) {
|
|
589
|
-
const attachment = message.attachment;
|
|
590
|
-
if (attachment.mediaType === "application/json" && attachment.body) {
|
|
591
|
-
const body = JSON.parse(attachment.body);
|
|
592
|
-
info = body.info;
|
|
593
|
-
}
|
|
594
|
-
}
|
|
595
|
-
});
|
|
596
|
-
if (errorMesssage) {
|
|
597
|
-
const bvtError = new Error(errorMesssage);
|
|
598
|
-
Object.assign(bvtError, { info: errInfo });
|
|
599
|
-
throw bvtError;
|
|
600
|
-
}
|
|
601
|
-
} catch (error) {
|
|
602
|
-
console.error("Error running cucumber-js", error);
|
|
603
|
-
throw error;
|
|
604
|
-
}
|
|
605
|
-
};
|
|
605
|
+
// return {
|
|
606
|
+
// result,
|
|
607
|
+
// info,
|
|
608
|
+
// };
|
|
609
|
+
// } catch (error) {
|
|
610
|
+
// console.error("Error running cucumber-js", error);
|
|
611
|
+
// throw error;
|
|
612
|
+
// }
|
|
613
|
+
// };
|
|
606
614
|
}
|
|
607
615
|
function findFilesWithExtension(folderPath, extension, rootFolder = null) {
|
|
608
616
|
let result = [];
|
|
@@ -267,7 +267,7 @@ class LocalAgent {
|
|
|
267
267
|
null,
|
|
268
268
|
true,
|
|
269
269
|
null,
|
|
270
|
-
|
|
270
|
+
-1,
|
|
271
271
|
null,
|
|
272
272
|
initScript
|
|
273
273
|
);
|
|
@@ -655,6 +655,7 @@ class LocalAgent {
|
|
|
655
655
|
|
|
656
656
|
let page = agent.project.getPage(agent.pageName, true);
|
|
657
657
|
const generateCodeResult = generateCode(recording, page, userData, agent.project.rootFolder, methodName);
|
|
658
|
+
|
|
658
659
|
if (generateCodeResult.simple === true) {
|
|
659
660
|
agent.sendDone("generateStepCode", null);
|
|
660
661
|
break;
|
|
@@ -707,6 +708,9 @@ class LocalAgent {
|
|
|
707
708
|
cucumberStep: agent.cucumberStep,
|
|
708
709
|
},
|
|
709
710
|
});
|
|
711
|
+
if (generateCodeResult.locatorsMetadata) {
|
|
712
|
+
page.addLocatorsMetadata(generateCodeResult.locatorsMetadata);
|
|
713
|
+
}
|
|
710
714
|
await page.save();
|
|
711
715
|
agent.scenarioReport.updateLastStep({
|
|
712
716
|
recording: command.data.recording,
|
|
@@ -967,7 +971,7 @@ class LocalAgent {
|
|
|
967
971
|
async createNewStepLocal(
|
|
968
972
|
featureName,
|
|
969
973
|
cucumberStep,
|
|
970
|
-
|
|
974
|
+
comments,
|
|
971
975
|
userData,
|
|
972
976
|
firstStep,
|
|
973
977
|
previousTasks,
|
|
@@ -1083,7 +1087,7 @@ class LocalAgent {
|
|
|
1083
1087
|
previousTasks,
|
|
1084
1088
|
scenarioDocument,
|
|
1085
1089
|
scenarioStepIndex,
|
|
1086
|
-
comments
|
|
1090
|
+
comments,
|
|
1087
1091
|
featureFileText,
|
|
1088
1092
|
recover,
|
|
1089
1093
|
dumpConfig: this.dumpConfig,
|