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

Intermediate

Time limit

90 min

Category

performance

k6

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

DimensionWhat reviewers look for
Correct k6 options and stagesIs 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 checksThresholds 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 hygieneAre 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 distinctionDoes 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 readingCan 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