K6
k6: load testing, performance testing, stress testing, soak testing, thresholds, checks, scenarios, browser testing, CI integration
You are an expert in using k6 for load testing, performance testing, and reliability validation of APIs and web applications. ## Key Points - Define thresholds for every test so CI pipelines can automatically pass or fail based on performance budgets; never rely on manually inspecting output after each run. - Use the `setup()` function for one-time operations like authentication or test data creation, and `teardown()` for cleanup; these run outside the VU loop and do not inflate metrics. - Start with a realistic baseline (the `ramping-vus` executor) before moving to stress or spike tests; establish what "normal" looks like so regressions are detectable.
skilldb get testing-services-skills/K6Full skill: 220 linesk6 — Testing
You are an expert in using k6 for load testing, performance testing, and reliability validation of APIs and web applications.
Core Philosophy
Overview
k6 is an open-source load testing tool built by Grafana Labs. Tests are written in JavaScript/TypeScript and executed by a high-performance Go runtime, so a single machine can simulate thousands of concurrent virtual users without the overhead of real browser instances. k6 supports HTTP, WebSocket, gRPC, and (via the browser module) real Chromium-based browser interactions. It is designed to be developer-centric: tests live in version control, run in CI pipelines, and produce machine-readable output for dashboards and automated pass/fail decisions via thresholds.
Setup & Configuration
Installation
# macOS
brew install k6
# Windows (winget)
winget install k6 --source winget
# Docker
docker run --rm -i grafana/k6 run - <script.js
# npm wrapper (for JS/TS projects)
npm install -D @grafana/k6
Basic test structure
// tests/load/api-load.js
import http from "k6/http";
import { check, sleep } from "k6";
export const options = {
// Ramp up to 50 users over 30s, hold for 1m, ramp down
stages: [
{ duration: "30s", target: 50 },
{ duration: "1m", target: 50 },
{ duration: "10s", target: 0 },
],
// Automated pass/fail criteria
thresholds: {
http_req_duration: ["p(95)<300"], // 95th percentile under 300ms
http_req_failed: ["rate<0.01"], // less than 1% errors
},
};
export default function () {
const res = http.get("https://api.example.com/users");
check(res, {
"status is 200": (r) => r.status === 200,
"response time < 500ms": (r) => r.timings.duration < 500,
"body has users array": (r) => JSON.parse(r.body).users.length > 0,
});
sleep(1); // simulate think time between requests
}
Running tests
# Run locally
k6 run tests/load/api-load.js
# Run with environment variables
k6 run -e BASE_URL=https://staging.example.com tests/load/api-load.js
# Output results to JSON for post-processing
k6 run --out json=results.json tests/load/api-load.js
# Stream results to Grafana Cloud k6
k6 cloud run tests/load/api-load.js
Core Patterns
Scenario-based testing (multiple workloads)
// tests/load/mixed-workload.js
import http from "k6/http";
import { check, sleep } from "k6";
export const options = {
scenarios: {
browse_api: {
executor: "ramping-vus",
startVUs: 0,
stages: [
{ duration: "1m", target: 100 },
{ duration: "3m", target: 100 },
{ duration: "30s", target: 0 },
],
exec: "browseEndpoint",
},
create_orders: {
executor: "constant-arrival-rate",
rate: 10, // 10 iterations per timeUnit
timeUnit: "1s", // = 10 RPS
duration: "4m",
preAllocatedVUs: 20,
maxVUs: 50,
exec: "createOrder",
},
},
thresholds: {
"http_req_duration{scenario:browse_api}": ["p(95)<200"],
"http_req_duration{scenario:create_orders}": ["p(95)<500"],
},
};
const BASE_URL = __ENV.BASE_URL || "https://api.example.com";
export function browseEndpoint() {
const res = http.get(`${BASE_URL}/products`);
check(res, { "browse 200": (r) => r.status === 200 });
sleep(Math.random() * 2);
}
export function createOrder() {
const payload = JSON.stringify({
productId: "prod-001",
quantity: Math.ceil(Math.random() * 5),
});
const params = { headers: { "Content-Type": "application/json" } };
const res = http.post(`${BASE_URL}/orders`, payload, params);
check(res, { "order created": (r) => r.status === 201 });
}
Authentication and headers
// tests/load/authenticated.js
import http from "k6/http";
import { check } from "k6";
export function setup() {
// Runs once before all VUs; return value is passed to default function
const loginRes = http.post("https://api.example.com/auth/login", JSON.stringify({
email: "loadtest@example.com",
password: __ENV.TEST_PASSWORD,
}), { headers: { "Content-Type": "application/json" } });
const token = JSON.parse(loginRes.body).accessToken;
return { token };
}
export default function (data) {
const params = {
headers: {
Authorization: `Bearer ${data.token}`,
"Content-Type": "application/json",
},
};
const res = http.get("https://api.example.com/me", params);
check(res, { "authenticated 200": (r) => r.status === 200 });
}
Custom metrics and tagging
import http from "k6/http";
import { Trend, Counter } from "k6/metrics";
const orderLatency = new Trend("order_latency");
const orderErrors = new Counter("order_errors");
export const options = {
thresholds: {
order_latency: ["p(99)<1000"],
order_errors: ["count<10"],
},
};
export default function () {
const res = http.get("https://api.example.com/orders", {
tags: { endpoint: "list_orders" }, // tag for filtering in dashboards
});
orderLatency.add(res.timings.duration);
if (res.status !== 200) {
orderErrors.add(1);
}
}
Best Practices
- Define thresholds for every test so CI pipelines can automatically pass or fail based on performance budgets; never rely on manually inspecting output after each run.
- Use the
setup()function for one-time operations like authentication or test data creation, andteardown()for cleanup; these run outside the VU loop and do not inflate metrics. - Start with a realistic baseline (the
ramping-vusexecutor) before moving to stress or spike tests; establish what "normal" looks like so regressions are detectable.
Common Pitfalls
- Writing k6 scripts that import Node.js modules (fs, path, axios); k6 uses its own JavaScript runtime (not Node.js), so only k6-specific modules and ES-compatible code work. Use
k6/httpinstead of fetch/axios. - Setting unrealistically high VU counts on a single load generator machine without monitoring its own CPU and network; if the load generator is saturated, the test measures the generator's limits, not the target system's performance.
Anti-Patterns
Using the service without understanding its pricing model. Cloud services bill differently — per request, per GB, per seat. Deploying without modeling expected costs leads to surprise invoices.
Hardcoding configuration instead of using environment variables. API keys, endpoints, and feature flags change between environments. Hardcoded values break deployments and leak secrets.
Ignoring the service's rate limits and quotas. Every external API has throughput limits. Failing to implement backoff, queuing, or caching results in dropped requests under load.
Treating the service as always available. External services go down. Without circuit breakers, fallbacks, or graceful degradation, a third-party outage becomes your outage.
Coupling your architecture to a single provider's API. Building directly against provider-specific interfaces makes migration painful. Wrap external services in thin adapter layers.
Install this skill directly: skilldb add testing-services-skills
Related Skills
Cypress
"Cypress: end-to-end/component testing, commands, intercept (network mocking), fixtures, custom commands, CI, retries"
MSW
"MSW (Mock Service Worker): API mocking, request handlers, rest/graphql, browser/node, setupServer, network-level interception"
Pact
Pact: consumer-driven contract testing, provider verification, pact broker, API compatibility, microservice integration testing
Playwright
"Playwright: end-to-end testing, browser automation, selectors, assertions, fixtures, page objects, visual regression, CI, codegen"
Storybook Test
Storybook Test: component testing via play functions, interaction testing, accessibility checks, visual regression, test-runner, portable stories
Testing Library
"Testing Library: React/DOM testing, queries (getBy/findBy/queryBy), user events, screen, waitFor, render, accessibility-first"