Introduction to K6 Load Testing
K6 is an open-source, developer-centric load testing tool built for modern engineering teams. It enables you to write test scripts in JavaScript, run them from your terminal or CI pipeline, and get rich performance metrics out of the box. Whether you are validating an API, stressing a microservice, or checking WebSocket stability under load, K6 gives you the flexibility and observability you need without heavyweight infrastructure.
What is K6?
🚀 Deploy your AI agent in 10 minutes
Managed Hermes hosting. Zero DevOps. 100M tokens/mo included.
Try it free →K6 is a high-performance load testing tool written in Go, with test scripts authored in JavaScript. It sits in the same space as tools like JMeter or Locust, but distinguishes itself by being developer-friendly, scriptable, and easily integrated into modern CI/CD workflows. Tests run locally or in the cloud (via Grafana Cloud k6), and results can be streamed to various outputs including Prometheus, InfluxDB, and Grafana dashboards.
Key Features
- JavaScript scripting: Write tests using ES6+ JavaScript, leveraging modules, logic, and async patterns.
- High performance: A single instance can generate tens of thousands of requests per second with efficient resource usage.
- Built-in metrics: Automatically collects HTTP timing, errors, checks, and custom metrics.
- Thresholds: Pass/fail criteria that gate your CI pipeline based on SLOs.
- Extensible outputs: Results can be sent to JSON, CSV, InfluxDB, Prometheus, and more.
- Cloud execution: Scale beyond local machine limits using Grafana Cloud k6.
- No Java dependency: Lightweight single binary, easy to install on any OS.
Why K6 Matters for Developers
Traditional load testing tools often require GUI-based configuration, complex XML, or separate scripting languages. K6 removes those barriers: you write test logic in the same language you use daily. This means performance testing becomes a natural extension of development, not a separate silo. Teams that adopt K6 can:
- Shift-left performance: Catch regressions early by running load tests on every commit.
- Define SLOs as code: Encode latency and error rate budgets directly in test scripts.
- Debug with developer tools: Use console.log, handle errors gracefully, and integrate with existing observability stacks.
- Automate reliability: Schedule tests via cron, CI, or Kubernetes jobs.
- Collaborate easily: Share and review test scripts just like application code.
Setting Up K6
Installation
K6 is distributed as a single binary for Linux, macOS, and Windows. The easiest way is via package managers or direct download.
# macOS (Homebrew)
brew install k6
# Ubuntu/Debian
sudo apt-get update
sudo apt-get install k6
# Using Docker
docker pull grafana/k6:latest
Running Your First Test
Create a file test.js and run it with k6 run test.js. Here is a minimal smoke test that hits a public API:
// test.js
import http from 'k6/http';
import { sleep } from 'k6';
export default function () {
http.get('https://test-api.k6.io/public/crocodiles/');
sleep(1);
}
Execute it and observe the built-in output:
k6 run test.js
You’ll see a summary table with metrics like http_req_duration, iterations, and virtual users.
Writing Test Scripts
Basic HTTP Test with Stages
Most load tests define virtual users (VUs) and a duration. K6 uses the options object to configure the test executor. Below we simulate 10 VUs running for 30 seconds:
import http from 'k6/http';
import { sleep } from 'k6';
export const options = {
vus: 10,
duration: '30s',
};
export default function () {
const res = http.get('https://test-api.k6.io/public/crocodiles/');
sleep(1);
}
Checks and Thresholds
Checks are assertions that do not stop the test, but record pass/fail for later analysis. Thresholds are pass/fail criteria evaluated at the end of the test—they can be used to gate CI pipelines.
import http from 'k6/http';
import { check, sleep } from 'k6';
export const options = {
vus: 50,
duration: '1m',
thresholds: {
http_req_duration: ['p(95)<200'], // 95% of requests must be <200ms
'checks{check:status is 200}': ['rate>0.99'], // more than 99% of checks pass
},
};
export default function () {
const res = http.get('https://test-api.k6.io/public/crocodiles/');
check(res, {
'status is 200': (r) => r.status === 200,
'response time < 300ms': (r) => r.timings.duration < 300,
});
sleep(1);
}
Ramping Patterns
Use the ramping-arrival-rate or ramping-vus executors to simulate traffic patterns that gradually increase and decrease. This is essential for realistic soak and stress tests.
import http from 'k6/http';
import { sleep } from 'k6';
export const options = {
stages: [
{ duration: '30s', target: 20 }, // ramp-up to 20 VUs
{ duration: '1m', target: 50 }, // stay at 50 VUs
{ duration: '20s', target: 0 }, // ramp-down to 0
],
thresholds: {
http_req_duration: ['p(95)<300'],
},
};
export default function () {
http.get('https://httpbin.test.k6.io/get');
sleep(1);
}
Advanced Techniques
Custom Metrics
Beyond built-in metrics, you can define your own counters, trends, rates, and gauges to track business-specific values like login time, item purchase success, or WebSocket message latency.
import http from 'k6/http';
import { Trend, Counter } from 'k6/metrics';
const myTrend = new Trend('payment_processing_time');
const myCounter = new Counter('successful_payments');
export default function () {
const res = http.post('https://api.example.com/pay', { amount: 100 });
if (res.status === 200) {
myCounter.add(1);
myTrend.add(res.timings.duration);
}
}
Data-Driven Tests
Feed your test with CSV or JSON data using the papaparse library or SharedArray to simulate different user scenarios. Here’s an example reading a CSV file of user credentials:
import { SharedArray } from 'k6/data';
import http from 'k6/http';
import { check } from 'k6';
const users = new SharedArray('credentials', function () {
// Return an array parsed from CSV
return open('users.csv').split('\n')
.filter(line => line.trim() !== '')
.map(line => {
const [username, password] = line.split(',');
return { username, password };
});
});
export default function () {
const user = users[Math.floor(Math.random() * users.length)];
const res = http.post('https://api.example.com/login', {
username: user.username,
password: user.password,
});
check(res, { 'login succeeded': (r) => r.status === 200 });
}
WebSocket Testing
K6 has first-class WebSocket support. You can open connections, send messages, and listen for events to test real-time features like chat, notifications, or streaming prices.
import ws from 'k6/ws';
import { check } from 'k6';
export default function () {
const url = 'wss://echo.websocket.org';
const response = ws.connect(url, null, function (socket) {
socket.on('open', () => {
console.log('Connected');
socket.send('Hello from k6');
});
socket.on('message', (message) => {
console.log(`Received: ${message}`);
check(message, { 'is string': (m) => typeof m === 'string' });
});
socket.on('close', () => console.log('Disconnected'));
socket.setTimeout(function () {
socket.close();
}, 5000);
});
check(response, { 'status is 101': (r) => r && r.status === 101 });
}
Using K6 with CI/CD
K6 is designed to run in automation. A typical CI job runs the test, checks thresholds, and fails the pipeline if SLOs are breached. Below is an example using Docker and GitHub Actions:
# .github/workflows/load-test.yml
name: Load Test
on: [push]
jobs:
k6:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Run k6 test
uses: grafana/k6-action@v0.2.0
with:
path: test.js
flags: --out json=results.json
- name: Archive results
uses: actions/upload-artifact@v3
with:
name: k6-results
path: results.json
You can also run k6 directly in a Docker container and exit with a non-zero code if thresholds fail, using k6 run --exit-on-assertion-failure.
Best Practices for K6 Testing
- Start small, iterate fast: Begin with a smoke test (1 VU, short duration) before scaling to hundreds of VUs. Validate your script logic and checks early.
- Define thresholds for every critical path: Set SLOs on latency (e.g., p95 < 300ms), error rate, and custom metrics. This turns tests into automated quality gates.
- Keep scripts modular: Extract reusable logic (authentication, data fetching) into separate JavaScript modules. K6 supports ES module imports.
- Use environments variables: Avoid hardcoding URLs or credentials. Use
__ENVto read environment variables, making scripts portable across staging, production, and CI. - Monitor resource usage: K6 is efficient, but generating extremely high RPS may require multiple instances. Use
k6 cloudor distributed instances when needed. - Store historical results: Stream metrics to InfluxDB or Prometheus and visualize with Grafana to detect performance trends over time.
- Clean up test data: If your tests create side effects (e.g., database records), add a teardown phase or use dedicated test endpoints.
Conclusion
K6 brings performance testing into the modern developer workflow. By writing tests in JavaScript, embedding thresholds as code, and integrating with CI pipelines, you can continuously validate the reliability and speed of your services. From simple smoke tests to complex WebSocket scenarios and data-driven simulations, K6 provides the tooling you need without the overhead of traditional load testing suites. Start with a small script, add checks and thresholds, run it in your CI, and watch your system's performance become a first-class deliverable of your engineering process.