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.
@@ -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
+ ]
@@ -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
- * Adds internal cross-links between reports.
8
- */
9
- function addLinksToReport(targetFile, otherFiles) {
10
- const content = fs.readFileSync(targetFile, "utf-8");
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"><i class="fas fa-file-alt"></i> &nbsp; Cucumber Report</label>
45
+ <label for="tabcucumber">Cucumber Report</label>
16
46
  <div class="tab">
17
- <iframe src="cucumber-report.html" style="width:100%; height:600px; border:none;"></iframe>
47
+ <div style="max-height:80vh; overflow:auto; padding:1rem;">
48
+ ${cucumberBody}
49
+ </div>
18
50
  </div>
19
51
  `;
20
52
 
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
- );
53
+ const fullTabs = cucumberBody ? cucumberTab + tabs : tabs;
31
54
 
32
- fs.writeFileSync(targetFile, modifiedContent, "utf-8");
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 htmlFiles = fs
45
- .readdirSync(reportsDir)
46
- .filter((f) => f.endsWith(".html"))
47
- .map((f) => path.join(reportsDir, f));
87
+ const cucumberFile = findCucumberReportPath();
88
+ const cucumberBody = cucumberFile
89
+ ? extractCucumberBody(path.join(reportsDir, cucumberFile))
90
+ : null;
48
91
 
49
- if (htmlFiles.length < 2) {
50
- console.warn("⚠️ Not enough HTML files to link.");
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
- for (const file of htmlFiles) {
55
- const others = htmlFiles.filter((f) => f !== file);
56
- addLinksToReport(file, others);
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 };