k6-cucumber-steps 2.0.6 โ†’ 2.0.8-alpha.0

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.
Files changed (29) hide show
  1. package/README.md +300 -9
  2. package/dist/cli.js +17 -8
  3. package/dist/cli.js.map +1 -1
  4. package/dist/generators/k6-script.generator.d.ts.map +1 -1
  5. package/dist/generators/k6-script.generator.js +53 -35
  6. package/dist/generators/k6-script.generator.js.map +1 -1
  7. package/dist/generators/project.generator.d.ts +1 -4
  8. package/dist/generators/project.generator.d.ts.map +1 -1
  9. package/dist/generators/project.generator.js +66 -926
  10. package/dist/generators/project.generator.js.map +1 -1
  11. package/dist/generators/samples/sample-features.generator.d.ts +7 -0
  12. package/dist/generators/samples/sample-features.generator.d.ts.map +1 -0
  13. package/dist/generators/samples/sample-features.generator.js +196 -0
  14. package/dist/generators/samples/sample-features.generator.js.map +1 -0
  15. package/dist/generators/samples/sample-steps.generator.d.ts +6 -0
  16. package/dist/generators/samples/sample-steps.generator.d.ts.map +1 -0
  17. package/dist/generators/samples/sample-steps.generator.js +1183 -0
  18. package/dist/generators/samples/sample-steps.generator.js.map +1 -0
  19. package/dist/generators/samples/steps-metadata.generator.d.ts +12 -0
  20. package/dist/generators/samples/steps-metadata.generator.d.ts.map +1 -0
  21. package/dist/generators/samples/steps-metadata.generator.js +502 -0
  22. package/dist/generators/samples/steps-metadata.generator.js.map +1 -0
  23. package/dist/index.d.ts +4 -1
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +7 -1
  26. package/dist/index.js.map +1 -1
  27. package/dist/metadata.json +645 -0
  28. package/package.json +10 -6
  29. package/payload.json.example +15 -0
