Moving from k6: turning k6 scripts into Spitfire tests
You do not need to rewrite your k6 scripts. Spitfire reads a k6 script (.js, .ts), turns it into a Spitfire test and opens it in the editor: the load model, scenarios, HTTP requests, checks, extraction from responses and thresholds. The script is only read, never run; everything without an equivalent is listed in a report with its line.
Steps
- On the Tests page, press Open file and pick the
.jsfile (up to 4 MB). The file is only read, never run. - The report above the editor says what was converted and how: not converted (missing from the test), check (converted approximately), note (how it was converted). Every line comes with its place in the source file; nothing is dropped silently.
- Review the steps, thresholds and variables; Try sends one iteration of the selected scenario and shows every request and response. Then save.
What is converted, and how
| k6 | Spitfire |
|---|---|
options.vus / duration / stages | Load model: constant or ramping VUs |
options.scenarios | One scenario each; executor, startTime and every k6 executor under the same name |
options.thresholds | Thresholds, same syntax (p(95)<500, abortOnFail, tags as step/scenario) |
http.get / post / put / patch / del / request | HTTP step: method, URL, headers, body (JSON or form) |
check(res, {...}) | Checks: status, text in the body, JSONPath, duration |
res.json('a.b') · JSON.parse(res.body) · res.headers['X'] | Extraction from the response (JSONPath or header); {{name}} in later requests |
sleep(n) · randomIntBetween | Think time (fixed or a range) |
group(name, fn) | The group name prefixes the step names |
__ENV.BASE || 'https://…' | A variable with its default |
__VU · __ITER | {{__VU}} · {{__ITER}} |
setup() | Its returned values become variables; its requests run once per VU as the first steps (once per test in k6) |
POST, PUT, PATCH and DELETE requests are marked as changing data, so a run asks for confirmation before it starts. Untick it in the editor on read-only ones such as a login or a search; the report reminds you of this too.
Threshold expressions come over with the same syntax (p(95)<500, rate<0.01, abortOnFail), and the CI exit codes of spitfire cloud run are k6-compatible (99 thresholds failed, 97 a threshold aborted the run): the gate in your pipeline stays, only the command changes. More: performance testing in CI/CD.
Example: before and after
A small script that signs in, takes a token, lists items with it, and has two checks and a p95 threshold:
import http from 'k6/http';
import { check, sleep } from 'k6';
export const options = {
stages: [
{ duration: '1m', target: 50 },
{ duration: '5m', target: 50 },
],
thresholds: { http_req_duration: ['p(95)<500'] },
};
const BASE = __ENV.BASE || 'https://shop.example.com';
export default function () {
const login = http.post(`${BASE}/api/login`, JSON.stringify({ user: 'demo', pass: 'demo' }), {
headers: { 'Content-Type': 'application/json' },
});
check(login, { 'login 200': (r) => r.status === 200 });
const token = login.json('token');
const items = http.get(`${BASE}/api/items`, { headers: { Authorization: `Bearer ${token}` } });
check(items, { 'items 200': (r) => r.status === 200 });
sleep(1);
}The test the converter wrote (the general options block left out). __ENV.BASE became a variable with its default, login.json('token') a JSONPath extraction and sleep(1) think time:
{
"name": "checkout",
"variables": {"BASE": "https://shop.example.com"},
"scenarios": [
{
"name": "default",
"executor": {
"type": "ramping-vus",
"startVUs": 1,
"stages": [{"duration": "1m0s", "target": 50}, {"duration": "5m0s", "target": 50}]
},
"steps": [
{
"id": "post_api_login",
"name": "POST /api/login",
"protocol": "http",
"request": {
"method": "POST",
"url": "{{BASE}}/api/login",
"headers": [{"key": "Content-Type", "value": "application/json"}],
"body": {"type": "json", "content": "{\"user\":\"demo\",\"pass\":\"demo\"}"},
"modifiesData": true
},
"extract": [{"var": "token", "from": "jsonpath", "expr": "$.token"}],
"checks": [{"name": "login 200", "type": "status", "op": "eq", "value": 200}]
},
{
"id": "get_api_items",
"name": "GET /api/items",
"protocol": "http",
"request": {
"method": "GET",
"url": "{{BASE}}/api/items",
"headers": [{"key": "Authorization", "value": "Bearer {{token}}"}],
"body": {"type": "none"}
},
"checks": [{"name": "items 200", "type": "status", "op": "eq", "value": 200}],
"thinkTime": {"min": "1s"}
}
]
}
],
"thresholds": [{"metric": "req_duration", "expr": "p(95)<500"}]
}What is not converted
These are listed in the report with their line; you set up the Spitfire equivalent by hand before saving:
- Loops and conditions (
for,if): Spitfire runs the steps in order; every iteration is the whole flow. - Data files (
SharedArray,open()): upload the CSV under Data files and bind it to the test; columns become variables such as{{users.email}}. - Non-HTTP requests (WebSocket, gRPC modules): Spitfire supports them built in; add the step in the editor. Scripts made only of these are counted separately as having nothing to convert.
- Custom metrics and free-form code (Trend, Counter, helper functions): no equivalent; per-step timings, checks and thresholds cover most cases.
A whole folder
Select several files in Open file and the bulk import page converts, validates and lists each with its report; you save the ones you pick with one button. On the command line one command converts a folder; one test file is written per script and every report line goes to the CSV (file, test, line, level, message):
spitfire convert ./tests -o spitfire-tests/ -r report.csvSpitfire installs on Docker or Kubernetes with one command; every testing feature and protocol is open in the free edition.