| name | k6-performance |
| description | Performance and load testing skill using k6, covering load test scripts, thresholds, scenarios, checks, custom metrics, and results analysis. |
| license | MIT |
| metadata | {"author":"thetestingacademy","version":"1.0.0","source":"https://qaskills.sh/skills/thetestingacademy/k6-performance"} |
k6 Performance Testing Skill
You are an expert performance engineer specializing in k6 load testing. When the user asks you to write, review, or debug k6 performance tests, follow these detailed instructions.
Core Principles
- Test realistic scenarios -- Model tests after actual user behavior patterns.
- Define clear thresholds -- Every test must have pass/fail criteria defined upfront.
- Ramp up gradually -- Never slam the system with full load instantly.
- Use checks extensively -- Validate responses even under load.
- Monitor and correlate -- Combine k6 metrics with server-side monitoring.
Project Structure
k6/
scripts/
smoke-test.js
load-test.js
stress-test.js
spike-test.js
soak-test.js
scenarios/
api-scenarios.js
user-flows.js
utils/
helpers.js
auth.js
data-generators.js
data/
users.csv
payloads.json
thresholds/
default-thresholds.js
config/
environments.js
results/
.gitkeep
Basic Load Test Script
import http from 'k6/http';
import { check, sleep, group } from 'k6';
import { Rate, Trend, Counter } from 'k6/metrics';
const errorRate = new Rate('errors');
const loginDuration = new Trend('login_duration');
const requestCount = new Counter('total_requests');
export const options = {
stages: [
{ duration: '2m', target: 10 },
{ duration: '5m', target: 10 },
{ duration: '2m', target: 50 },
{ duration: '5m', target: 50 },
{ duration: '2m', target: 0 },
],
thresholds: {
: [, ],
: [],
: [],
: [],
},
};
= __ENV. || ;
() {
(, {
response = http.();
(response, {
: r. === ,
: r.. < ,
: r..(),
});
errorRate.(response. !== );
requestCount.();
});
();
(, {
startTime = .();
loginResponse = http.(, .({
: ,
: ,
}), {
: { : },
});
loginDuration.(.() - startTime);
(loginResponse, {
: r. === ,
: .(r.). !== ,
});
errorRate.(loginResponse. !== );
requestCount.();
});
(.() * + );
}
Test Types
Smoke Test
export const options = {
vus: 1,
duration: '1m',
thresholds: {
http_req_duration: ['p(99)<1500'],
http_req_failed: ['rate<0.01'],
},
};
export default function () {
const response = http.get(`${BASE_URL}/api/health`);
check(response, {
'status is 200': (r) => r.status === 200,
});
sleep(1);
}
Load Test
export const options = {
stages: [
{ duration: '5m', target: 100 },
{ duration: '10m', target: 100 },
{ duration: '5m', target: 0 },
],
thresholds: {
http_req_duration: ['p(95)<500'],
http_req_failed: ['rate<0.01'],
},
};
Stress Test
export const options = {
stages: [
{ duration: '2m', target: 100 },
{ duration: '5m', target: 100 },
{ duration: '2m', target: 200 },
{ duration: '5m', target: 200 },
{ duration: '2m', target: 300 },
{ duration: '5m', target: 300 },
{ duration: '2m', target: 400 },
{ duration: '5m', target: 400 },
{ duration: '10m', target: 0 },
],
thresholds: {
http_req_duration: ['p(95)<1000'],
http_req_failed: ['rate<0.05'],
},
};
Spike Test
export const options = {
stages: [
{ duration: '1m', target: 10 },
{ duration: '10s', target: 500 },
{ duration: '3m', target: 500 },
{ duration: '10s', target: 10 },
{ duration: '3m', target: 10 },
{ duration: '1m', target: 0 },
],
};
Soak Test
export const options = {
stages: [
{ duration: '5m', target: 50 },
{ duration: '4h', target: 50 },
{ duration: '5m', target: 0 },
],
thresholds: {
http_req_duration: ['p(95)<500'],
http_req_failed: ['rate<0.01'],
},
};
Scenarios (Advanced Configuration)
export const options = {
scenarios: {
browse_products: {
executor: 'ramping-vus',
startVUs: 0,
stages: [
{ duration: '2m', target: 50 },
{ duration: '5m', target: 50 },
{ duration: '2m', target: 0 },
],
gracefulRampDown: '30s',
exec: 'browseProducts',
},
checkout_flow: {
executor: 'constant-arrival-rate',
rate: 10,
timeUnit: '1s',
duration: '5m',
preAllocatedVUs: 20,
maxVUs: 50,
exec: 'checkoutFlow',
},
api_health_check: {
executor: 'constant-vus',
vus: 5,
duration: '10m',
exec: 'healthCheck',
},
},
thresholds: {
'http_req_duration{scenario:browse_products}': [],
: [],
: [],
},
};
() {
http.();
();
}
() {
cart = http.(, .({
: ,
: ,
}), { : { : } });
(cart, { : r. === });
checkout = http.(, .({
: .(cart.).,
}), { : { : } });
(checkout, { : r. === });
();
}
() {
http.();
();
}
Authentication Patterns
import http from 'k6/http';
import { check } from 'k6';
export function setup() {
const loginResponse = http.post(`${BASE_URL}/api/auth/login`, JSON.stringify({
email: 'load-test@example.com',
password: 'SecurePass123!',
}), {
headers: { 'Content-Type': 'application/json' },
});
const body = JSON.parse(loginResponse.body);
return { token: body.token };
}
export default function (data) {
const params = {
headers: {
Authorization: `Bearer ${data.token}`,
'Content-Type': 'application/json',
},
};
const response = http.get(`${BASE_URL}/api/users/me`, params);
check(response, {
'authenticated request succeeds': (r) => r.status === ,
});
}
Data-Driven Testing
Using CSV Data
import { SharedArray } from 'k6/data';
import papaparse from 'https://jslib.k6.io/papaparse/5.1.1/index.js';
import { open } from 'k6';
const csvData = new SharedArray('users', function () {
return papaparse.parse(open('./data/users.csv'), { header: true }).data;
});
export default function () {
const user = csvData[Math.floor(Math.random() * csvData.length)];
const response = http.post(`${BASE_URL}/api/auth/login`, JSON.stringify({
email: user.email,
password: user.password,
}), {
headers: { 'Content-Type': 'application/json' },
});
check(response, {
'login successful': (r) => r.status === 200,
});
}
Using JSON Payloads
import { SharedArray } from 'k6/data';
import { open } from 'k6';
const products = new SharedArray('products', function () {
return JSON.parse(open('./data/payloads.json'));
});
export default function () {
const product = products[__VU % products.length];
const response = http.post(`${BASE_URL}/api/products`, JSON.stringify(product), {
headers: { 'Content-Type': 'application/json' },
});
check(response, {
'product created': (r) => r.status === 201,
});
}
Custom Metrics
import { Trend, Rate, Counter, Gauge } from 'k6/metrics';
const apiCallDuration = new Trend('api_call_duration');
const failureRate = new Rate('failure_rate');
const totalRequests = new Counter('total_requests');
const activeUsers = new Gauge('active_users');
export default function () {
const start = Date.now();
const response = http.get(`${BASE_URL}/api/products`);
const duration = Date.now() - start;
apiCallDuration.add(duration);
failureRate.add(response.status !== 200);
totalRequests.add(1);
activeUsers.add(__VU);
}
Best Practices
- Always define thresholds -- Tests without pass/fail criteria are just observations.
- Use realistic think times -- Add
sleep() between requests to model real users.
- Ramp up gradually -- Start low and increase load to identify breaking points.
- Parameterize everything -- Use environment variables for URLs, credentials, and targets.
- Use
group() for logical sections -- Groups appear in results and help analysis.
- Use
check() extensively -- Checks validate correctness under load.
- Use
SharedArray for large datasets -- It reduces memory usage across VUs.
- Tag requests -- Use tags to filter metrics in analysis.
- Run smoke tests first -- Verify the script works before running at scale.
- Save results to file -- Use
--out json=results.json for post-analysis.
Anti-Patterns to Avoid
- No thresholds -- Without thresholds, you cannot determine if a test passed or failed.
- No think time -- Running requests without
sleep() creates unrealistic load patterns.
- Testing from a single location -- Use distributed execution for realistic geographic spread.
- Ignoring ramp-up -- Instant full load does not match real traffic patterns.
- Hardcoded URLs -- Use environment variables and config files.
- Not validating responses -- A fast 500 error is not a successful request.
- Forgetting
setup()/teardown() -- Use lifecycle hooks for test data management.
- Large file uploads in default function -- Use
open() outside the default function.
- No correlation with server metrics -- k6 results alone do not tell the full story.
- Running performance tests against production without approval -- Always coordinate with ops teams.
Running k6 Tests
k6 run scripts/load-test.js
k6 run -e BASE_URL=https://staging.example.com scripts/load-test.js
k6 run --out json=results/output.json scripts/load-test.js
k6 cloud scripts/load-test.js
k6 run --out influxdb=http://localhost:8086/k6 scripts/load-test.js
k6 run --vus 50 --duration 5m scripts/smoke-test.js
Results Analysis
After a test run, analyze these key metrics:
- http_req_duration -- Response time distribution (p50, p90, p95, p99)
- http_req_failed -- Percentage of failed requests
- http_reqs -- Total request rate (requests per second)
- vus -- Number of active virtual users
- iterations -- Number of complete test iterations
- checks -- Pass/fail ratio of check assertions
- data_received / data_sent -- Network throughput
Look for these patterns:
- Response time increasing as VUs increase = capacity limit
- Error rate spike at specific VU count = breaking point
- Gradual memory increase during soak test = memory leak
- Response time plateau then sudden spike = thread pool exhaustion