k6-cucumber-steps 1.1.1 → 1.1.2
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/.env +38 -0
- package/bin/k6-cucumber-steps.js +34 -17
- package/cucumber.js +3 -3
- package/lib/helpers/buildK6Script.js +15 -2
- package/package.json +2 -1
- package/reports/cucumber-report.html +50 -0
- package/reports/k6-report-2025-06-03T12-54-18-512Z.html +582 -0
- package/reports/k6-report-2025-06-03T12-54-33-806Z.html +582 -0
- package/reports/load-report.json +283 -0
- package/scripts/linkReports.js +95 -33
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"description": "",
|
|
4
|
+
"elements": [
|
|
5
|
+
{
|
|
6
|
+
"description": "",
|
|
7
|
+
"id": "rate-limit-enforcement-for-/login-and-/bsp;api-allows-up-to-5-requests-in-10-seconds-(under-limit)",
|
|
8
|
+
"keyword": "Scenario",
|
|
9
|
+
"line": 9,
|
|
10
|
+
"name": "API allows up to 5 requests in 10 seconds (under limit)",
|
|
11
|
+
"steps": [
|
|
12
|
+
{
|
|
13
|
+
"arguments": [],
|
|
14
|
+
"keyword": "Given ",
|
|
15
|
+
"line": 5,
|
|
16
|
+
"name": "I login via POST to \"/api/v3/client/api/login\" with payload from \"login.json\"",
|
|
17
|
+
"match": {
|
|
18
|
+
"location": "step_definitions/load_test_steps.js:262"
|
|
19
|
+
},
|
|
20
|
+
"result": {
|
|
21
|
+
"status": "passed",
|
|
22
|
+
"duration": 894569875
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"arguments": [],
|
|
27
|
+
"keyword": "Then ",
|
|
28
|
+
"line": 6,
|
|
29
|
+
"name": "I store the value at \"data.token\" as alias \"auth_token\"",
|
|
30
|
+
"match": {
|
|
31
|
+
"location": "step_definitions/load_test_steps.js:237"
|
|
32
|
+
},
|
|
33
|
+
"result": {
|
|
34
|
+
"status": "passed",
|
|
35
|
+
"duration": 217708
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"arguments": [],
|
|
40
|
+
"keyword": "When ",
|
|
41
|
+
"line": 10,
|
|
42
|
+
"name": "I set a k6 script for POST testing",
|
|
43
|
+
"match": {
|
|
44
|
+
"location": "step_definitions/load_test_steps.js:28"
|
|
45
|
+
},
|
|
46
|
+
"result": {
|
|
47
|
+
"status": "passed",
|
|
48
|
+
"duration": 78040
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"arguments": [
|
|
53
|
+
{
|
|
54
|
+
"rows": [
|
|
55
|
+
{
|
|
56
|
+
"cells": [
|
|
57
|
+
"virtual_users",
|
|
58
|
+
"duration",
|
|
59
|
+
"http_req_failed",
|
|
60
|
+
"http_req_duration"
|
|
61
|
+
]
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"cells": [
|
|
65
|
+
"1",
|
|
66
|
+
"10",
|
|
67
|
+
"rate<0.05",
|
|
68
|
+
"p(95)<2000"
|
|
69
|
+
]
|
|
70
|
+
}
|
|
71
|
+
]
|
|
72
|
+
}
|
|
73
|
+
],
|
|
74
|
+
"keyword": "When ",
|
|
75
|
+
"line": 11,
|
|
76
|
+
"name": "I set to run the k6 script with the following configurations:",
|
|
77
|
+
"match": {
|
|
78
|
+
"location": "step_definitions/load_test_steps.js:40"
|
|
79
|
+
},
|
|
80
|
+
"result": {
|
|
81
|
+
"status": "passed",
|
|
82
|
+
"duration": 458667
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
"arguments": [],
|
|
87
|
+
"keyword": "When ",
|
|
88
|
+
"line": 14,
|
|
89
|
+
"name": "I use JSON payload from \"okra.json\" for POST to \"/api/v3/client/bsp\"",
|
|
90
|
+
"match": {
|
|
91
|
+
"location": "step_definitions/load_test_steps.js:174"
|
|
92
|
+
},
|
|
93
|
+
"result": {
|
|
94
|
+
"status": "passed",
|
|
95
|
+
"duration": 1823208
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
"arguments": [],
|
|
100
|
+
"keyword": "When ",
|
|
101
|
+
"line": 15,
|
|
102
|
+
"name": "I set the authentication type to \"auth_token\"",
|
|
103
|
+
"match": {
|
|
104
|
+
"location": "step_definitions/load_test_steps.js:226"
|
|
105
|
+
},
|
|
106
|
+
"result": {
|
|
107
|
+
"status": "passed",
|
|
108
|
+
"duration": 125207
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
"arguments": [],
|
|
113
|
+
"keyword": "Then ",
|
|
114
|
+
"line": 16,
|
|
115
|
+
"name": "I see the API should handle the POST request successfully",
|
|
116
|
+
"match": {
|
|
117
|
+
"location": "step_definitions/load_test_steps.js:306"
|
|
118
|
+
},
|
|
119
|
+
"result": {
|
|
120
|
+
"status": "failed",
|
|
121
|
+
"duration": 14665569374,
|
|
122
|
+
"error_message": "Error: k6 test execution failed\n at CustomWorld.<anonymous> (/Users/paschal/personal/k6-cucumber-steps/step_definitions/load_test_steps.js:334:13)"
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
],
|
|
126
|
+
"tags": [
|
|
127
|
+
{
|
|
128
|
+
"name": "@rate-limit",
|
|
129
|
+
"line": 1
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
"name": "@within-limit",
|
|
133
|
+
"line": 8
|
|
134
|
+
}
|
|
135
|
+
],
|
|
136
|
+
"type": "scenario"
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
"description": "",
|
|
140
|
+
"id": "rate-limit-enforcement-for-/login-and-/bsp;api-blocks-more-than-5-requests-in-10-seconds-(exceeds-limit)",
|
|
141
|
+
"keyword": "Scenario",
|
|
142
|
+
"line": 19,
|
|
143
|
+
"name": "API blocks more than 5 requests in 10 seconds (exceeds limit)",
|
|
144
|
+
"steps": [
|
|
145
|
+
{
|
|
146
|
+
"arguments": [],
|
|
147
|
+
"keyword": "Given ",
|
|
148
|
+
"line": 5,
|
|
149
|
+
"name": "I login via POST to \"/api/v3/client/api/login\" with payload from \"login.json\"",
|
|
150
|
+
"match": {
|
|
151
|
+
"location": "step_definitions/load_test_steps.js:262"
|
|
152
|
+
},
|
|
153
|
+
"result": {
|
|
154
|
+
"status": "passed",
|
|
155
|
+
"duration": 626931291
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
"arguments": [],
|
|
160
|
+
"keyword": "Then ",
|
|
161
|
+
"line": 6,
|
|
162
|
+
"name": "I store the value at \"data.token\" as alias \"auth_token\"",
|
|
163
|
+
"match": {
|
|
164
|
+
"location": "step_definitions/load_test_steps.js:237"
|
|
165
|
+
},
|
|
166
|
+
"result": {
|
|
167
|
+
"status": "passed",
|
|
168
|
+
"duration": 569790
|
|
169
|
+
}
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
"arguments": [],
|
|
173
|
+
"keyword": "When ",
|
|
174
|
+
"line": 20,
|
|
175
|
+
"name": "I set a k6 script for POST testing",
|
|
176
|
+
"match": {
|
|
177
|
+
"location": "step_definitions/load_test_steps.js:28"
|
|
178
|
+
},
|
|
179
|
+
"result": {
|
|
180
|
+
"status": "passed",
|
|
181
|
+
"duration": 76042
|
|
182
|
+
}
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
"arguments": [
|
|
186
|
+
{
|
|
187
|
+
"rows": [
|
|
188
|
+
{
|
|
189
|
+
"cells": [
|
|
190
|
+
"virtual_users",
|
|
191
|
+
"duration",
|
|
192
|
+
"http_req_failed",
|
|
193
|
+
"http_req_duration"
|
|
194
|
+
]
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
"cells": [
|
|
198
|
+
"6",
|
|
199
|
+
"10",
|
|
200
|
+
"rate>=0.50",
|
|
201
|
+
"p(95)<3000"
|
|
202
|
+
]
|
|
203
|
+
}
|
|
204
|
+
]
|
|
205
|
+
}
|
|
206
|
+
],
|
|
207
|
+
"keyword": "When ",
|
|
208
|
+
"line": 21,
|
|
209
|
+
"name": "I set to run the k6 script with the following configurations:",
|
|
210
|
+
"match": {
|
|
211
|
+
"location": "step_definitions/load_test_steps.js:40"
|
|
212
|
+
},
|
|
213
|
+
"result": {
|
|
214
|
+
"status": "passed",
|
|
215
|
+
"duration": 255083
|
|
216
|
+
}
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
"arguments": [],
|
|
220
|
+
"keyword": "When ",
|
|
221
|
+
"line": 24,
|
|
222
|
+
"name": "I use JSON payload from \"okra.json\" for POST to \"/api/v3/client/bsp\"",
|
|
223
|
+
"match": {
|
|
224
|
+
"location": "step_definitions/load_test_steps.js:174"
|
|
225
|
+
},
|
|
226
|
+
"result": {
|
|
227
|
+
"status": "passed",
|
|
228
|
+
"duration": 1752667
|
|
229
|
+
}
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
"arguments": [],
|
|
233
|
+
"keyword": "When ",
|
|
234
|
+
"line": 25,
|
|
235
|
+
"name": "I set the authentication type to \"auth_token\"",
|
|
236
|
+
"match": {
|
|
237
|
+
"location": "step_definitions/load_test_steps.js:226"
|
|
238
|
+
},
|
|
239
|
+
"result": {
|
|
240
|
+
"status": "passed",
|
|
241
|
+
"duration": 87707
|
|
242
|
+
}
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
"arguments": [],
|
|
246
|
+
"keyword": "Then ",
|
|
247
|
+
"line": 26,
|
|
248
|
+
"name": "I see the API should handle the POST request successfully",
|
|
249
|
+
"match": {
|
|
250
|
+
"location": "step_definitions/load_test_steps.js:306"
|
|
251
|
+
},
|
|
252
|
+
"result": {
|
|
253
|
+
"status": "passed",
|
|
254
|
+
"duration": 14650450625
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
],
|
|
258
|
+
"tags": [
|
|
259
|
+
{
|
|
260
|
+
"name": "@rate-limit",
|
|
261
|
+
"line": 1
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
"name": "@exceed-limit",
|
|
265
|
+
"line": 18
|
|
266
|
+
}
|
|
267
|
+
],
|
|
268
|
+
"type": "scenario"
|
|
269
|
+
}
|
|
270
|
+
],
|
|
271
|
+
"id": "rate-limit-enforcement-for-/login-and-/bsp",
|
|
272
|
+
"line": 2,
|
|
273
|
+
"keyword": "Feature",
|
|
274
|
+
"name": "Rate Limit Enforcement for /login and /bsp",
|
|
275
|
+
"tags": [
|
|
276
|
+
{
|
|
277
|
+
"name": "@rate-limit",
|
|
278
|
+
"line": 1
|
|
279
|
+
}
|
|
280
|
+
],
|
|
281
|
+
"uri": "features/bsp.feature"
|
|
282
|
+
}
|
|
283
|
+
]
|
package/scripts/linkReports.js
CHANGED
|
@@ -1,60 +1,122 @@
|
|
|
1
1
|
const fs = require("fs");
|
|
2
2
|
const path = require("path");
|
|
3
|
+
const { minify } = require("html-minifier-terser");
|
|
3
4
|
|
|
4
5
|
const reportsDir = path.resolve("reports");
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
function findCucumberReportPath() {
|
|
8
|
+
const files = fs.readdirSync(reportsDir);
|
|
9
|
+
return files.find((f) => f.includes("cucumber") && f.endsWith(".html"));
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function extractCucumberBody(filepath) {
|
|
13
|
+
const html = fs.readFileSync(filepath, "utf-8");
|
|
14
|
+
|
|
15
|
+
// Remove <script> tags to avoid JS conflicts
|
|
16
|
+
const withoutScripts = html.replace(/<script[\s\S]*?<\/script>/gi, "");
|
|
17
|
+
|
|
18
|
+
const match = withoutScripts.match(/<body[^>]*>([\s\S]*)<\/body>/i);
|
|
19
|
+
return match
|
|
20
|
+
? match[1]
|
|
21
|
+
: "<p>⚠️ Failed to extract body of Cucumber report.</p>";
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function extractK6ReportBody(file) {
|
|
25
|
+
const html = fs.readFileSync(file, "utf-8");
|
|
26
|
+
const match = html.match(/<body[^>]*>([\s\S]*)<\/body>/i);
|
|
27
|
+
return match ? match[1] : "";
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function buildCombinedHtml({ title, k6Bodies, cucumberBody }) {
|
|
31
|
+
const tabs = k6Bodies
|
|
32
|
+
.map(
|
|
33
|
+
(_, i) => `
|
|
34
|
+
<input type="radio" name="tabs" id="tabk6${i}" ${i === 0 ? "checked" : ""}>
|
|
35
|
+
<label for="tabk6${i}">K6 Report ${i + 1}</label>
|
|
36
|
+
<div class="tab">
|
|
37
|
+
${k6Bodies[i]}
|
|
38
|
+
</div>
|
|
39
|
+
`
|
|
40
|
+
)
|
|
41
|
+
.join("\n");
|
|
11
42
|
|
|
12
|
-
// Inject the Cucumber Report tab before the Request Metrics tab
|
|
13
43
|
const cucumberTab = `
|
|
14
44
|
<input type="radio" name="tabs" id="tabcucumber">
|
|
15
|
-
<label for="tabcucumber"
|
|
45
|
+
<label for="tabcucumber">Cucumber Report</label>
|
|
16
46
|
<div class="tab">
|
|
17
|
-
<
|
|
47
|
+
<div style="max-height:80vh; overflow:auto; padding:1rem;">
|
|
48
|
+
${cucumberBody}
|
|
49
|
+
</div>
|
|
18
50
|
</div>
|
|
19
51
|
`;
|
|
20
52
|
|
|
21
|
-
const
|
|
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
|
-
);
|
|
53
|
+
const fullTabs = cucumberBody ? cucumberTab + tabs : tabs;
|
|
31
54
|
|
|
32
|
-
|
|
55
|
+
return `
|
|
56
|
+
<!DOCTYPE html>
|
|
57
|
+
<html lang="en">
|
|
58
|
+
<head>
|
|
59
|
+
<meta charset="UTF-8" />
|
|
60
|
+
<title>${title}</title>
|
|
61
|
+
<link rel="stylesheet" href="https://unpkg.com/purecss@2.0.3/build/pure-min.css" crossorigin="anonymous">
|
|
62
|
+
<style>
|
|
63
|
+
body { margin:1rem; font-family:sans-serif; }
|
|
64
|
+
.tabs { display:flex; flex-wrap:wrap; }
|
|
65
|
+
.tabs label { order:1; padding:1rem; cursor:pointer; background:#ddd; margin-right:0.2rem; border-radius:5px 5px 0 0; }
|
|
66
|
+
.tabs .tab { order:99; flex-grow:1; width:100%; display:none; padding:1rem; background:#fff; }
|
|
67
|
+
.tabs input[type="radio"] { display:none; }
|
|
68
|
+
.tabs input[type="radio"]:checked + label { background:#fff; font-weight:bold; }
|
|
69
|
+
.tabs input[type="radio"]:checked + label + .tab { display:block; }
|
|
70
|
+
</style>
|
|
71
|
+
</head>
|
|
72
|
+
<body>
|
|
73
|
+
<h1>${title}</h1>
|
|
74
|
+
<div class="tabs">
|
|
75
|
+
${fullTabs}
|
|
76
|
+
</div>
|
|
77
|
+
</body>
|
|
78
|
+
</html>`;
|
|
33
79
|
}
|
|
34
80
|
|
|
35
|
-
|
|
36
|
-
* Auto-link all HTML reports in the reports directory.
|
|
37
|
-
*/
|
|
38
|
-
function linkReports() {
|
|
81
|
+
async function linkReports() {
|
|
39
82
|
if (!fs.existsSync(reportsDir)) {
|
|
40
83
|
console.warn("⚠️ No reports directory found.");
|
|
41
84
|
return;
|
|
42
85
|
}
|
|
43
86
|
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
.
|
|
47
|
-
|
|
87
|
+
const cucumberFile = findCucumberReportPath();
|
|
88
|
+
const cucumberBody = cucumberFile
|
|
89
|
+
? extractCucumberBody(path.join(reportsDir, cucumberFile))
|
|
90
|
+
: null;
|
|
48
91
|
|
|
49
|
-
|
|
50
|
-
|
|
92
|
+
const k6Files = fs
|
|
93
|
+
.readdirSync(reportsDir)
|
|
94
|
+
.filter((f) => /^k6-report.*\.html$/.test(f));
|
|
95
|
+
if (k6Files.length === 0) {
|
|
96
|
+
console.warn("⚠️ No K6 reports found.");
|
|
51
97
|
return;
|
|
52
98
|
}
|
|
53
99
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
100
|
+
const k6Bodies = k6Files.map((f) =>
|
|
101
|
+
extractK6ReportBody(path.join(reportsDir, f))
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
const combinedHtml = buildCombinedHtml({
|
|
105
|
+
title: "Combined K6 + Cucumber Report",
|
|
106
|
+
k6Bodies,
|
|
107
|
+
cucumberBody,
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
const finalHtml = await minify(combinedHtml, {
|
|
111
|
+
collapseWhitespace: true,
|
|
112
|
+
removeComments: true,
|
|
113
|
+
minifyCSS: true,
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
const outPath = path.join(reportsDir, "combined-report.html");
|
|
117
|
+
fs.writeFileSync(outPath, finalHtml, "utf-8");
|
|
118
|
+
|
|
119
|
+
console.log(`✅ Combined and minified report saved to: ${outPath}`);
|
|
58
120
|
}
|
|
59
121
|
|
|
60
122
|
module.exports = { linkReports };
|