package/README.md CHANGED
@@ -16,17 +16,57 @@ Run [k6](https://k6.io/) performance/load tests using [Cucumber](https://cucumbe
16
16
 
17
17
  ---
18
18
 
19
+ ## ๐Ÿ“š Step Definitions Documentation
20
+
21
+ All step definitions are fully documented and available in multiple formats:
22
+
23
+ ### 1. TypeDoc API Documentation
24
+ Interactive HTML documentation with search and navigation:
25
+
26
+ **Online:** [View TypeDoc Documentation](https://qapaschale.github.io/k6-cucumber-steps/docs/)
27
+
28
+ **Locally:**
29
+ ```bash
30
+ # Generate documentation
31
+ npm run docs
32
+
33
+ # View in browser
34
+ npm run docs:serve
35
+ ```
36
+
37
+ ### 2. Step Metadata (JSON)
38
+ When you initialize a project, a `steps/metadata.json` file is generated containing:
39
+ - All available step patterns
40
+ - Function names and parameters
41
+ - Categories (HTTP, Browser, Assertions, etc.)
42
+ - Descriptions for each step
43
+
44
+ ### 3. TypeScript Type Definitions
45
+ The `src/steps.d.ts` file provides:
46
+ - Full TypeScript type definitions
47
+ - JSDoc comments with examples
48
+ - IntelliSense support in IDEs
49
+
50
+ ### 4. README Reference
51
+ See the [Step Definitions Reference](#step-definitions-reference) section below for common usage examples.
52
+
53
+ ---
54
+
19
55
  ## โœจ Features
20
56
 
21
57
  - โœ… Cucumber + Gherkin for writing k6 tests
22
58
  to generate JSON and HTML reports.
23
59
  - โœ… Flexible configuration through Cucumber data tables.
24
60
  - โœ… Support for JSON body parsing and escaping
25
- - โœ… Dynamic request body generation using environment variables, Faker templates, and JSON file includes.
61
+ - โœ… **Environment Variable Support**: Dynamic request body generation using `{{VARIABLE_NAME}}` placeholders
62
+ - โœ… **Enhanced Auth Storage**: Store tokens with aliases for cross-scenario reuse
26
63
  - โœ… `.env` + `K6.env`-style variable resolution (`{{API_KEY}}`)
27
64
  - โœ… Support for headers, query params, stages
28
65
  - โœ… Supports multiple authentication types: API key, Bearer token, Basic Auth, and No Auth.
29
-
66
+ - โœ… **Extended HTTP Methods**: GET, POST, PUT, PATCH with body support
67
+ - โœ… **Response Time Assertions**: Validate API performance with millisecond/second thresholds
68
+ - โœ… **Property Validation**: Deep nested property checks, boolean assertions, empty checks
69
+ - โœ… **Alias System**: Store and compare response values across scenarios
30
70
  - โœ… Clean-up of temporary k6 files after execution
31
71
  - โœ… Built-in support for **distributed load testing** with stages
32
72
  - โœ… TypeScript-first ๐Ÿงก
@@ -39,6 +79,87 @@ Run [k6](https://k6.io/) performance/load tests using [Cucumber](https://cucumbe
39
79
  - ๐Ÿ›  **JS & TS Support**: Generate your project in pure JavaScript or TypeScript.
40
80
  - ๐Ÿ“Š **Metric Segmentation**: Scenarios are wrapped in k6 `group()` blocks for cleaner reporting.
41
81
 
82
+ ## ๐Ÿ†• What's New in v2.0.8
83
+
84
+ ### โš ๏ธ Breaking Change: Step Definition Prefix
85
+
86
+ All step definitions now use the **`k6` prefix** to avoid conflicts with other step libraries and make it clear these are k6-specific steps.
87
+
88
+ **Before:**
89
+ ```gherkin
90
+ Given the base URL is "{{API_BASE_URL}}"
91
+ When I make a GET request to "/users/1"
92
+ ```
93
+
94
+ **After:**
95
+ ```gherkin
96
+ Given the k6 base URL is "{{API_BASE_URL}}"
97
+ When I k6 make a GET request to "/users/1"
98
+ ```
99
+
100
+ ### ๐ŸŒ Environment Variable Support
101
+
102
+ Replace placeholders in your tests using `{{VARIABLE_NAME}}` syntax. Values are resolved from:
103
+ - `__ENV` (k6 environment variables)
104
+ - `K6_*` prefixed variables
105
+ - Global context variables
106
+
107
+ ```gherkin
108
+ Background:
109
+ Given the k6 base URL is "{{API_BASE_URL}}"
110
+
111
+ Scenario: Login with environment credentials
112
+ When I k6 authenticate with the following url and request body as "user":
113
+ | endpoint | userName | password |
114
+ | /login | {{TEST_USER_USERNAME}} | {{TEST_USER_PASSWORD}} |
115
+ ```
116
+
117
+ ### ๐Ÿ” Enhanced Alias System
118
+
119
+ Store response data with custom aliases and reuse across scenarios:
120
+
121
+ ```gherkin
122
+ Scenario: Store and reuse values
123
+ When I k6 make a POST request to "/login"
124
+ And I k6 store response "data.accessToken" as "authToken"
125
+ Then the k6 alias "authToken" should not be empty
126
+
127
+ Scenario: Compare against stored values
128
+ Then the k6 response property "userName" should be alias "expectedUsername"
129
+ And the k6 response property "message" should contain alias "expectedMessage"
130
+ ```
131
+
132
+ ### ๐Ÿ“ New Assertion Steps
133
+
134
+ | Step | Description | Example |
135
+ |------|-------------|---------|
136
+ | `theResponsePropertyShouldNotBeEmpty` | Validate property has a value | `Then the k6 response property "data.token" should not be empty` |
137
+ | `theResponsePropertyShouldBeTrue/False` | Boolean assertions | `Then the k6 response property "success" should be true` |
138
+ | `theResponsePropertyShouldHaveProperty` | Check nested properties | `Then the k6 response property "data" should have property "user"` |
139
+ | `theResponseTimeShouldBeLessThan...` | Performance assertions | `Then the k6 response time should be less than "500" milliseconds` |
140
+ | `theAliasShouldNotBeEmpty` | Validate stored aliases | `Then the k6 alias "authToken" should not be empty` |
141
+ | `theAliasShouldBeEqualTo` | Compare alias to value | `Then the k6 alias "username" should be equal to "test_user"` |
142
+
143
+ ### ๐ŸŒ Extended HTTP Support
144
+
145
+ - **PUT requests**: `When I k6 make a PUT request to "/users/1"`
146
+ - **PUT with body**: `When I k6 make a PUT request to "/users/1" with body:`
147
+ - **PATCH requests**: `When I k6 make a PATCH request to "/api/settings"`
148
+ - **PATCH with body**: `When I k6 make a PATCH request to "/api/settings" with body:`
149
+
150
+ ### ๐Ÿ–จ๏ธ Debug Helpers
151
+
152
+ ```gherkin
153
+ And I k6 print alias "authToken" # Print a specific alias
154
+ And I k6 print all aliases # Print all stored aliases
155
+ ```
156
+
157
+ ### ๐Ÿงน Utility Steps
158
+
159
+ ```gherkin
160
+ Given I k6 clear auth token # Remove Authorization header
161
+ ```
162
+
42
163
  ## โœจ New: Hybrid Performance Testing
43
164
 
44
165
  You can now combine **Protocol-level (HTTP)** load testing and **Browser-level (Web Vitals)** testing in a single Gherkin suite.
@@ -137,6 +258,54 @@ For projects where you prefer to run single features directly.
137
258
 
138
259
  ---
139
260
 
261
+ ## ๐Ÿ” Environment Variables Support
262
+
263
+ The generated project includes `dotenv-cli` for easy environment variable management.
264
+
265
+ ### Using .env file
266
+
267
+ 1. Create a `.env` file in your project root:
268
+ ```bash
269
+ API_BASE_URL=https://api.example.com
270
+ AUTH_BASE_URL=https://auth.example.com
271
+ TEST_USER_USERNAME=myuser
272
+ TEST_USER_PASSWORD=mypassword
273
+ POST_TITLE=My Test Post
274
+ CLIENT_ID=my-client-id
275
+ CLIENT_SECRET=my-secret
276
+ ```
277
+
278
+ 2. Run tests with environment variables:
279
+ ```bash
280
+ # Using dotenv-cli to load .env file
281
+ npx dotenv-cli -- k6 run generated/test.generated.ts
282
+
283
+ # Or add to your package.json scripts:
284
+ "test:env": "dotenv-cli -- k6 run generated/test.generated.ts"
285
+ ```
286
+
287
+ ### Using K6_ prefixed variables
288
+
289
+ You can also use `K6_` prefixed environment variables directly:
290
+ ```bash
291
+ K6_API_BASE_URL=https://api.example.com k6 run generated/test.generated.ts
292
+ ```
293
+
294
+ ### In your feature files
295
+
296
+ Use `{{VARIABLE_NAME}}` syntax to reference environment variables:
297
+ ```gherkin
298
+ Background:
299
+ Given the k6 base URL is "{{API_BASE_URL}}"
300
+
301
+ Scenario: Login with environment credentials
302
+ When I k6 authenticate with the following url and request body as "user":
303
+ | endpoint | userName | password |
304
+ | /login | {{TEST_USER_USERNAME}} | {{TEST_USER_PASSWORD}} |
305
+ ```
306
+
307
+ ---
308
+
140
309
  ## ๐Ÿ”‘ Advanced Authentication Flow
141
310
 
142
311
  We now support **Dynamic Handshake Authentication**. You can log in once in an initial scenario, store the token, and all subsequent scenarios will automatically be authenticated.
@@ -188,14 +357,23 @@ Scenario: Login and Save Session
188
357
 
189
358
  ```
190
359
 
360
+ <a name="step-definitions-reference"></a>
191
361
  ## ๐Ÿงผ Step Definitions Reference
192
362
 
193
363
  | Step Example | Layer | Description |
194
364
  | ------------------------------------- | ------- | ---------------------------- |
195
- | `When I make a GET request to "/api"` | API | Standard HTTP request. |
196
- | `When I navigate to the "/home" page` | Browser | Opens URL in Chromium. |
197
- | `And I click the button ".submit"` | Browser | Interacts with DOM elements. |
198
- | `And I store "path" in "file.json"` | Both | Dynamic data persistence. |
365
+ | `When I k6 make a GET request to "/api"` | API | Standard HTTP request. |
366
+ | `When I k6 make a POST request to "/api"` | API | Create resource with stored body |
367
+ | `When I k6 make a PUT request to "/api"` | API | Update resource with stored body |
368
+ | `When I k6 make a PATCH request to "/api"` | API | Partial update with stored body |
369
+ | `When I k6 navigate to the "/home" page` | Browser | Opens URL in Chromium. |
370
+ | `And I k6 click the button ".submit"` | Browser | Interacts with DOM elements. |
371
+ | `And I k6 store "path" in "file.json"` | Both | Dynamic data persistence. |
372
+ | `And I k6 store response "data.token" as "authToken"` | API | Store response with alias |
373
+ | `Then the k6 response property "id" should be "123"` | API | Validate property value |
374
+ | `Then the k6 response property "success" should be true` | API | Boolean assertion |
375
+ | `Then the k6 response time should be less than "500" milliseconds` | API | Performance check |
376
+ | `Then the k6 alias "authToken" should not be empty` | API | Validate stored alias |
199
377
 
200
378
  ---
201
379
 
@@ -216,13 +394,126 @@ Every test run now produces a rich HTML dashboard. Your scenarios are grouped na
216
394
  ### Authentication Steps
217
395
 
218
396
  ```gherkin
219
- When I authenticate with the following url and request body as "standard_user":
397
+ When I k6 authenticate with the following url and request body as "standard_user":
220
398
  | endpoint | username | password |
221
399
  | /login | paschal_qa | pass123 |
222
- And I am authenticated as a "standard_user" # Lookups token from memory
400
+ And I k6 am authenticated as a "standard_user" # Lookups token from memory
401
+ ```
402
+
403
+ ### Environment Variable Steps
404
+
405
+ ```gherkin
406
+ Background:
407
+ Given the k6 base URL is "{{API_BASE_URL}}" # Resolves from __ENV or .env
408
+
409
+ Scenario: Use environment variables in request body
410
+ Given I k6 have the following post data:
411
+ """
412
+ {
413
+ "username": "{{TEST_USER_USERNAME}}",
414
+ "password": "{{TEST_USER_PASSWORD}}"
415
+ }
416
+ """
417
+ ```
418
+
419
+ ### Payload JSON File Steps
420
+
421
+ Load request body from a JSON file with support for both environment variables and aliases.
422
+
423
+ ```gherkin
424
+ Scenario: Use payload.json file with env vars and aliases
425
+ # First, store a token as an alias
426
+ When I k6 authenticate with the following url and request body as "user":
427
+ | endpoint | username | password |
428
+ | /login | testuser | pass123 |
429
+ And I k6 store response "accessToken" as "authToken"
430
+
431
+ # Load payload from file (supports {{VARIABLE_NAME}} and {{alias:NAME}})
432
+ Given I k6 use payload json from file "payload.json"
433
+ When I k6 make a POST request to "/api/users"
434
+
435
+ # Or combine loading and request in one step
436
+ When I k6 make a POST request to "/api/users" with payload from "data/create-user.json"
437
+ ```
438
+
439
+ **File Resolution Order:**
440
+ 1. `data/{fileName}` if exists
441
+ 2. `{fileName}` in project root if exists
442
+ 3. `payload.json` in project root as fallback
443
+
444
+ **Template Syntax:**
445
+ - `{{VARIABLE_NAME}}` - Replaced with environment variable value
446
+ - `{{alias:NAME}}` - Replaced with stored alias value
447
+
448
+ **Example payload.json:**
449
+ ```json
450
+ {
451
+ "title": "{{POST_TITLE}}",
452
+ "author": "{{alias:username}}",
453
+ "token": "{{alias:authToken}}",
454
+ "body": "Content with {{VARIABLE_NAME}} support"
455
+ }
456
+ ```
457
+
458
+ ### Alias & Storage Steps
459
+
460
+ ```gherkin
461
+ Scenario: Store and reuse values
462
+ When I k6 make a POST request to "/login"
463
+ And I k6 store response "data.accessToken" as "authToken"
464
+ Then the k6 alias "authToken" should not be empty
465
+
466
+ # Compare response against stored alias
467
+ Then the k6 response property "userName" should be alias "expectedUsername"
468
+
469
+ # Debug: print stored values
470
+ And I k6 print alias "authToken"
471
+ And I k6 print all aliases
472
+ ```
473
+
474
+ ### Response Assertion Steps
475
+
476
+ ```gherkin
477
+ # Property validation
478
+ Then the k6 response property "data.id" should be "123"
479
+ Then the k6 response property "data.token" should not be empty
480
+ Then the k6 response property "success" should be true
481
+ Then the k6 response property "deleted" should be false
482
+ Then the k6 response property "user" should have property "email"
483
+ Then the k6 response property "message" should contain "success"
484
+
485
+ # Performance assertions
486
+ Then the k6 response time should be less than "500" milliseconds
487
+ Then the k6 response time should be less than "2" seconds
488
+
489
+ # Alias comparisons
490
+ Then the k6 alias "authToken" should not be empty
491
+ Then the k6 alias "username" should be equal to "test_user"
492
+ Then the k6 response property "token" should be alias "expectedToken"
493
+ ```
494
+
495
+ ### HTTP Request Steps
496
+
497
+ ```gherkin
498
+ # GET requests
499
+ When I k6 make a GET request to "/users/1"
500
+ When I k6 make a GET request to "/users/1" with headers:
501
+ | Authorization | Content-Type |
502
+ | Bearer abc123 | application/json |
503
+
504
+ # POST requests
505
+ When I k6 make a POST request to "/users"
506
+
507
+ # PUT requests
508
+ When I k6 make a PUT request to "/users/1"
509
+ When I k6 make a PUT request to "/users/1" with body:
510
+
511
+ # PATCH requests
512
+ When I k6 make a PATCH request to "/settings"
513
+ When I k6 make a PATCH request to "/settings" with body:
223
514
  ```
224
515
 
225
- ### sample features
516
+ ### Sample Features
226
517
 
227
518
  ```gherkin
228
519
  @smoke @vus:10 @duration:1m
package/dist/cli.js CHANGED
@@ -30,6 +30,7 @@ commander_1.program
30
30
  .description("Generate k6 scripts from feature files")
31
31
  .option("-f, --features <path>", "Path to feature files", "./features")
32
32
  .option("-o, --output <path>", "Output path for generated scripts", "./generated")
33
+ .option("--exclude-tags <tags>", "Exclude scenarios by tags (comma-separated)")
33
34
  .option("-l, --lang <language>", "Output language (js or ts)", "ts")
34
35
  .option("--tags <tags>", "Filter scenarios by tags")
35
36
  .action(async (options) => {
@@ -38,12 +39,19 @@ commander_1.program
38
39
  async function generateK6Scripts(options) {
39
40
  console.log("Generating k6 scripts from feature files...");
40
41
  const parser = new feature_parser_1.FeatureParser();
41
- const features = await parser.loadAndParseFeatures(options.features);
42
- // Filter by tags if provided
42
+ const features = await parser.loadAndParseFeatures(options.features || "./features");
43
+ // Include by tags
43
44
  if (options.tags) {
44
- const tagFilters = options.tags.split(",");
45
+ const includeTags = options.tags.split(",").map(t => t.trim()).filter(t => t);
45
46
  features.forEach((feature) => {
46
- feature.scenarios = feature.scenarios.filter((scenario) => tagFilters.some((filter) => scenario.tags.includes(filter)));
47
+ feature.scenarios = feature.scenarios.filter((scenario) => includeTags.some(tag => scenario.tags.includes(tag)));
48
+ });
49
+ }
50
+ // Exclude by tags
51
+ if (options.excludeTags) {
52
+ const excludeTags = options.excludeTags.split(",").map(t => t.trim()).filter(t => t);
53
+ features.forEach((feature) => {
54
+ feature.scenarios = feature.scenarios.filter((scenario) => !excludeTags.some(tag => scenario.tags.includes(tag)));
47
55
  });
48
56
  }
49
57
  // Flatten all scenarios
@@ -52,16 +60,17 @@ async function generateK6Scripts(options) {
52
60
  const generator = new k6_script_generator_1.K6ScriptGenerator();
53
61
  const config = {
54
62
  language: options.lang,
55
- featuresDir: options.features,
56
- outputDir: options.output,
63
+ featuresDir: options.features || "./features",
64
+ outputDir: options.output || "./generated",
57
65
  includeHtmlReporter: true,
58
66
  author: "Enyimiri Chetachi Paschal (qaPaschalE)",
59
67
  version: packageJson.version,
60
68
  };
61
69
  const k6Script = generator.generateK6File(allScenarios, metadata, config);
70
+ const outputDir = options.output || "./generated";
62
71
  // Ensure output directory exists
63
- if (!fs_1.default.existsSync(options.output)) {
64
- fs_1.default.mkdirSync(options.output, { recursive: true });
72
+ if (!fs_1.default.existsSync(outputDir)) {
73
+ fs_1.default.mkdirSync(outputDir, { recursive: true });
65
74
  }
66
75
  const outputFile = `${options.output}/test.generated.${options.lang}`;
67
76
  fs_1.default.writeFileSync(outputFile, k6Script);
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"k6-script.generator.d.ts","sourceRoot":"","sources":["../../src/generators/k6-script.generator.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAErE,qBAAa,iBAAiB;IAC5B,cAAc,CACZ,SAAS,EAAE,QAAQ,EAAE,EACrB,QAAQ,EAAE,gBAAgB,EAAE,EAC5B,MAAM,EAAE,aAAa,GACpB,MAAM;IAqGT,OAAO,CAAC,eAAe;IAuBvB,OAAO,CAAC,eAAe;IA8FvB,OAAO,CAAC,eAAe;IAuBvB,OAAO,CAAC,iBAAiB;IAezB,OAAO,CAAC,gBAAgB;IASxB,OAAO,CAAC,oBAAoB;CAiG7B"}
1
+ {"version":3,"file":"k6-script.generator.d.ts","sourceRoot":"","sources":["../../src/generators/k6-script.generator.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACrE,qBAAa,iBAAiB;IAC5B,cAAc,CACZ,SAAS,EAAE,QAAQ,EAAE,EACrB,QAAQ,EAAE,gBAAgB,EAAE,EAC5B,MAAM,EAAE,aAAa,GACpB,MAAM;IAiGT,OAAO,CAAC,eAAe;IA6BvB,OAAO,CAAC,eAAe;IA8FvB,OAAO,CAAC,eAAe;IAuBvB,OAAO,CAAC,iBAAiB;IAyBzB,OAAO,CAAC,gBAAgB;IASxB,OAAO,CAAC,oBAAoB;CAuG7B"}
@@ -12,18 +12,16 @@ globalThis.lastResponse = globalThis.lastResponse || {};
12
12
  `;
13
13
  const imports = this.generateImports(config, metadata);
14
14
  const options = this.generateOptions(metadata);
15
- const testFunction = this.generateTestFunction(scenarios, metadata);
16
- // โœ… Resolve teardown based on config.language NOW (in Node.js)
15
+ const testFunction = this.generateTestFunction(scenarios, metadata, config);
16
+ // โœ… Simplified teardown: just return the tokens (no globalThis needed)
17
17
  const teardownFn = config.language === "ts"
18
18
  ? `export function teardown(tokensFromDefault: Record<string, any>) {
19
- // Capture return value from default function
20
- globalThis.exportedTokens = tokensFromDefault;
19
+ return tokensFromDefault;
21
20
  }`
22
21
  : `export function teardown(tokensFromDefault) {
23
- // Capture return value from default function
24
- globalThis.exportedTokens = tokensFromDefault;
22
+ return tokensFromDefault;
25
23
  }`;
26
- // โœ… Resolve handleSummary based on config.language NOW
24
+ // โœ… handleSummary already uses data.teardown_data โ€” no change needed
27
25
  const handleSummaryFn = config.language === "ts"
28
26
  ? `export function handleSummary(data: any): Record<string, any> {
29
27
  const reports: Record<string, any> = {
@@ -34,18 +32,18 @@ globalThis.lastResponse = globalThis.lastResponse || {};
34
32
 
35
33
  console.log('--- Summary File Write Audit ---');
36
34
 
37
- let tokens = data.teardown_data || globalThis.exportedTokens || globalThis.savedTokens || {};
38
- if (data.setup_data && !Object.keys(tokens).length) {
39
- tokens = data.setup_data;
40
- }
41
-
35
+ const tokens = data.teardown_data || {};
42
36
  const keys = Object.keys(tokens).filter(k => k.endsWith('.json'));
43
37
  console.log('Tokens found:', keys.length > 0 ? keys.join(', ') : 'None');
44
38
 
45
39
  for (const [path, tokenValue] of Object.entries(tokens)) {
46
40
  if (path.endsWith('.json')) {
47
41
  const fullPath = path.startsWith('./') ? path : \`./\${path}\`;
48
- reports[fullPath] = JSON.stringify(typeof tokenValue === 'string' ? { access_token: tokenValue } : tokenValue, null, 2);
42
+ reports[fullPath] = JSON.stringify(
43
+ typeof tokenValue === 'string' ? { access_token: tokenValue } : tokenValue,
44
+ null,
45
+ 2
46
+ );
49
47
  console.log(\`โœ… Writing: \${fullPath}\`);
50
48
  }
51
49
  }
@@ -61,25 +59,24 @@ globalThis.lastResponse = globalThis.lastResponse || {};
61
59
 
62
60
  console.log('--- Summary File Write Audit ---');
63
61
 
64
- let tokens = data.teardown_data || globalThis.exportedTokens || globalThis.savedTokens || {};
65
- if (data.setup_data && !Object.keys(tokens).length) {
66
- tokens = data.setup_data;
67
- }
68
-
62
+ const tokens = data["root_group::teardown"] || {};
69
63
  const keys = Object.keys(tokens).filter(k => k.endsWith('.json'));
70
64
  console.log('Tokens found:', keys.length > 0 ? keys.join(', ') : 'None');
71
65
 
72
66
  for (const [path, tokenValue] of Object.entries(tokens)) {
73
67
  if (path.endsWith('.json')) {
74
- const fullPath = path.startsWith('./') ? path : \`./\${path}\`;
75
- reports[fullPath] = JSON.stringify(typeof tokenValue === 'string' ? { access_token: tokenValue } : tokenValue, null, 2);
76
- console.log(\`โœ… Writing: \${fullPath}\`);
68
+ // โœ… Use path directly โ€” no ./ prefix
69
+ reports[path] = JSON.stringify(
70
+ typeof tokenValue === 'string' ? { access_token: tokenValue } : tokenValue,
71
+ null,
72
+ 2
73
+ );
74
+ console.log(\`โœ… Writing: \${path}\`);
77
75
  }
78
76
  }
79
77
 
80
78
  return reports;
81
79
  }`;
82
- // โœ… Now embed the resolved strings into the final output
83
80
  return `
84
81
  ${header}
85
82
  ${imports}
@@ -87,7 +84,6 @@ ${imports}
87
84
  ${options}
88
85
 
89
86
  export function setup() {
90
- // We must return an object here to initialize the "data" channel
91
87
  return { v: Date.now() };
92
88
  }
93
89
 
@@ -98,19 +94,28 @@ ${teardownFn}
98
94
  ${handleSummaryFn}
99
95
  `;
100
96
  }
101
- generateImports(config, metadata) {
97
+ generateImports(config, meta) {
102
98
  const ext = config.language === "ts" ? "ts" : "js";
103
- const hasBrowser = metadata.some((m) => m.tags?.includes("browser"));
99
+ const hasBrowser = meta.some(m => m.tags?.includes("browser")); // โ† now used!
104
100
  const baseImports = [
105
101
  'import http from "k6/http";',
106
102
  'import { check, sleep, group } from "k6";',
107
- 'import { htmlReport } from "https://raw.githubusercontent.com/benc-uk/k6-reporter/main/dist/bundle.js";',
108
- 'import { textSummary } from "https://jslib.k6.io/k6-summary/0.1.0/index.js";',
109
103
  ];
104
+ // Add @ts-ignore for remote modules in TS mode
105
+ if (config.language === "ts") {
106
+ baseImports.push('// @ts-ignore: k6 resolves remote modules at runtime');
107
+ baseImports.push('import { htmlReport } from "https://raw.githubusercontent.com/benc-uk/k6-reporter/main/dist/bundle.js";');
108
+ baseImports.push('// @ts-ignore: k6 resolves remote modules at runtime');
109
+ baseImports.push('import { textSummary } from "https://jslib.k6.io/k6-summary/0.1.0/index.js";');
110
+ }
111
+ else {
112
+ baseImports.push('import { htmlReport } from "https://raw.githubusercontent.com/benc-uk/k6-reporter/main/dist/bundle.js";');
113
+ baseImports.push('import { textSummary } from "https://jslib.k6.io/k6-summary/0.1.0/index.js";');
114
+ }
110
115
  if (hasBrowser) {
111
116
  baseImports.push('import { browser } from "k6/browser";');
112
117
  }
113
- baseImports.push(`import * as steps from "../steps/sample.steps.${ext}"; `);
118
+ baseImports.push(`import * as steps from "../steps/sample.steps.${ext}";`);
114
119
  return baseImports.join("\n");
115
120
  }
116
121
  generateOptions(metadata) {
@@ -233,6 +238,14 @@ ${handleSummaryFn}
233
238
  .replace(/[^a-zA-Z0-9\s]/g, " ")
234
239
  .trim();
235
240
  const words = clean.split(/\s+/).filter(w => w);
241
+ // Check if the step starts with "k6" or contains "k6" as a keyword
242
+ // If text contains "k6", move it to the front
243
+ const k6Index = words.findIndex(w => w.toLowerCase() === 'k6');
244
+ if (k6Index > 0) {
245
+ // Remove 'k6' from its position and put it at the front
246
+ const k6Word = words.splice(k6Index, 1)[0];
247
+ words.unshift(k6Word);
248
+ }
236
249
  return words.map((w, i) => i === 0 ? w.toLowerCase() : w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join("");
237
250
  }
238
251
  extractArguments(text) {
@@ -242,7 +255,7 @@ ${handleSummaryFn}
242
255
  return matches.join(", ");
243
256
  }
244
257
  // Inside K6ScriptGenerator.ts
245
- generateTestFunction(scenarios, metadata) {
258
+ generateTestFunction(scenarios, metadata, config) {
246
259
  const hasAnyBrowser = metadata.some((m) => m.tags?.includes("browser"));
247
260
  const lines = [];
248
261
  // Open browser if any scenario uses it
@@ -252,7 +265,7 @@ ${handleSummaryFn}
252
265
  lines.push(` page = await browser.newPage();`);
253
266
  lines.push(` console.log('Browser page opened once for all scenarios');`);
254
267
  // Set fallback base URL only if needed
255
- lines.push(` steps.theBaseUrlIs("https://demoqa.com");`);
268
+ lines.push(` steps.k6TheBaseUrlIs("https://demoqa.com");`);
256
269
  lines.push(` console.log('Fallback baseUrl set to:', globalThis.baseUrl || 'not set');`);
257
270
  lines.push(``);
258
271
  }
@@ -271,7 +284,7 @@ ${handleSummaryFn}
271
284
  const args = argsStr ? argsStr.split(/,\s*/).filter(Boolean) : [];
272
285
  const needsPage = [
273
286
  "navigate", "click", "see", "fill", "type", "press", "waitfor", "wait", "locator",
274
- "select", "title", "url", "element", "shouldsee", "shouldnotsee"
287
+ "select", "title", "url", "element", "shouldsee", "shouldnotsee", "button", "find"
275
288
  ].some(kw => name.toLowerCase().includes(kw)) &&
276
289
  !["thebaseurlis", "thebaseurl"].some(kw => name.toLowerCase().includes(kw));
277
290
  const params = [];
@@ -286,9 +299,12 @@ ${handleSummaryFn}
286
299
  lines.push(` ${callPrefix}steps.${name}(${params.join(", ")});`);
287
300
  });
288
301
  lines.push(` } catch (err) {`);
289
- lines.push(` console.error('Error in ${scenario.name}:', err);`);
290
- lines.push(` console.error('Stack:', err && err.stack ? err.stack : 'No stack');`);
291
- lines.push(` }`);
302
+ const stackAccessCode = config.language === "ts"
303
+ ? "(err as any)?.stack || 'No stack'"
304
+ : "err?.stack || 'No stack'";
305
+ lines.push(` console.error('Error in ${scenario.name}:', err);`);
306
+ lines.push(` console.error('Stack:', ${stackAccessCode});`);
307
+ lines.push(` }`);
292
308
  }
293
309
  else {
294
310
  // Non-browser (HTTP/API) steps
@@ -325,7 +341,9 @@ ${handleSummaryFn}
325
341
  return `
326
342
  export default async function () {
327
343
  ${lines.join("\n")}
328
- return globalThis.savedTokens;
344
+ console.log('๐Ÿ” Final savedTokens:', JSON.stringify(globalThis.savedTokens));
345
+
346
+ return globalThis.savedTokens || {};
329
347
  }`;
330
348
  }
331
349
  }