Writing k6 Scripts

Every Straden script is a folder on disk with a script.js entry point. You can create it with the Test Agent, edit it with the Script Agent, or write it yourself in the built-in editor. Straden runs it with the bundled k6 v2.0.0.


The minimum script

JAVASCRIPT
import http from 'k6/http'
import { check, sleep } from 'k6'

const BASE = __ENV.TARGET_URL

export const options = {
  vus: 10,
  duration: '1m',
  thresholds: {
    http_req_failed: ['rate<0.01'],
    http_req_duration: ['p(95)<500'],
  },
}

export default function () {
  const res = http.get(`${BASE}/health`)
  check(res, { 'status is 200': (r) => r.status === 200 })
  sleep(1)
}

Requirements checked by validation:

  • Imports from k6/http.
  • Exports a default function.
  • Uses check() assertions.
  • Declares load profile and thresholds in export const options.
  • Passes k6 inspect.

TARGET_URL

The test's target URL is injected as the TARGET_URL environment variable. Always build requests from __ENV.TARGET_URL rather than hard-coding a host, so the same script works when you change the target.

Scenarios

Straden encourages one scenario per script, which keeps runs comparable over time.

JAVASCRIPT
// load-test/script.js
export const options = {
  scenarios: {
    load: {
      executor: 'ramping-vus',
      startVUs: 0,
      stages: [
        { duration: '2m', target: 50 },
        { duration: '5m', target: 50 },
        { duration: '1m', target: 0 },
      ],
      gracefulRampDown: '30s',
    },
  },
  thresholds: {
    http_req_duration: ['p(95)<400', 'p(99)<1000'],
    checks: ['rate>0.99'],
  },
}
TypePurposeTypical shape
SmokeDoes it work at all?1–2 VUs, 30s
LoadExpected trafficRamp to normal VUs, hold 5–15m
StressWhere does it break?Keep ramping past normal
SpikeSudden burstsJump from low to very high VUs
SoakLeaks over timeNormal load for hours

Straden reads the options before each run and stores the resolved config (VUs, duration, stages) with the run.

Straden reads options with a lightweight parser to drive the progress bar. It understands a literal export const options = { … } with numeric vus / iterations and quoted durations ('30s', '5m', '1h'), including the sum of stages. Options built dynamically still run fine in k6. The progress bar just can't estimate them.

Multi-file scripts

Split helpers into modules inside the script folder:

load-test/
├── script.js
├── lib/
│   └── auth.js
└── data/
    └── users.json
JAVASCRIPT
import { login } from './lib/auth.js'
const users = JSON.parse(open('./data/users.json'))

Thresholds and run status

k6's exit code decides the result. If every threshold passes the run is passed; if any threshold fails it's failed (exit 99). Straden adds p(99) to the summary trend stats, so p(99) thresholds work out of the box.

setup() and teardown()

k6 kills setup() and teardown() after 60 seconds by default — and the target is often still saturated when teardown runs. If they make HTTP requests:

JAVASCRIPT
export const options = {
  setupTimeout: '3m',
  teardownTimeout: '5m',
}

export function teardown(data) {
  // Batch cleanup in bounded chunks instead of one request per item
  for (let i = 0; i < data.ids.length; i += 50) {
    http.batch(data.ids.slice(i, i + 50).map((id) => ['DELETE', `${__ENV.TARGET_URL}/items/${id}`]))
  }
}

A teardown timeout after an otherwise clean run is reported as passed with a warning.

Good practice

  • Add realistic sleep() think time between requests.
  • Name checks descriptively — they show up in the summary and insights.
  • Tag requests ({ tags: { name: 'GET /products/:id' } }) so dynamic URLs group together.
  • Prefer check() over console.log; console output goes to the run log and is noisy at scale.