k6-cucumber-steps 1.0.40 โ 1.1.1
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/.vscode/settings.json +2 -1
- package/bin/k6-cucumber-steps.js +47 -4
- package/cucumber.js +6 -6
- package/lib/helpers/buildK6Script.js +35 -24
- package/lib/utils/k6Runner.js +33 -36
- package/package.json +1 -1
- package/scripts/generateHtmlReports.js +53 -0
- package/scripts/linkReports.js +60 -0
- package/step_definitions/load_test_steps.js +43 -17
- package/step_definitions/world.js +24 -1
- package/.env.example +0 -17
- package/generateReport.js +0 -24
package/.vscode/settings.json
CHANGED
package/bin/k6-cucumber-steps.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
2
|
+
//bin/k6-cucumber-steps.js
|
|
3
3
|
const path = require("path");
|
|
4
4
|
const fs = require("fs");
|
|
5
5
|
const { spawn } = require("child_process");
|
|
6
6
|
const yargs = require("yargs");
|
|
7
7
|
require("dotenv").config();
|
|
8
|
+
const { generateHtmlReports } = require("../scripts/generateHtmlReports");
|
|
9
|
+
const { linkReports } = require("../scripts/linkReports");
|
|
8
10
|
|
|
9
11
|
console.log(`
|
|
10
12
|
-----------------------------------------
|
|
@@ -95,9 +97,32 @@ if (configOptions.require && Array.isArray(configOptions.require)) {
|
|
|
95
97
|
|
|
96
98
|
// Determine base report name
|
|
97
99
|
const reportsDir = path.join(process.cwd(), "reports");
|
|
100
|
+
// Clean and prepare reports directory
|
|
101
|
+
const cleanReportsDir = () => {
|
|
102
|
+
if (fs.existsSync(reportsDir)) {
|
|
103
|
+
try {
|
|
104
|
+
fs.rmSync(reportsDir, { recursive: true, force: true });
|
|
105
|
+
console.log("๐งน Cleaned existing reports directory.");
|
|
106
|
+
} catch (err) {
|
|
107
|
+
console.error("โ Failed to clean reports directory:", err.message);
|
|
108
|
+
process.exit(1);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
try {
|
|
113
|
+
fs.mkdirSync(reportsDir, { recursive: true });
|
|
114
|
+
console.log("๐ Created fresh reports directory.");
|
|
115
|
+
} catch (err) {
|
|
116
|
+
console.error("โ Failed to create reports directory:", err.message);
|
|
117
|
+
process.exit(1);
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
cleanReportsDir();
|
|
122
|
+
|
|
98
123
|
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
99
124
|
|
|
100
|
-
let baseReportName = "load-
|
|
125
|
+
let baseReportName = "load-report";
|
|
101
126
|
if (featureFiles.length === 1) {
|
|
102
127
|
const nameFromFeature = path.basename(featureFiles[0], ".feature");
|
|
103
128
|
baseReportName = nameFromFeature || baseReportName;
|
|
@@ -145,12 +170,30 @@ const cucumberProcess = spawn("npx", cucumberArgs, {
|
|
|
145
170
|
});
|
|
146
171
|
|
|
147
172
|
cucumberProcess.on("close", (code) => {
|
|
148
|
-
console.log(`\n-----------------------------------------`);
|
|
149
173
|
if (code === 0) {
|
|
174
|
+
console.log("-----------------------------------------");
|
|
150
175
|
console.log("โ
k6-cucumber-steps execution completed successfully.");
|
|
176
|
+
|
|
177
|
+
generateHtmlReports(); // Cucumber HTML
|
|
178
|
+
console.log(
|
|
179
|
+
"๐ Cucumber HTML report reports/cucumber-report.html generated successfully ๐"
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
const k6ReportPath = detectMostRecentK6Report(); // Add this helper function
|
|
183
|
+
if (k6ReportPath) {
|
|
184
|
+
console.log(
|
|
185
|
+
`๐ K6 HTML report ${k6ReportPath} generated successfully ๐`
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
linkReports(); // Link both reports
|
|
190
|
+
|
|
191
|
+
console.log("-----------------------------------------");
|
|
151
192
|
} else {
|
|
193
|
+
console.error("-----------------------------------------");
|
|
152
194
|
console.error("โ k6-cucumber-steps execution failed.");
|
|
195
|
+
console.error("-----------------------------------------");
|
|
153
196
|
}
|
|
154
|
-
|
|
197
|
+
|
|
155
198
|
process.exit(code);
|
|
156
199
|
});
|
package/cucumber.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
module.exports = {
|
|
2
2
|
default: {
|
|
3
|
-
require: ["./step_definitions/**/*.js"],
|
|
3
|
+
// require: ["./step_definitions/**/*.js"],
|
|
4
4
|
format: [
|
|
5
5
|
"summary",
|
|
6
|
-
"json:./
|
|
7
|
-
"html:./
|
|
6
|
+
"json:./reports/load-report.json",
|
|
7
|
+
"html:./reports/cucumber-report.html",
|
|
8
8
|
],
|
|
9
|
-
paths: ["./
|
|
10
|
-
tags:
|
|
9
|
+
paths: ["./features/bsp.feature"],
|
|
10
|
+
tags: "@load",
|
|
11
11
|
worldParameters: {
|
|
12
|
-
payloadPath: "
|
|
12
|
+
payloadPath: "src/examples/payloads",
|
|
13
13
|
},
|
|
14
14
|
overwrite: true,
|
|
15
15
|
reporter: true,
|
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
module.exports = function buildK6Script(config) {
|
|
2
2
|
const { method, endpoints, endpoint, body, headers, options } = config;
|
|
3
3
|
|
|
4
|
-
// Ensure at least one of `endpoints` or `endpoint` is defined
|
|
5
4
|
if (!endpoints?.length && !endpoint) {
|
|
6
5
|
throw new Error(
|
|
7
6
|
'Either "endpoints" or "endpoint" must be defined in the configuration.'
|
|
8
7
|
);
|
|
9
8
|
}
|
|
10
9
|
|
|
11
|
-
// Prefer API_BASE_URL, fallback to BASE_URL
|
|
12
10
|
const BASE_URL = process.env.API_BASE_URL || process.env.BASE_URL;
|
|
13
11
|
if (!BASE_URL) {
|
|
14
12
|
throw new Error(
|
|
@@ -16,53 +14,66 @@ module.exports = function buildK6Script(config) {
|
|
|
16
14
|
);
|
|
17
15
|
}
|
|
18
16
|
|
|
19
|
-
// Resolve relative endpoints by prepending BASE_URL
|
|
20
17
|
const resolveEndpoint = (url) => {
|
|
21
18
|
return url.startsWith("/") ? `${BASE_URL}${url}` : url;
|
|
22
19
|
};
|
|
23
20
|
|
|
21
|
+
const stringifiedHeaders = JSON.stringify(headers, null, 2);
|
|
22
|
+
const stringifiedBody = JSON.stringify(body, null, 2);
|
|
23
|
+
|
|
24
24
|
return `
|
|
25
25
|
import http from 'k6/http';
|
|
26
26
|
import { check } from 'k6';
|
|
27
|
+
import { htmlReport } from "https://raw.githubusercontent.com/benc-uk/k6-reporter/main/dist/bundle.js";
|
|
28
|
+
import { textSummary } from "https://jslib.k6.io/k6-summary/0.0.1/index.js";
|
|
27
29
|
|
|
28
30
|
export const options = ${JSON.stringify(options, null, 2)};
|
|
29
31
|
|
|
30
32
|
export default function () {
|
|
33
|
+
const headers = ${stringifiedHeaders};
|
|
34
|
+
const body = ${stringifiedBody};
|
|
35
|
+
|
|
31
36
|
${
|
|
32
37
|
endpoints?.length
|
|
33
38
|
? endpoints
|
|
34
39
|
.map(
|
|
35
40
|
(url, i) => `
|
|
36
|
-
|
|
37
|
-
|
|
41
|
+
const resolvedUrl${i} = "${resolveEndpoint(url)}";
|
|
42
|
+
const res${i} = http.request("${method}", resolvedUrl${i}, ${
|
|
38
43
|
method === "GET" || method === "DELETE"
|
|
39
44
|
? "null"
|
|
40
|
-
: JSON.stringify(body)
|
|
41
|
-
}, {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
"status is 2xx": (r) => r.status >= 200 && r.status < 300
|
|
47
|
-
});
|
|
45
|
+
: "JSON.stringify(body)"
|
|
46
|
+
}, { headers });
|
|
47
|
+
console.log(\`Response Body for \${resolvedUrl${i}}: \${res${i}.body}\`);
|
|
48
|
+
check(res${i}, {
|
|
49
|
+
"status is 2xx": (r) => r.status >= 200 && r.status < 300
|
|
50
|
+
});
|
|
48
51
|
`
|
|
49
52
|
)
|
|
50
53
|
.join("\n")
|
|
51
54
|
: `
|
|
52
|
-
|
|
53
|
-
|
|
55
|
+
const resolvedUrl = "${resolveEndpoint(endpoint)}";
|
|
56
|
+
const res = http.request("${method}", resolvedUrl, ${
|
|
54
57
|
method === "GET" || method === "DELETE"
|
|
55
58
|
? "null"
|
|
56
|
-
: JSON.stringify(body)
|
|
57
|
-
}, {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
"status is 2xx": (r) => r.status >= 200 && r.status < 300
|
|
63
|
-
});
|
|
59
|
+
: "JSON.stringify(body)"
|
|
60
|
+
}, { headers });
|
|
61
|
+
console.log(\`Response Body for \${resolvedUrl}: \${res.body}\`);
|
|
62
|
+
check(res, {
|
|
63
|
+
"status is 2xx": (r) => r.status >= 200 && r.status < 300
|
|
64
|
+
});
|
|
64
65
|
`
|
|
65
66
|
}
|
|
66
67
|
}
|
|
67
|
-
|
|
68
|
+
|
|
69
|
+
export function handleSummary(data) {
|
|
70
|
+
const outputDir = __ENV.REPORT_OUTPUT_DIR || "reports";
|
|
71
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
72
|
+
const filename = \`\${outputDir}/k6-report-\${timestamp}.html\`;
|
|
73
|
+
return {
|
|
74
|
+
[filename]: htmlReport(data),
|
|
75
|
+
stdout: textSummary(data, { indent: " ", enableColors: true }),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
`;
|
|
68
79
|
};
|
package/lib/utils/k6Runner.js
CHANGED
|
@@ -60,46 +60,43 @@ const runK6Script = async (scriptPath, overwrite = false) => {
|
|
|
60
60
|
const customLogo = `${chalkGreen} with @qaPaschalE's ${chalkYellow}k6-cucumber-steps v${packageJson.version}${resetColor}`;
|
|
61
61
|
|
|
62
62
|
return new Promise(async (resolve, reject) => {
|
|
63
|
-
exec(
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
// Split the k6 logo lines
|
|
67
|
-
const logoLines = stdout.split("\n");
|
|
63
|
+
exec(`k6 run "${scriptPath}"`, async (error, stdout, stderr) => {
|
|
64
|
+
// Split the k6 logo lines
|
|
65
|
+
const logoLines = stdout.split("\n");
|
|
68
66
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
}
|
|
77
|
-
modifiedStdout += "\n";
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// Handle errors and cleanup
|
|
81
|
-
if (error) {
|
|
82
|
-
console.error("k6 error:", error);
|
|
83
|
-
console.error("k6 stdout:", modifiedStdout);
|
|
84
|
-
await delay(3000); // Wait for 3 seconds
|
|
85
|
-
console.error("k6 stderr:", stderr);
|
|
86
|
-
reject(new Error(`k6 test execution failed: ${error.message}`));
|
|
87
|
-
} else if (stderr) {
|
|
88
|
-
console.log("k6 stdout:", modifiedStdout);
|
|
89
|
-
await delay(3000); // Wait for 3 seconds
|
|
90
|
-
resolve(stdout);
|
|
91
|
-
} else {
|
|
92
|
-
console.log("k6 stdout:", modifiedStdout);
|
|
93
|
-
await delay(3000); // Wait for 3 seconds
|
|
94
|
-
resolve(stdout);
|
|
67
|
+
// Insert the custom logo under "Grafana" (on the third line)
|
|
68
|
+
let modifiedStdout = "";
|
|
69
|
+
for (let i = 0; i < logoLines.length; i++) {
|
|
70
|
+
modifiedStdout += logoLines[i];
|
|
71
|
+
if (i === 5) {
|
|
72
|
+
// Target the third line (index 2) of the k6 logo
|
|
73
|
+
modifiedStdout += ` ${customLogo}\n`;
|
|
95
74
|
}
|
|
75
|
+
modifiedStdout += "\n";
|
|
76
|
+
}
|
|
96
77
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
);
|
|
78
|
+
// Handle errors and cleanup
|
|
79
|
+
if (error) {
|
|
80
|
+
console.error("k6 error:", error);
|
|
81
|
+
console.error("k6 stdout:", modifiedStdout);
|
|
82
|
+
await delay(3000); // Wait for 3 seconds
|
|
83
|
+
console.error("k6 stderr:", stderr);
|
|
84
|
+
reject(new Error(`k6 test execution failed: ${error.message}`));
|
|
85
|
+
} else if (stderr) {
|
|
86
|
+
console.log("k6 stdout:", modifiedStdout);
|
|
87
|
+
await delay(3000); // Wait for 3 seconds
|
|
88
|
+
resolve(stdout);
|
|
89
|
+
} else {
|
|
90
|
+
console.log("k6 stdout:", modifiedStdout);
|
|
91
|
+
await delay(3000); // Wait for 3 seconds
|
|
92
|
+
resolve(stdout);
|
|
101
93
|
}
|
|
102
|
-
|
|
94
|
+
|
|
95
|
+
// Clean up the temporary script file
|
|
96
|
+
fs.unlink(scriptPath).catch((err) =>
|
|
97
|
+
console.error("Error deleting temporary k6 script:", err)
|
|
98
|
+
);
|
|
99
|
+
});
|
|
103
100
|
});
|
|
104
101
|
};
|
|
105
102
|
|
package/package.json
CHANGED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const reporter = require("cucumber-html-reporter");
|
|
4
|
+
|
|
5
|
+
function getJsonReportPath() {
|
|
6
|
+
// 1. Check for cucumber.js or custom config
|
|
7
|
+
const configFile = path.resolve("cucumber.js");
|
|
8
|
+
if (fs.existsSync(configFile)) {
|
|
9
|
+
const configText = fs.readFileSync(configFile, "utf-8");
|
|
10
|
+
|
|
11
|
+
const match = configText.match(/json:(.*?\.json)/);
|
|
12
|
+
if (match && match[1]) {
|
|
13
|
+
const reportPath = match[1].trim().replace(/['"`]/g, "");
|
|
14
|
+
console.log(`๐ Found report path in cucumber.js: ${reportPath}`);
|
|
15
|
+
return reportPath;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// 2. Check environment variable
|
|
20
|
+
if (process.env.CUCUMBER_JSON) {
|
|
21
|
+
console.log(
|
|
22
|
+
`๐ Using report path from CUCUMBER_JSON: ${process.env.CUCUMBER_JSON}`
|
|
23
|
+
);
|
|
24
|
+
return process.env.CUCUMBER_JSON;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// 3. Fallback to default
|
|
28
|
+
console.log("๐ Using default report path: reports/load-report.json");
|
|
29
|
+
return "reports/load-report.json";
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function generateHtmlReports() {
|
|
33
|
+
const jsonReportPath = getJsonReportPath();
|
|
34
|
+
|
|
35
|
+
if (!fs.existsSync(jsonReportPath)) {
|
|
36
|
+
console.warn(`โ ๏ธ Cucumber JSON report not found at: ${jsonReportPath}`);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const reportDir = path.dirname(jsonReportPath);
|
|
41
|
+
|
|
42
|
+
reporter.generate({
|
|
43
|
+
theme: "bootstrap",
|
|
44
|
+
jsonFile: jsonReportPath,
|
|
45
|
+
output: path.join(reportDir, "cucumber-report.html"),
|
|
46
|
+
reportSuiteAsScenarios: true,
|
|
47
|
+
launchReport: false,
|
|
48
|
+
name: "k6-cucumber-steps Performance Report",
|
|
49
|
+
brandTitle: "๐ Performance Test Summary",
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
module.exports = { generateHtmlReports };
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
|
|
4
|
+
const reportsDir = path.resolve("reports");
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Adds internal cross-links between reports.
|
|
8
|
+
*/
|
|
9
|
+
function addLinksToReport(targetFile, otherFiles) {
|
|
10
|
+
const content = fs.readFileSync(targetFile, "utf-8");
|
|
11
|
+
|
|
12
|
+
// Inject the Cucumber Report tab before the Request Metrics tab
|
|
13
|
+
const cucumberTab = `
|
|
14
|
+
<input type="radio" name="tabs" id="tabcucumber">
|
|
15
|
+
<label for="tabcucumber"><i class="fas fa-file-alt"></i> Cucumber Report</label>
|
|
16
|
+
<div class="tab">
|
|
17
|
+
<iframe src="cucumber-report.html" style="width:100%; height:600px; border:none;"></iframe>
|
|
18
|
+
</div>
|
|
19
|
+
`;
|
|
20
|
+
|
|
21
|
+
const modifiedContent = content
|
|
22
|
+
.replace(
|
|
23
|
+
/<input type="radio" name="tabs" id="tabone"/,
|
|
24
|
+
`${cucumberTab}\n<input type="radio" name="tabs" id="tabone"`
|
|
25
|
+
)
|
|
26
|
+
// Remove standalone links to cucumber-report.html or k6-report.html
|
|
27
|
+
.replace(
|
|
28
|
+
/<div style="padding:10px;margin-top:20px;text-align:center">[\s\S]*?View (Cucumber|k6).*?<\/div>/,
|
|
29
|
+
""
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
fs.writeFileSync(targetFile, modifiedContent, "utf-8");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Auto-link all HTML reports in the reports directory.
|
|
37
|
+
*/
|
|
38
|
+
function linkReports() {
|
|
39
|
+
if (!fs.existsSync(reportsDir)) {
|
|
40
|
+
console.warn("โ ๏ธ No reports directory found.");
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const htmlFiles = fs
|
|
45
|
+
.readdirSync(reportsDir)
|
|
46
|
+
.filter((f) => f.endsWith(".html"))
|
|
47
|
+
.map((f) => path.join(reportsDir, f));
|
|
48
|
+
|
|
49
|
+
if (htmlFiles.length < 2) {
|
|
50
|
+
console.warn("โ ๏ธ Not enough HTML files to link.");
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
for (const file of htmlFiles) {
|
|
55
|
+
const others = htmlFiles.filter((f) => f !== file);
|
|
56
|
+
addLinksToReport(file, others);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
module.exports = { linkReports };
|
|
@@ -35,26 +35,56 @@ Given("I set a k6 script for {word} testing", function (method) {
|
|
|
35
35
|
* When I set to run the k6 script with the following configurations:
|
|
36
36
|
* | virtual_users | duration | http_req_failed | http_req_duration | error_rate | stages |
|
|
37
37
|
* | 10 | 5 | rate<0.05 | p(95)<200 | | |
|
|
38
|
-
* | 50 | 10 |
|
|
38
|
+
* | 50 | 10 | rate<0.01 | | rate<0.01 | [{"target": 10, "duration": "10s"}, {"target": 50, "duration": "30s"}] |
|
|
39
39
|
*/
|
|
40
40
|
When(
|
|
41
41
|
"I set to run the k6 script with the following configurations:",
|
|
42
42
|
function (dataTable) {
|
|
43
|
-
const
|
|
43
|
+
const rawRow = dataTable.hashes()[0];
|
|
44
|
+
const row = {};
|
|
45
|
+
|
|
46
|
+
// Extract example values manually from this.pickle
|
|
47
|
+
const exampleMap = {};
|
|
48
|
+
if (this.pickle && this.pickle.astNodeIds && this.gherkinDocument) {
|
|
49
|
+
const scenario = this.gherkinDocument.feature.children.find((child) => {
|
|
50
|
+
return child.scenario && child.scenario.examples?.length;
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const exampleValues =
|
|
54
|
+
scenario?.scenario?.examples?.[0]?.tableBody?.[0]?.cells?.map(
|
|
55
|
+
(cell) => cell.value
|
|
56
|
+
) || [];
|
|
57
|
+
|
|
58
|
+
const exampleKeys =
|
|
59
|
+
scenario?.scenario?.examples?.[0]?.tableHeader?.cells?.map(
|
|
60
|
+
(cell) => cell.value
|
|
61
|
+
) || [];
|
|
62
|
+
|
|
63
|
+
exampleKeys.forEach((key, idx) => {
|
|
64
|
+
exampleMap[key] = exampleValues[idx];
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
for (const [key, value] of Object.entries(rawRow)) {
|
|
69
|
+
row[key] = value.replace(/<([^>]+)>/g, (_, param) => {
|
|
70
|
+
return exampleMap[param] || value;
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
console.log("๐จ Resolved config row:", row);
|
|
44
75
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
if (value &&
|
|
48
|
-
|
|
76
|
+
const validateThreshold = (value) => {
|
|
77
|
+
const regex = /^[\w{}()<>:]+[<>=]\d+(\.\d+)?$/;
|
|
78
|
+
if (value && !regex.test(value)) {
|
|
79
|
+
throw new Error(`Invalid threshold format: ${value}`);
|
|
49
80
|
}
|
|
50
81
|
};
|
|
51
82
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
83
|
+
validateThreshold(row.http_req_failed);
|
|
84
|
+
validateThreshold(row.http_req_duration);
|
|
85
|
+
validateThreshold(row.error_rate);
|
|
55
86
|
|
|
56
87
|
if (row.stages) {
|
|
57
|
-
// User provided a stages definition (JSON array)
|
|
58
88
|
try {
|
|
59
89
|
this.config.options = {
|
|
60
90
|
stages: JSON.parse(row.stages),
|
|
@@ -63,11 +93,10 @@ When(
|
|
|
63
93
|
http_req_duration: [row.http_req_duration],
|
|
64
94
|
},
|
|
65
95
|
};
|
|
66
|
-
} catch
|
|
96
|
+
} catch {
|
|
67
97
|
throw new Error("Invalid stages JSON format.");
|
|
68
98
|
}
|
|
69
99
|
} else {
|
|
70
|
-
// Default to VUs and duration
|
|
71
100
|
this.config.options = {
|
|
72
101
|
vus: parseInt(row.virtual_users),
|
|
73
102
|
duration: `${row.duration}s`,
|
|
@@ -78,7 +107,7 @@ When(
|
|
|
78
107
|
};
|
|
79
108
|
}
|
|
80
109
|
|
|
81
|
-
if (row.error_rate
|
|
110
|
+
if (row.error_rate) {
|
|
82
111
|
this.config.options.thresholds.error_rate = [row.error_rate];
|
|
83
112
|
}
|
|
84
113
|
}
|
|
@@ -171,14 +200,11 @@ When(
|
|
|
171
200
|
});
|
|
172
201
|
|
|
173
202
|
this.config = {
|
|
203
|
+
...this.config, // โ
Keep previously set options!
|
|
174
204
|
method: methodUpper,
|
|
175
205
|
endpoint,
|
|
176
206
|
body: resolved,
|
|
177
207
|
headers: this.config?.headers || {},
|
|
178
|
-
options: {
|
|
179
|
-
vus: 1,
|
|
180
|
-
iterations: 1,
|
|
181
|
-
},
|
|
182
208
|
};
|
|
183
209
|
|
|
184
210
|
this.lastRequest = {
|
|
@@ -1,16 +1,39 @@
|
|
|
1
1
|
const { setWorldConstructor } = require("@cucumber/cucumber");
|
|
2
2
|
|
|
3
3
|
class CustomWorld {
|
|
4
|
-
constructor({ parameters }) {
|
|
4
|
+
constructor({ parameters, pickle }) {
|
|
5
5
|
this.options = {};
|
|
6
6
|
this.configurations = {};
|
|
7
7
|
this.endpoints = [];
|
|
8
8
|
this.authType = "";
|
|
9
9
|
this.postBody = {};
|
|
10
|
+
this.aliases = {};
|
|
11
|
+
this.lastRequest = null;
|
|
12
|
+
this.lastResponse = null;
|
|
13
|
+
|
|
10
14
|
this.overwrite =
|
|
11
15
|
parameters?.overwrite ||
|
|
12
16
|
process.env.K6_CUCUMBER_OVERWRITE === "true" ||
|
|
13
17
|
false;
|
|
18
|
+
|
|
19
|
+
// โ
Store scenario example values from <placeholders>
|
|
20
|
+
this.parameters = {};
|
|
21
|
+
|
|
22
|
+
if (parameters) {
|
|
23
|
+
this.parameters = { ...parameters };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (pickle?.steps?.length) {
|
|
27
|
+
const matches = pickle.steps.flatMap((step) => {
|
|
28
|
+
return [...(step.text?.matchAll(/<([^>]+)>/g) || [])];
|
|
29
|
+
});
|
|
30
|
+
matches.forEach((match) => {
|
|
31
|
+
const key = match[1];
|
|
32
|
+
if (parameters?.[key]) {
|
|
33
|
+
this.parameters[key] = parameters[key];
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
}
|
|
14
37
|
}
|
|
15
38
|
}
|
|
16
39
|
|
package/.env.example
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
# .env
|
|
2
|
-
TEST_ENVIRONMENT=STAGING
|
|
3
|
-
APP_VERSION=1.0.0
|
|
4
|
-
BROWSER=Chrome 100.0.4896.88
|
|
5
|
-
PLATFORM=Windows 10
|
|
6
|
-
PARALLEL=Scenarios
|
|
7
|
-
EXECUTED=Remote
|
|
8
|
-
|
|
9
|
-
# Base URLs
|
|
10
|
-
API_URL=https://test-api.example.com
|
|
11
|
-
BASE_URL=https://example.com
|
|
12
|
-
AUTH_URL=https://auth.example.com
|
|
13
|
-
PAYMENT_URL=https://payment.example.com
|
|
14
|
-
|
|
15
|
-
# Secret Parameters
|
|
16
|
-
API_KEY=your-secret-api-key-12345
|
|
17
|
-
BEARER_TOKEN=your_bearer_token_here
|
package/generateReport.js
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
const reporter = require("cucumber-html-reporter");
|
|
2
|
-
|
|
3
|
-
// Configuration for the HTML report
|
|
4
|
-
const options = {
|
|
5
|
-
theme: "bootstrap", // You can change the theme to 'simple', 'compact', etc.
|
|
6
|
-
jsonFile: "reports/load-results.json", // Path to the JSON file generated by Cucumber
|
|
7
|
-
output: "reports/report.html", // Path where you want to save the HTML report
|
|
8
|
-
reportSuiteAsScenarios: true, // Group scenarios by their name
|
|
9
|
-
launchReport: true, // Automatically open the report in the browser
|
|
10
|
-
metadata: {
|
|
11
|
-
browser: {
|
|
12
|
-
name: "Chrome" || "unknown",
|
|
13
|
-
version: "89" || "unknown",
|
|
14
|
-
},
|
|
15
|
-
device: "Local test machine",
|
|
16
|
-
platform: {
|
|
17
|
-
name: "MacOS",
|
|
18
|
-
version: "20.04",
|
|
19
|
-
},
|
|
20
|
-
},
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
// Generate the report
|
|
24
|
-
reporter.generate(options);
|