k6-cucumber-steps 1.1.13 → 1.2.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "k6-cucumber-steps",
3
- "version": "1.1.13",
3
+ "version": "1.2.1",
4
4
  "main": "index.js",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -54,7 +54,7 @@
54
54
  "test-automation"
55
55
  ],
56
56
  "engines": {
57
- "node": ">=18"
57
+ "node": ">=20"
58
58
  },
59
59
  "author": "qaPaschalE",
60
60
  "description": "Cucumber step definitions for running k6 performance tests.",
@@ -3,354 +3,406 @@
3
3
  const { Given, When, Then } = require("@cucumber/cucumber");
4
4
  const fs = require("fs");
5
5
  const path = require("path");
6
- const { execSync } = require("child_process");
7
6
  const resolveBody = require("../lib/helpers/resolveBody.js");
8
7
  const buildK6Script = require("../lib/helpers/buildK6Script.js");
9
8
  const generateHeaders = require("../lib/helpers/generateHeaders.js");
10
9
  const { generateK6Script, runK6Script } = require("../lib/utils/k6Runner.js");
11
10
  require("dotenv").config();
12
11
 
13
- // Validate thresholds (e.g., "rate<0.01")
14
- const validateThreshold = (threshold) => {
15
- const regex = /^[\w{}()<>:]+[<>=]\d+(\.\d+)?$/;
16
- if (!regex.test(threshold)) {
17
- throw new Error(`Invalid threshold format: ${threshold}`);
18
- }
19
- };
20
-
21
12
  /**
22
- * @param {string} method - HTTP method (e.g., GET, POST).
13
+ * Sets the HTTP method for the k6 script.
14
+ *
15
+ * ```gherkin
16
+ * Given I set a k6 script for {word} testing
17
+ * ```
23
18
  * @example
24
19
  * Given I set a k6 script for GET testing
25
- * Given I set a k6 script for POST testing
20
+ * @category K6 Steps
26
21
  */
27
- Given("I set a k6 script for {word} testing", function (method) {
22
+ async function Given_I_set_a_k6_script_for_method(this, method) {
28
23
  this.config = { method: method.toUpperCase() };
29
- });
24
+ }
25
+ Given(/^I set a k6 script for (\w+) testing$/, Given_I_set_a_k6_script_for_method);
30
26
 
31
27
  /**
32
- * @param {DataTable} dataTable - Table with configurations.
28
+ * Sets k6 script options from a configuration table.
29
+ *
30
+ * ```gherkin
31
+ * When I set to run the k6 script with the following configurations:
32
+ * | virtual_users | duration | http_req_failed | http_req_duration | error_rate | stages |
33
+ * ```
33
34
  * @example
34
35
  * When I set to run the k6 script with the following configurations:
35
- * | virtual_users | duration | http_req_failed | http_req_duration | error_rate | stages |
36
- * | 10 | 5 | rate<0.05 | p(95)<200 | | |
37
- * | 50 | 10 | rate<0.01 | | rate<0.01 | [{"target": 10, "duration": "10s"}, {"target": 50, "duration": "30s"}] |
36
+ * | virtual_users | duration | http_req_failed | http_req_duration | error_rate | stages |
37
+ * | 10 | 5 | rate<0.05 | p(95)<200 | | |
38
+ * @category K6 Steps
38
39
  */
