Skip to content

Commit 7066c1c

Browse files
authored
feat: Conver .http load tests to k6.io .js test files (#26)
* feat: added simple k6 converter * fix(cli): require -f flag when not offline
1 parent 4e7739d commit 7066c1c

7 files changed

Lines changed: 664 additions & 0 deletions

File tree

CLAUDE.md

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,152 @@ github.com/deicon/httprunner/
3737
./httprunner -u 20 -i 10 -d 1000 -f requests.http
3838
```
3939

40+
## K6 Export
41+
42+
httprunner can convert .http files to [k6](https://k6.io/) JavaScript test scripts, enabling you to leverage k6's advanced load testing features while maintaining your existing .http request definitions.
43+
44+
### Usage
45+
46+
```bash
47+
./httprunner -convert k6 -f requests.http > test.js
48+
```
49+
50+
### Parameters
51+
52+
- `-convert k6`: Converts the .http file to k6 format
53+
- `-f filename`: Source .http file to convert (required)
54+
- `-i n`: Number of iterations for the k6 script (default: 1)
55+
- `-d n`: Delay between requests in milliseconds (default: 0)
56+
- `-e filename`: .env file containing environment variable defaults
57+
58+
### Example
59+
60+
```bash
61+
# Basic conversion
62+
./httprunner -convert k6 -f requests.http > test.js
63+
64+
# With custom iterations and delay
65+
./httprunner -convert k6 -i 100 -d 500 -f requests.http > load-test.js
66+
67+
# With environment variable defaults
68+
./httprunner -convert k6 -e .env -f requests.http > test.js
69+
70+
# Run the generated k6 script
71+
k6 run test.js
72+
```
73+
74+
### How It Works
75+
76+
The converter transforms your .http file into a k6-compatible JavaScript script:
77+
78+
1. **Template Variables**: Converts httprunner placeholders `{{.VARIABLE}}` to k6 template literals `${vars.VARIABLE}`
79+
2. **Environment Defaults**: Loads defaults from the .env file (if provided) for discovered placeholder variables
80+
3. **Variable Override**: Generated scripts use k6's `__ENV` to allow runtime variable overrides via `-e KEY=VALUE`
81+
4. **Pre/Post Scripts**: Preserves JavaScript pre-request and post-request scripts with appropriate context
82+
5. **Request Chaining**: Maintains request order and dependencies through global variable sharing
83+
84+
### Generated Script Structure
85+
86+
The k6 script includes:
87+
88+
- **Options block**: Configures iterations based on `-i` parameter
89+
- **Defaults object**: Contains environment variable defaults from .env file
90+
- **Vars proxy**: Enables runtime variable override via k6's `__ENV` or script-level assignment
91+
- **Client API**: Provides `client.global.set/get` for variable management compatible with httprunner scripts
92+
- **Request execution**: Converts HTTP requests to appropriate k6 `http.*` method calls
93+
- **Sleep delays**: Adds `sleep()` calls between requests based on `-d` parameter
94+
95+
### Example Conversion
96+
97+
**Input (requests.http):**
98+
```
99+
###
100+
# @name Create User
101+
> {%
102+
client.global.set("timestamp", Date.now().toString())
103+
%}
104+
105+
POST {{.BASEURL}}/api/users
106+
Authorization: Bearer {{.TOKEN}}
107+
Content-Type: application/json
108+
109+
{
110+
"username": "testuser",
111+
"timestamp": "{{.timestamp}}"
112+
}
113+
114+
> {%
115+
var jsonData = response.body
116+
client.global.set("userId", jsonData.id)
117+
%}
118+
119+
###
120+
# @name Get User
121+
GET {{.BASEURL}}/api/users/{{.userId}}
122+
Authorization: Bearer {{.TOKEN}}
123+
```
124+
125+
**Output (k6 script):**
126+
```javascript
127+
import http from 'k6/http';
128+
import { sleep } from 'k6';
129+
130+
export const options = {
131+
iterations: 1,
132+
};
133+
134+
const defaults = {BASEURL: 'http://localhost:8080', TOKEN: 'secret123'};
135+
const vars = new Proxy(Object.assign({}, defaults), {
136+
get: (t, p) => (typeof __ENV !== 'undefined' && __ENV[p] !== undefined ? __ENV[p] : t[p]),
137+
set: (t, p, v) => { t[p] = v; return true; },
138+
});
139+
140+
const client = { global: { set: (k, v) => { vars[k] = v; }, get: (k) => vars[k] } };
141+
function safeJson(s) { try { return JSON.parse(s); } catch (_) { return s; } }
142+
143+
export default function () {
144+
let response;
145+
// Pre-script
146+
client.global.set("timestamp", Date.now().toString())
147+
148+
const httpRes_0 = http.post(`${vars.BASEURL}/api/users`, `{
149+
"username": "testuser",
150+
"timestamp": "${vars.timestamp}"
151+
}`, { headers: {'Authorization': `Bearer ${vars.TOKEN}`, 'Content-Type': 'application/json'} });
152+
const response_0 = { body: safeJson(httpRes_0.body), status: httpRes_0.status, headers: httpRes_0.headers };
153+
response = response_0;
154+
// Post-script
155+
var jsonData = response.body
156+
client.global.set("userId", jsonData.id)
157+
158+
const httpRes_1 = http.get(`${vars.BASEURL}/api/users/${vars.userId}`, { headers: {'Authorization': `Bearer ${vars.TOKEN}`} });
159+
const response_1 = { body: safeJson(httpRes_1.body), status: httpRes_1.status, headers: httpRes_1.headers };
160+
response = response_1;
161+
}
162+
```
163+
164+
### Running k6 Tests
165+
166+
```bash
167+
# Run with default environment variables from .env
168+
k6 run test.js
169+
170+
# Override environment variables at runtime
171+
k6 run -e BASEURL=https://api.example.com -e TOKEN=xyz123 test.js
172+
173+
# Scale up with virtual users
174+
k6 run --vus 50 --duration 30s test.js
175+
176+
# Use k6 cloud for distributed testing
177+
k6 cloud test.js
178+
```
179+
180+
### Limitations
181+
182+
- **Checks**: httprunner `client.check()` calls are not automatically converted to k6 checks. Manual adaptation may be required.
183+
- **Metrics**: httprunner's `client.metrics` API is not available in k6. Use k6's native metrics and custom metrics instead.
184+
- **Lifecycle requests**: Requests marked with lifecycle stages (setup/teardown) are skipped in conversion with comments.
185+
40186
## HTTP Request File Format
41187

42188
Requests are separated by `###` and follow this format:

cmd/httprunner/convert_k6.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package main
2+
3+
import (
4+
k6conv "github.com/deicon/httprunner/converter/k6"
5+
chttp "github.com/deicon/httprunner/http"
6+
)
7+
8+
// requireK6Generate is isolated to avoid import when not used
9+
func requireK6Generate(requests []chttp.Request, opts struct {
10+
Iterations, DelayMS int
11+
EnvFile string
12+
}) (string, error) {
13+
return k6conv.Generate(requests, k6conv.Options{
14+
Iterations: opts.Iterations,
15+
DelayMS: opts.DelayMS,
16+
EnvFile: opts.EnvFile,
17+
})
18+
}

cmd/httprunner/main.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ func main() {
2929
offset := flag.Int("offset", 0, "Max random startup delay per VU in milliseconds")
3030
requestFile := flag.String("f", "", ".http file containing http requests")
3131
envFile := flag.String("e", "", ".env file containing environment variables")
32+
convert := flag.String("convert", "", "Convert input file to target format (e.g., k6) and print to stdout")
3233
reportFormat := flag.String("report", "console", "Report format: console, html, csv, json")
3334
reportOutput := flag.String("output", "results", "Output directory for results and reports")
3435
reportDetail := flag.String("detail", "summary", "Report detail level: summary, goroutine, iteration, full")
@@ -50,6 +51,43 @@ func main() {
5051
os.Exit(1)
5152
}
5253

54+
// Conversion mode: when -convert is provided, parse and emit converted output
55+
if *convert != "" {
56+
requests, err := parser.Parse(*requestFile)
57+
if err != nil {
58+
fmt.Printf("Error parsing file: %v\n", err)
59+
os.Exit(1)
60+
}
61+
62+
switch strings.ToLower(*convert) {
63+
case "k6":
64+
// Generate K6 script
65+
genOpts := struct {
66+
Iterations int
67+
DelayMS int
68+
EnvFile string
69+
}{
70+
Iterations: *iterations,
71+
DelayMS: *delay,
72+
EnvFile: *envFile,
73+
}
74+
// Import and call converter
75+
k6gen, err := func() (string, error) {
76+
// Local import to avoid unused import when not converting
77+
return requireK6Generate(requests, genOpts)
78+
}()
79+
if err != nil {
80+
fmt.Printf("Error generating k6 script: %v\n", err)
81+
os.Exit(1)
82+
}
83+
fmt.Print(k6gen)
84+
return
85+
default:
86+
fmt.Printf("Error: unknown converter '%s'\n", *convert)
87+
os.Exit(1)
88+
}
89+
}
90+
5391
// If -csv shorthand is provided, force CSV formatting and summary detail;
5492
// also prefer printing to stdout for easy redirection.
5593
forceStdout := false

0 commit comments

Comments
 (0)