Writing k6 scripts

loadtest.dev generates the k6 script from your plan, so you never write one to use the service. This page is a k6 reference for the scripts in the guides and for tests you run with k6 directly.

The basics

Every k6 test has two parts: options that configure the test, and a default function that runs once per virtual user iteration.

basics.js
import http from 'k6/http';

// Options configure the test
export const options = {
  vus: 10,         // 10 virtual users
  duration: '30s', // run for 30 seconds
};

// This runs once per VU iteration
export default function () {
  http.get('https://api.example.com/health');
}

Checks: assert responses

Checks validate that responses meet your expectations. They do not stop the test on failure; they track the pass rate so you can set thresholds on it.

checks.js
import http from 'k6/http';
import { check } from 'k6';

export default function () {
  const res = http.get('https://api.example.com/users');
  check(res, {
    'status is 200': (r) => r.status === 200,
    'body has users': (r) => JSON.parse(r.body).length > 0,
    'response < 300ms': (r) => r.timings.duration < 300,
  });
}

Thresholds: pass or fail

Thresholds define SLAs. If any threshold is breached the test fails, which is what makes a load test usable in CI.

thresholds.js
export const options = {
  vus: 50,
  duration: '5m',
  thresholds: {
    // 95% of requests must complete under 500ms
    http_req_duration: ['p(95)<500'],
    // Error rate must be below 1%
    http_req_failed: ['rate<0.01'],
    // 99% of checks must pass
    checks: ['rate>0.99'],
  },
};

Stages: ramp over time

Stages increase and decrease load gradually, like real ramp-ups and ramp-downs. With the ramping-arrival-rate executor the targets are requests per second instead of users, which is how loadtest.dev runs every plan.

stages.js
export const options = {
  stages: [
    { duration: '1m', target: 20 },  // Warm up to 20 VUs
    { duration: '3m', target: 100 }, // Ramp to 100 VUs
    { duration: '2m', target: 100 }, // Hold at 100 VUs
    { duration: '1m', target: 0 },   // Ramp down to 0
  ],
};

POST requests with JSON

Send POST, PUT, PATCH, and DELETE requests with custom headers and JSON payloads.

post.js
import http from 'k6/http';
import { check } from 'k6';

export default function () {
  const payload = JSON.stringify({
    name: 'Test User',
    email: 'test@example.com',
  });
  const params = {
    headers: { 'Content-Type': 'application/json' },
  };
  const res = http.post('https://api.example.com/users', payload, params);
  check(res, {
    created: (r) => r.status === 201,
  });
}

Groups: organize a flow

Groups organize a sequence of requests that make up one user flow, such as browse, add to cart, and check out.

groups.js
import http from 'k6/http';
import { group, check, sleep } from 'k6';

export default function () {
  group('Browse products', () => {
    const res = http.get('https://shop.example.com/products');
    check(res, { listed: (r) => r.status === 200 });
  });
  group('View product', () => {
    const res = http.get('https://shop.example.com/products/1');
    check(res, { loaded: (r) => r.status === 200 });
  });
  group('Add to cart', () => {
    const res = http.post(
      'https://shop.example.com/cart',
      JSON.stringify({ productId: 1, qty: 1 }),
      { headers: { 'Content-Type': 'application/json' } },
    );
    check(res, { added: (r) => r.status === 200 });
  });
  sleep(1);
}
loadtest.dev — Part of the Narduk Enterprises network