39
- When(
40
- "I set to run the k6 script with the following configurations:",
41
- function (dataTable) {
42
- const rawRow = dataTable.hashes()[0];
43
- const row = {};
44
-
45
- // Extract example values manually from this.pickle
46
- const exampleMap = {};
47
- if (this.pickle && this.pickle.astNodeIds && this.gherkinDocument) {
48
- const scenario = this.gherkinDocument.feature.children.find((child) => {
49
- return child.scenario && child.scenario.examples?.length;
50
- });
51
-
52
- const exampleValues =
53
- scenario?.scenario?.examples?.[0]?.tableBody?.[0]?.cells?.map(
54
- (cell) => cell.value
55
- ) || [];
56
-
57
- const exampleKeys =
58
- scenario?.scenario?.examples?.[0]?.tableHeader?.cells?.map(
59
- (cell) => cell.value
60
- ) || [];
61
-
62
- exampleKeys.forEach((key, idx) => {
63
- exampleMap[key] = exampleValues[idx];
64
- });
65
- }
40
+ async function When_I_set_k6_script_configurations(this, dataTable) {
41
+ const rawRow = dataTable.hashes()[0];
42
+ const row = {};
43
+
44
+ // Extract example values manually from this.pickle
45
+ const exampleMap = {};
46
+ if (this.pickle && this.pickle.astNodeIds && this.gherkinDocument) {
47
+ const scenario = this.gherkinDocument.feature.children.find((child) => {
48
+ return child.scenario && child.scenario.examples?.length;
49
+ });
66
50
 
67
- for (const [key, value] of Object.entries(rawRow)) {
68
- row[key] = value.replace(/<([^>]+)>/g, (_, param) => {
69
- return exampleMap[param] || value;
70
- });
71
- }
51
+ const exampleValues =
52
+ scenario?.scenario?.examples?.[0]?.tableBody?.[0]?.cells?.map(
53
+ (cell) => cell.value
54
+ ) || [];
72
55
 
73
- console.log("🚨 Resolved config row:", row);
56
+ const exampleKeys =
57
+ scenario?.scenario?.examples?.[0]?.tableHeader?.cells?.map(
58
+ (cell) => cell.value
59
+ ) || [];
74
60
 
75
- const validateThreshold = (value) => {
76
- const regex = /^[\w{}()<>:]+[<>=]\d+(\.\d+)?$/;
77
- if (value && !regex.test(value)) {
78
- throw new Error(`Invalid threshold format: ${value}`);
79
- }
80
- };
61
+ exampleKeys.forEach((key, idx) => {
62
+ exampleMap[key] = exampleValues[idx];
63
+ });
64
+ }
81
65
 
82
- validateThreshold(row.http_req_failed);
83
- validateThreshold(row.http_req_duration);
84
- validateThreshold(row.error_rate);
85
-
86
- if (row.stages) {
87
- try {
88
- this.config.options = {
89
- stages: JSON.parse(row.stages),
90
- thresholds: {
91
- http_req_failed: [row.http_req_failed],
92
- http_req_duration: [row.http_req_duration],
93
- },
94
- };
95
- } catch {
96
- throw new Error("Invalid stages JSON format.");
97
- }
98
- } else {
66
+ for (const [key, value] of Object.entries(rawRow)) {
67
+ row[key] = value.replace(/<([^>]+)>/g, (_, param) => {
68
+ return exampleMap[param] || value;
69
+ });
70
+ }
71
+
72
+ const validateThreshold = (value) => {
73
+ const regex = /^[\w{}()<>:]+[<>=]\d+(\.\d+)?$/;
74
+ if (value && !regex.test(value)) {
75
+ throw new Error(`Invalid threshold format: ${value}`);
76
+ }
77
+ };
78
+
79
+ validateThreshold(row.http_req_failed);
80
+ validateThreshold(row.http_req_duration);
81
+ validateThreshold(row.error_rate);
82
+
83
+ if (row.stages) {
84
+ try {
99
85
  this.config.options = {
100
- vus: parseInt(row.virtual_users),
101
- duration: `${row.duration}s`,
86
+ stages: JSON.parse(row.stages),
102
87
  thresholds: {
103
88
  http_req_failed: [row.http_req_failed],
104
89
  http_req_duration: [row.http_req_duration],
105
90
  },
106
91
  };
92
+ } catch {
93
+ throw new Error("Invalid stages JSON format.");
107
94
  }
95
+ } else {
96
+ this.config.options = {
97
+ vus: parseInt(row.virtual_users),
98
+ duration: `${row.duration}s`,
99
+ thresholds: {
100
+ http_req_failed: [row.http_req_failed],
101
+ http_req_duration: [row.http_req_duration],
102
+ },
103
+ };
104
+ }
108
105
 
109
- if (row.error_rate) {
110
- this.config.options.thresholds.error_rate = [row.error_rate];
111
- }
106
+ if (row.error_rate) {
107
+ this.config.options.thresholds.error_rate = [row.error_rate];
112
108
  }
109
+ }
110
+ When(
111
+ /^I set to run the k6 script with the following configurations:$/,
112
+ When_I_set_k6_script_configurations
113
113
  );
114
114
 
115
115
  /**
116
- * @param {DataTable} dataTable - Table with header and value for request headers.
116
+ * Sets request headers for the k6 script.
117
+ *
118
+ * ```gherkin
119
+ * When I set the request headers:
120
+ * | Header | Value |
121
+ * ```
117
122
  * @example
118
123
  * When I set the request headers:
119
- * | Header | Value |
120
- * | Content-Type | application/json |
121
- * | Authorization | Bearer your_auth_token |
124
+ * | Header | Value |
125
+ * | Content-Type | application/json |
126
+ * @category K6 Steps
122
127
  */
123
- When("I set the request headers:", function (dataTable) {
128
+ async function When_I_set_request_headers(this, dataTable) {
124
129
  const headers = {};
125
130
  dataTable.hashes().forEach(({ Header, Value }) => {
126
131
  headers[Header] = Value;
127
132
  });
128
133
 
129
134
  this.config.headers = {
130
- ...this.config.headers, // preserve auth headers if set
135
+ ...this.config.headers,
131
136
  ...headers,
132
137
  };
133
- });
138
+ }
139
+ When(/^I set the request headers:$/, When_I_set_request_headers);
134
140
 
135
141
  /**
136
- * @param {string} docString - Multiline string containing the endpoints.
142
+ * Sets endpoints for the k6 script.
143
+ *
144
+ * ```gherkin
145
+ * When I set the following endpoints used:
146
+ * """
147
+ * /api/users
148
+ * /api/products
149
+ * """
150
+ * ```
137
151
  * @example
138
152
  * When I set the following endpoints used:
139
153
  * """
140
154
  * /api/users
141
155
  * /api/products
142
156
  * """
157
+ * @category K6 Steps
143
158
  */
144
- When("I set the following endpoints used:", function (docString) {
159
+ async function When_I_set_endpoints_used(this, docString) {
145
160
  this.config.endpoints = docString
146
161
  .trim()
147
162
  .split("\n")
148
163
  .map((line) => line.trim());
149
- });
164
+ }
165
+ When(/^I set the following endpoints used:$/, When_I_set_endpoints_used);
150
166
 
151
167
  /**
152
- * @param {string} method - HTTP method (e.g., POST, PUT).
153
- * @param {string} endpoint - The endpoint for the body.
154
- * @param {string} docString - Multiline string containing the request body (can use placeholders).
168
+ * Sets the request body for a specific method and endpoint.
169
+ *
170
+ * ```gherkin
171
+ * When I set the following {word} body is used for {string}
172
+ * """
173
+ * { ... }
174
+ * """
175
+ * ```
155
176
  * @example
156
177
  * When I set the following POST body is used for "/api/users"
157
178
  * """
158
- * {
159
- * "username": "{{username}}",
160
- * "email": "{{faker.internet.email}}"
161
- * }
179
+ * { "username": "{{username}}" }
162
180
  * """
181
+ * @category K6 Steps
163
182
  */
183
+ async function When_I_set_body_for_method_and_endpoint(this, method, endpoint, docString) {
184
+ this.config.method = method.toUpperCase();
185
+ this.config.body = resolveBody(docString, process.env);
186
+ this.config.endpoint = endpoint;
187
+ }
164
188
  When(
165
- "I set the following {word} body is used for {string}",
166
- function (method, endpoint, docString) {
167
- this.config.method = method.toUpperCase();
168
- this.config.body = resolveBody(docString, process.env);
169
- this.config.endpoint = endpoint;
170
- }
189
+ /^I set the following (\w+) body is used for "([^"]+)"$/,
190
+ When_I_set_body_for_method_and_endpoint
171
191
  );
172
192
 
173
- When(
174
- "I use JSON payload from {string} for {word} to {string}",
175
- function (fileName, method, endpoint) {
176
- const allowedMethods = ["POST", "PUT", "PATCH"];
177
- const methodUpper = method.toUpperCase();
178
-
179
- if (!allowedMethods.includes(methodUpper)) {
180
- throw new Error(
181
- `Method "${method}" is not supported. Use one of: ${allowedMethods.join(
182
- ", "
183
- )}`
184
- );
185
- }
193
+ /**
194
+ * Loads a JSON payload for a method and endpoint.
195
+ *
196
+ * ```gherkin
197
+ * When I use JSON payload from {string} for {word} to {string}
198
+ * ```
199
+ * @example
200
+ * When I use JSON payload from "login.json" for POST to "/api/login"
201
+ * @category K6 Steps
202
+ */
203
+ async function When_I_use_JSON_payload(this, fileName, method, endpoint) {
204
+ const allowedMethods = ["POST", "PUT", "PATCH"];
205
+ const methodUpper = method.toUpperCase();
206
+
207
+ if (!allowedMethods.includes(methodUpper)) {
208
+ throw new Error(
209
+ `Method "${method}" is not supported. Use one of: ${allowedMethods.join(
210
+ ", "
211
+ )}`
212
+ );
213
+ }
186
214
 
187
- const projectRoot = path.resolve(__dirname, "..", "..");
188
- const payloadDir = this.parameters?.payloadPath || "payloads";
189
- const payloadPath = path.isAbsolute(payloadDir)
190
- ? path.join(payloadDir, fileName)
191
- : path.join(__dirname, "..", "..", payloadDir, fileName);
215
+ const projectRoot = path.resolve(__dirname, "..", "..");
216
+ const payloadDir = this.parameters?.payloadPath || "payloads";
217
+ const payloadPath = path.isAbsolute(payloadDir)
218
+ ? path.join(payloadDir, fileName)
219
+ : path.join(projectRoot, payloadDir, fileName);
192
220
 
193
- if (!fs.existsSync(payloadPath)) {
194
- throw new Error(`Payload file not found: ${payloadPath}`);
195
- }
221
+ if (!fs.existsSync(payloadPath)) {
222
+ throw new Error(`Payload file not found: ${payloadPath}`);
223
+ }
196
224
 
197
- const rawTemplate = fs.readFileSync(payloadPath, "utf-8");
198
- const resolved = resolveBody(rawTemplate, {
199
- ...process.env,
200
- ...(this.aliases || {}),
201
- });
225
+ const rawTemplate = fs.readFileSync(payloadPath, "utf-8");
226
+ const resolved = resolveBody(rawTemplate, {
227
+ ...process.env,
228
+ ...(this.aliases || {}),
229
+ });
202
230
 
203
- this.config = {
204
- ...this.config, // ✅ Keep previously set options!
205
- method: methodUpper,
206
- endpoint,
207
- body: resolved,
208
- headers: this.config?.headers || {},
209
- };
231
+ this.config = {
232
+ ...this.config,
233
+ method: methodUpper,
234
+ endpoint,
235
+ body: resolved,
236
+ headers: this.config?.headers || {},
237
+ };
210
238
 
211
- this.lastRequest = {
212
- method: methodUpper,
213
- endpoint,
214
- body: resolved,
215
- };
216
- }
239
+ this.lastRequest = {
240
+ method: methodUpper,
241
+ endpoint,
242
+ body: resolved,
243
+ };
244
+ }
245
+ When(
246
+ /^I use JSON payload from "([^"]+)" for (\w+) to "([^"]+)"$/,
247
+ When_I_use_JSON_payload
217
248
  );
218
249
 
219
250
  /**
220
- * @param {string} authType - Authentication type (api_key, bearer_token, basic, none).
251
+ * Sets the authentication type for the request.
252
+ *
253
+ * ```gherkin
254
+ * When I set the authentication type to {string}
255
+ * ```
221
256
  * @example
222
257
  * When I set the authentication type to "bearer_token"
223
- * When I set the authentication type to "api_key"
224
- * When I set the authentication type to "basic"
225
- * When I set the authentication type to "none"
258
+ * @category K6 Steps
226
259
  */
227
- When("I set the authentication type to {string}", function (authType) {
260
+ async function When_I_set_authentication_type(this, authType) {
228
261
  this.config.headers = generateHeaders(
229
262
  authType,
230
263
  process.env,
231
264
  this.aliases || {}
232
265
  );
233
- });
266
+ }
267
+ When(/^I set the authentication type to "([^"]+)"$/, When_I_set_authentication_type);
234
268
 
235
269
  /**
270
+ * Stores a value from the last response as an alias.
271
+ *
272
+ * ```gherkin
273
+ * Then I store the value at {string} as alias {string}
274
+ * ```
275
+ * @example
236
276
  * Then I store the value at "data.token" as alias "token"
277
+ * @category K6 Steps
237
278
  */
238
- Then(
239
- "I store the value at {string} as alias {string}",
240
- function (jsonPath, alias) {
241
- if (!this.lastResponse) {
242
- throw new Error("No previous response available.");
243
- }
279
+ async function Then_I_store_value_as_alias(this, jsonPath, alias) {
280
+ if (!this.lastResponse) {
281
+ throw new Error("No previous response available.");
282
+ }
244
283
 
245
- const pathParts = jsonPath.split(".");
246
- let value = this.lastResponse;
284
+ const pathParts = jsonPath.split(".");
285
+ let value = this.lastResponse;
247
286
 
248
- for (const key of pathParts) {
249
- value = value?.[key];
250
- if (value === undefined) break;
251
- }
287
+ for (const key of pathParts) {
288
+ value = value?.[key];
289
+ if (value === undefined) break;
290
+ }
252
291
 
253
- if (value === undefined) {
254
- throw new Error(`Could not resolve path "${jsonPath}" in the response`);
255
- }
292
+ if (value === undefined) {
293
+ throw new Error(`Could not resolve path "${jsonPath}" in the response`);
294
+ }
256
295
 
257
- if (!this.aliases) this.aliases = {};
258
- this.aliases[alias] = value;
296
+ if (!this.aliases) this.aliases = {};
297
+ this.aliases[alias] = value;
259
298
 
260
- console.log(`🧩 Stored alias "${alias}":`, value);
261
- }
299
+ console.log(`🧩 Stored alias "${alias}":`, value);
300
+ }
301
+ Then(
302
+ /^I store the value at "([^"]+)" as alias "([^"]+)"$/,
303
+ Then_I_store_value_as_alias
262
304
  );
263
- When(
264
- "I login via POST to {string} with payload from {string}",
265
- async function (endpoint, fileName) {
266
- const payloadDir = this.parameters?.payloadPath || "payloads";
267
- const payloadPath = path.isAbsolute(payloadDir)
268
- ? path.join(payloadDir, fileName)
269
- : path.join(__dirname, "..", "..", payloadDir, fileName);
270
-
271
- if (!fs.existsSync(payloadPath)) {
272
- throw new Error(`Payload file not found: ${payloadPath}`);
273
- }
274
305
 
275
- const rawTemplate = fs.readFileSync(payloadPath, "utf-8");
276
-
277
- const resolved = resolveBody(rawTemplate, {
278
- ...process.env,
279
- ...(this.aliases || {}),
280
- });
306
+ /**
307
+ * Logs in via POST to an endpoint with a payload from a file.
308
+ *
309
+ * ```gherkin
310
+ * When I login via POST to {string} with payload from {string}
311
+ * ```
312
+ * @example
313
+ * When I login via POST to "/api/login" with payload from "login.json"
314
+ * @category K6 Steps
315
+ */
316
+ async function When_I_login_via_POST_with_payload(this, endpoint, fileName) {
317
+ const payloadDir = this.parameters?.payloadPath || "payloads";
318
+ const projectRoot = path.resolve(__dirname, "..", "..");
319
+ const payloadPath = path.isAbsolute(payloadDir)
320
+ ? path.join(payloadDir, fileName)
321
+ : path.join(projectRoot, payloadDir, fileName);
322
+
323
+ if (!fs.existsSync(payloadPath)) {
324
+ throw new Error(`Payload file not found: ${payloadPath}`);
325
+ }
281
326
 
282
- try {
283
- const response = await fetch(`${process.env.BASE_URL}${endpoint}`, {
284
- method: "POST",
285
- headers: {
286
- "Content-Type": "application/json",
287
- },
288
- body: JSON.stringify(resolved),
289
- });
327
+ const rawTemplate = fs.readFileSync(payloadPath, "utf-8");
328
+ const resolved = resolveBody(rawTemplate, {
329
+ ...process.env,
330
+ ...(this.aliases || {}),
331
+ });
290
332
 
291
- const data = await response.json();
333
+ try {
334
+ const response = await fetch(`${process.env.BASE_URL}${endpoint}`, {
335
+ method: "POST",
336
+ headers: {
337
+ "Content-Type": "application/json",
338
+ },
339
+ body: JSON.stringify(resolved),
340
+ });
292
341
 
293
- if (!response.ok) {
294
- console.error("❌ Login request failed:", data);
295
- throw new Error(`Login request failed with status ${response.status}`);
296
- }
342
+ const data = await response.json();
297
343
 
298
- this.lastResponse = data; // ✅ Makes aliasing work
299
- console.log("🔐 Login successful, response saved to alias context.");
300
- } catch (err) {
301
- console.error("❌ Login request failed:", err.message);
302
- throw new Error("Login request failed");
344
+ if (!response.ok) {
345
+ console.error(" Login request failed:", data);
346
+ throw new Error(`Login request failed with status ${response.status}`);
303
347
  }
348
+
349
+ this.lastResponse = data;
350
+ console.log("🔐 Login successful, response saved to alias context.");
351
+ } catch (err) {
352
+ console.error("❌ Login request failed:", err.message);
353
+ throw new Error("Login request failed");
304
354
  }
355
+ }
356
+ When(
357
+ /^I login via POST to "([^"]+)" with payload from "([^"]+)"$/,
358
+ When_I_login_via_POST_with_payload
305
359
  );
306
360
 
307
361
  /**
308
- * @param {string} method - HTTP method of the request.
362
+ * Executes the k6 script and checks the result.
363
+ *
364
+ * ```gherkin
365
+ * Then I see the API should handle the {word} request successfully
366
+ * ```
309
367
  * @example
310
- * Then I see the API should handle the GET request successfully
311
368
  * Then I see the API should handle the POST request successfully
369
+ * @category K6 Steps
312
370
  */
313
- Then(
314
- "I see the API should handle the {word} request successfully",
315
- { timeout: 60000 },
316
- async function (method) {
317
- if (!this.config || !this.config.method) {
318
- throw new Error("Configuration is missing or incomplete.");
319
- }
320
- const expectedMethod = method.toUpperCase();
321
- const actualMethod = this.config.method.toUpperCase();
322
- if (actualMethod !== expectedMethod) {
323
- throw new Error(
324
- `Mismatched HTTP method: expected "${expectedMethod}", got "${actualMethod}"`
325
- );
326
- }
327
- try {
328
- // Build the k6 script content
329
- const scriptContent = buildK6Script(this.config);
330
-
331
- // Generate the k6 script file
332
- const scriptPath = await generateK6Script(
333
- scriptContent,
334
- "load",
335
- process.env.K6_CUCUMBER_OVERWRITE === "true"
336
- );
337
-
338
- // Run the k6 script and capture output
339
- const { stdout, stderr, code } = await runK6Script(
340
- scriptPath,
341
- process.env.K6_CUCUMBER_OVERWRITE === "true"
342
- );
343
-
344
- if (stdout) console.log(stdout);
345
- if (stderr) console.error(stderr);
346
-
347
- if (code !== 0) {
348
- throw new Error(`k6 exited with code ${code}`);
349
- }
350
- } catch (error) {
351
- // Print the full error for debugging
352
- console.error("k6 execution failed:", error.stack || error);
353
- throw new Error("k6 test execution failed");
371
+ async function Then_I_see_API_should_handle_request(this, method) {
372
+ if (!this.config || !this.config.method) {
373
+ throw new Error("Configuration is missing or incomplete.");
374
+ }
375
+ const expectedMethod = method.toUpperCase();
376
+ const actualMethod = this.config.method.toUpperCase();
377
+ if (actualMethod !== expectedMethod) {
378
+ throw new Error(
379
+ `Mismatched HTTP method: expected "${expectedMethod}", got "${actualMethod}"`
380
+ );
381
+ }
382
+ try {
383
+ const scriptContent = buildK6Script(this.config);
384
+ const scriptPath = await generateK6Script(
385
+ scriptContent,
386
+ "load",
387
+ process.env.K6_CUCUMBER_OVERWRITE === "true"
388
+ );
389
+ const { stdout, stderr, code } = await runK6Script(
390
+ scriptPath,
391
+ process.env.K6_CUCUMBER_OVERWRITE === "true"
392
+ );
393
+
394
+ if (stdout) console.log(stdout);
395
+ if (stderr) console.error(stderr);
396
+
397
+ if (code !== 0) {
398
+ throw new Error(`k6 exited with code ${code}`);
354
399
  }
400
+ } catch (error) {
401
+ console.error("k6 execution failed:", error.stack || error);
402
+ throw new Error("k6 test execution failed");
355
403
  }
404
+ }
405
+ Then(
406
+ /^I see the API should handle the (\w+) request successfully$/,
407
+ Then_I_see_API_should_handle_request
356
408
  );