k6 Load Testing with JavaScript
k6 is the modern, developer-friendly alternative to JMeter — load tests are JavaScript scripts, not XML configuration files. k6 integrates with CI/CD natively, has built-in threshold pass/fail criteria, generates beautiful Grafana dashboards, and scales to millions of virtual users. It's the performance testing tool of choice at companies like Google and Grafana Labs.
k6 Load Test Scripts
// ══════════════════════════════════════════════════════════════
// INSTALL: brew install k6 (macOS) / choco install k6 (Windows)
// RUN: k6 run load_test.js
// ══════════════════════════════════════════════════════════════
import http from 'k6/http';
import { check, sleep, group } from 'k6';
import { Counter, Rate, Trend } from 'k6/metrics';
// ── CUSTOM METRICS ────────────────────────────────────────────
const loginErrors = new Counter('login_errors');
const checkoutDuration = new Trend('checkout_duration');
const errorRate = new Rate('error_rate');
// ── TEST OPTIONS (load profile) ───────────────────────────────
export const options = {
stages: [
{ duration: '2m', target: 50 }, // Ramp up to 50 users over 2 min
{ duration: '5m', target: 50 }, // Hold at 50 users for 5 min
{ duration: '2m', target: 100 }, // Ramp up to 100 users
{ duration: '10m', target: 100 }, // Hold at 100 users for 10 min
{ duration: '2m', target: 0 }, // Ramp down to 0
],
// ── THRESHOLDS (define pass/fail criteria) ────────────────
thresholds: {
http_req_duration: [
'p(95)<500', // 95% of requests complete in <500ms
'p(99)<1000', // 99% complete in <1000ms
],
http_req_failed: ['rate<0.01'], // Error rate below 1%
error_rate: ['rate<0.05'], // Custom error rate below 5%
checkout_duration: ['p(95)<2000'], // Checkout under 2s at p95
},
};
// ── SETUP (runs once before the test) ─────────────────────────
export function setup() {
// Create test data (e.g., seed products in test DB via API)
const res = http.post('https://staging.myapp.com/api/test/seed', JSON.stringify({
products: 100, users: 50
}), { headers: { 'Content-Type': 'application/json' } });
return { products: res.json().productIds }; // Returned data available in default()
}
// ── MAIN FUNCTION (each virtual user runs this in a loop) ──────
export default function(data) {
const BASE_URL = 'https://staging.myapp.com';
group('Authentication', () => {
const loginRes = http.post(`${BASE_URL}/api/auth/login`, JSON.stringify({
email: `user${__VU}@test.com`, // __VU = virtual user number (unique per user)
password: 'Test@1234'
}), { headers: { 'Content-Type': 'application/json' } });
const loginOk = check(loginRes, {
'login status is 200': (r) => r.status === 200,
'login returns token': (r) => r.json().token !== undefined,
'login time < 500ms': (r) => r.timings.duration < 500,
});
if (!loginOk) { loginErrors.add(1); errorRate.add(1); return; }
const token = loginRes.json().token;
const authHeaders = { headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' } };
sleep(1); // Think time: 1 second
});
group('Product Browsing', () => {
const productId = data.products[Math.floor(Math.random() * data.products.length)];
const productRes = http.get(`${BASE_URL}/api/products/${productId}`, authHeaders);
check(productRes, {
'product page loads': (r) => r.status === 200,
'product has price': (r) => r.json().price > 0,
});
sleep(__ENV.THINK_TIME || 2); // Configurable think time via env var
});
}
// ── TEARDOWN (runs once after the test) ───────────────────────
export function teardown(data) {
// Clean up test data
http.del('https://staging.myapp.com/api/test/cleanup');
}
// ── RUN WITH OPTIONS ──────────────────────────────────────────
// k6 run load_test.js # Default
// k6 run --env THINK_TIME=3 load_test.js # Override think time
// k6 run --out json=results.json load_test.js # Export raw metrics
// k6 run --out influxdb=http://localhost:8086 load_test.js # Grafana dashboardCommon Mistakes
- No thresholds defined — k6 tests without thresholds always 'pass' regardless of response times; always define performance SLAs as thresholds
- All VUs using the same user — use __VU (virtual user number) to create unique user identities and avoid cache distortion
- No sleep() between actions — without think time, k6 sends requests as fast as possible, generating unrealistic load patterns
- Not using groups — grouping requests into workflows makes reports more readable and enables per-group performance analysis
Tip
Tip
Practice k6 Load Testing with JavaScript in small, isolated examples before integrating into larger projects. Breaking concepts into small experiments builds genuine understanding faster than reading alone.
Technical diagram.
Practice Task
Note
Practice Task — (1) Write a working example of k6 Load Testing with JavaScript from scratch without looking at notes. (2) Modify it to handle an edge case (empty input, null value, or error state). (3) Share your solution in the Priygop community for feedback.
Quick Quiz
Common Mistake
Warning
A common mistake with k6 Load Testing with JavaScript is skipping edge case testing — empty inputs, null values, and unexpected data types. Always validate boundary conditions to write robust, production-ready software testing code.
Key Takeaways
- k6 is the modern, developer-friendly alternative to JMeter — load tests are JavaScript scripts, not XML configuration files.
- No thresholds defined — k6 tests without thresholds always 'pass' regardless of response times; always define performance SLAs as thresholds
- All VUs using the same user — use __VU (virtual user number) to create unique user identities and avoid cache distortion
- No sleep() between actions — without think time, k6 sends requests as fast as possible, generating unrealistic load patterns