On this page10 sections
k6 Smoke & Load Test
Write a parameterised k6 script with separate smoke and load test configurations — covering VU stages, thresholds, checks, environment variables, and result interpretation.
Role
Performance QA
Difficulty
IntermediateTime limit
90 min
Category
performance
Scenario
Your team is integrating k6 into a CI pipeline for the Reqres.in practice API (https://reqres.in). You need to deliver a single k6 script that can run in two modes: a smoke test (1–2 VUs, 30 seconds — sanity check that the API is up and returning correct responses) and a load test (ramp to 50 VUs over 1 minute, sustain for 3 minutes, ramp down over 30 seconds). The script must be parameterised so the base URL and auth token are supplied via environment variables and never hard-coded. A reviewer should be able to run either mode from the CLI without editing the script.
Requirements
- 1.Write a k6 script that targets at least two Reqres.in endpoints: GET /api/users?page=2 and POST /api/login
- 2.Implement separate smoke and load options objects: smoke uses 1 VU for 30 s; load uses stages (ramp-up 1 min, steady 3 min, ramp-down 30 s) reaching 50 VUs at peak
- 3.Define at least two k6 thresholds: P95 response time ≤ 500 ms and HTTP error rate < 1% — applied to both configurations
- 4.Add k6 checks to validate response correctness on every iteration: HTTP status code, and at least one response body field (e.g. check that POST /api/login returns a 'token' field)
- 5.Parameterise the base URL and auth token via environment variables (__ENV.BASE_URL, __ENV.TOKEN) with sensible defaults — no hard-coded values in the script body
- 6.Document the CLI run commands for each mode, showing how to pass environment variables and select the options profile
- 7.Interpret a sample k6 result output: identify which threshold passed or failed, explain the checks summary (passes/failures), and state what the http_req_duration P95 line means
Starter data
- ›Practice API: https://reqres.in (public, no registration required for most endpoints)
- ›GET /api/users?page=2 — returns 200 with a data array of user objects; no auth required
- ›POST /api/login — body: { "email": "eve.holt@reqres.in", "password": "cityslicka" } — returns 200 with { "token": "QpwL5tpe83ilfN2" }
- ›POST /api/login with invalid credentials — body: { "email": "invalid@reqres.in", "password": "wrong" } — returns 400 with { "error": "user not found" }
- ›k6 CLI reference: k6 run script.js -e BASE_URL=https://reqres.in; select options profile using an ENV variable and conditional export
Expected deliverables
- ✓A k6 script file (script.js or documented outline) implementing: default function with GET and POST requests, smoke options object, load options object, thresholds, and checks
- ✓Environment variable usage: BASE_URL with default, TOKEN or credentials with default — no hard-coded production values
- ✓CLI run commands for smoke mode and load mode, each showing how to pass environment variables; explain how to switch between options profiles
- ✓A result interpretation section: annotate a representative k6 output block identifying threshold pass/fail markers, checks pass rate, and the http_req_duration P95 value — with a pass/fail verdict
- ✓A brief (3–5 sentence) note on the difference between k6 checks and k6 thresholds, and why both are needed
Evaluation rubric
| Dimension | What reviewers look for |
|---|---|
| Correct k6 options and stages | Is the smoke configuration minimal (1–2 VUs, short duration) and the load configuration using stages to model ramp-up, steady state, and ramp-down? Stage durations and target VU counts should be explicit numbers, not placeholders. A load test that starts at 50 VUs with no ramp-up is a spike test — flag this if present. |
| Meaningful thresholds vs checks | Thresholds are pass/fail gates that determine the k6 exit code; checks are per-request assertions that accumulate as a ratio. Both are needed: thresholds enforce SLAs at the aggregate level; checks catch individual response correctness. A script with only thresholds misses per-request validation; one with only checks has no automated pass/fail gate. |
| Parameterisation and secrets hygiene | Are BASE_URL and credentials read exclusively from __ENV? Is a sensible default provided (e.g. __ENV.BASE_URL || 'https://reqres.in') so the script runs without flags in development? Are there zero hard-coded tokens, passwords, or environment-specific URLs in the script body? Hardcoded secrets in a script that will live in version control is a security risk — call this out explicitly. |
| Smoke vs load distinction | Does the candidate explain the purpose of each mode: smoke = fast sanity check at minimal load (catches broken deploys in seconds); load = sustained throughput test at realistic concurrency (catches capacity and regression issues). Using the same VU count for both defeats the purpose of having two modes. |
| Result reading | Can the candidate correctly identify http_req_duration{p(95)} in the output and relate it to the threshold? Do they understand that checks failures (50/1000 failed) appear in the checks summary and are separate from threshold failures? Do they interpret ✓ and ✗ markers on threshold lines correctly (✓ = threshold met; ✗ = threshold exceeded and k6 exits with code 99)? |
Sample solution outline
- ›import http from 'k6/http'; import { check, sleep } from 'k6';
- ›const BASE_URL = __ENV.BASE_URL || 'https://reqres.in';
- ›export const smokeOptions = { vus: 1, duration: '30s', thresholds: { http_req_duration: ['p(95)<500'], http_req_failed: ['rate<0.01'] } };
- ›export const loadOptions = { stages: [{ duration: '1m', target: 50 }, { duration: '3m', target: 50 }, { duration: '30s', target: 0 }], thresholds: { http_req_duration: ['p(95)<500'], http_req_failed: ['rate<0.01'] } };
- ›export const options = __ENV.TEST_TYPE === 'smoke' ? smokeOptions : loadOptions;
- ›export default function () { const usersRes = http.get(`${BASE_URL}/api/users?page=2`); check(usersRes, { 'users status 200': (r) => r.status === 200, 'users has data array': (r) => r.json().data.length > 0 }); sleep(1); const loginRes = http.post(`${BASE_URL}/api/login`, JSON.stringify({ email: 'eve.holt@reqres.in', password: 'cityslicka' }), { headers: { 'Content-Type': 'application/json' } }); check(loginRes, { 'login status 200': (r) => r.status === 200, 'login returns token': (r) => r.json('token') !== undefined }); sleep(1); }
- ›CLI smoke: k6 run script.js -e TEST_TYPE=smoke -e BASE_URL=https://reqres.in
- ›CLI load: k6 run script.js -e TEST_TYPE=load -e BASE_URL=https://reqres.in
- ›Result interpretation: '✓ http_req_duration p(95)<500' means the 95th-percentile request duration was below 500 ms — threshold passed; 'checks...: 1920/2000 (96.00%)' means 80 checks failed across all iterations — investigate which check name is failing in the checks block
Common mistakes
- Confusing checks and thresholds — checks are assertions that accumulate as a pass/fail ratio; thresholds are the aggregate gates that set the k6 exit code. Failed checks do not automatically fail the test run unless you add a 'checks' threshold
- Hard-coding the base URL or credentials in the script body — any value that changes between environments or could be considered sensitive must come from __ENV
- Using a single options export for both smoke and load and just changing the numbers each time — separate named options objects allow both modes to coexist in the same file
- Forgetting sleep() between requests — without sleep, a single VU makes requests as fast as the server responds, generating far more load than one real user would
- Interpreting http_req_duration average instead of the P95 percentile — the average is distorted by outliers; P95 represents the worst experience for 95% of users and is the right metric for SLA validation
- Asserting a specific token value in the check ('token equals QpwL5tpe83ilfN2') — tokens change between sessions; assert that the field exists and is a non-empty string instead
- Not documenting the -e flag syntax — k6 environment variables are passed as -e KEY=VALUE (not --env or export), and forgetting this means the script runs with defaults silently
Submission checklist
- k6 script with default export function covering at least two endpoints
- Separate smokeOptions and loadOptions objects — or equivalent conditional export pattern
- Load options use stages: ramp-up, steady state, ramp-down with explicit durations and VU targets
- At least two thresholds: P95 response time and error rate
- Checks on every request: HTTP status code and at least one body field
- BASE_URL and credentials read from __ENV with defaults — no hard-coded values
- CLI commands for smoke and load modes documented
- Result interpretation section with P95, checks summary, and pass/fail verdict
- Brief explanation of checks vs thresholds distinction
Extension ideas
- +Add a third test stage: soak test options (10 VUs for 30 minutes) to detect memory leaks and gradual throughput degradation over time
- +Add a 'checks' threshold (check_failure_rate < 0.05) so that a high check-failure rate automatically fails the k6 run, not just latency breaches
- +Integrate the script into a GitHub Actions workflow: run the smoke test on every pull request, run the load test on a nightly schedule, and use the k6 --out json flag to archive results as an artefact