Scenarios
Scenarios provide fine-grained control over how k6 executes your test. They allow you to model complex user behavior patterns, run different workloads in parallel, and precisely control VU execution.
Why Use Scenarios?
Scenarios are more powerful than simple vus, duration, or stages options:
- Multiple workflows: Run different test functions simultaneously
- Different load patterns: Combine constant load, ramping, and spike patterns
- Precise VU control: Control exactly how VUs execute iterations
- Per-scenario configuration: Set different options for each scenario
- Advanced metrics: Get per-scenario metrics and tags
Basic Scenario Structure
import http from 'k6/http';
export let options = {
scenarios: {
my_scenario: { // Scenario name
executor: 'constant-vus', // Executor type
vus: 10, // Executor-specific options
duration: '30s',
},
},
};
export default function() {
http.get('https://quickpizza.grafana.com');
}
Executors
Executors define how VUs are scheduled and iterations are executed. k6 provides several executor types:
Constant VUs
Maintains a constant number of VUs for a duration.
export let options = {
scenarios: {
constant_load: {
executor: 'constant-vus',
vus: 10,
duration: '1m',
},
},
};
Use case: Steady-state load testing
Ramping VUs
Gradually ramps VUs up and down.
export let options = {
scenarios: {
ramping_load: {
executor: 'ramping-vus',
startVUs: 0,
stages: [
{ duration: '30s', target: 10 },
{ duration: '1m', target: 10 },
{ duration: '30s', target: 0 },
],
gracefulRampDown: '30s',
},
},
};
Use case: Gradually increasing load, soak testing
Constant Arrival Rate
Starts iterations at a constant rate, adding VUs as needed.
export let options = {
scenarios: {
constant_rate: {
executor: 'constant-arrival-rate',
rate: 30, // 30 iterations per timeUnit
timeUnit: '1s', // per second
duration: '1m',
preAllocatedVUs: 10, // Pre-allocated VUs
maxVUs: 50, // Maximum VUs to allocate
},
},
};
Use case: Testing system under constant request rate, measuring latency under load
Constant arrival rate focuses on iteration starts per time unit, not concurrent VUs. k6 will add VUs as needed to maintain the rate.
Ramping Arrival Rate
Gradually increases or decreases the iteration start rate.
export let options = {
scenarios: {
ramping_rate: {
executor: 'ramping-arrival-rate',
startRate: 10,
timeUnit: '1s',
preAllocatedVUs: 20,
maxVUs: 100,
stages: [
{ duration: '30s', target: 10 }, // Start at 10 iters/s
{ duration: '1m', target: 50 }, // Ramp to 50 iters/s
{ duration: '30s', target: 10 }, // Ramp down to 10 iters/s
],
},
},
};
Use case: Gradually increasing throughput testing
Shared Iterations
Shares a fixed number of iterations among VUs.
export let options = {
scenarios: {
shared_iters: {
executor: 'shared-iterations',
vus: 10,
iterations: 100, // Total iterations shared across VUs
maxDuration: '30s',
},
},
};
Use case: Completing a specific number of test iterations as quickly as possible
Per-VU Iterations
Each VU executes a specific number of iterations.
export let options = {
scenarios: {
per_vu_iters: {
executor: 'per-vu-iterations',
vus: 10,
iterations: 10, // Each VU runs 10 iterations
maxDuration: '30s',
},
},
};
Use case: Testing with a precise total number of iterations (vus × iterations)
Externally Controlled
Control execution externally via k6’s REST API.
export let options = {
scenarios: {
external: {
executor: 'externally-controlled',
vus: 10,
maxVUs: 50,
duration: '5m',
},
},
};
Use case: Dynamic control of VUs during test execution
Multiple Scenarios
Run different test functions with different load patterns:
import { browser } from 'k6/browser';
export const options = {
scenarios: {
messages: {
executor: 'constant-vus',
exec: 'messages', // Function to execute
vus: 1,
duration: '2s',
options: {
browser: {
type: 'chromium',
},
},
},
news: {
executor: 'per-vu-iterations',
exec: 'news', // Different function
vus: 1,
iterations: 2,
maxDuration: '5s',
options: {
browser: {
type: 'chromium',
},
},
},
},
thresholds: {
browser_web_vital_fcp: ['max < 5000'],
checks: ["rate==1.0"]
}
}
export async function messages() {
const page = await browser.newPage();
try {
await page.goto('https://quickpizza.grafana.com/my_messages.php', {
waitUntil: 'networkidle'
});
} finally {
await page.close();
}
}
export async function news() {
const page = await browser.newPage();
try {
await page.goto('https://quickpizza.grafana.com/news.php', {
waitUntil: 'networkidle'
});
} finally {
await page.close();
}
}
Scenario Configuration Options
Common Options
All scenarios support these options:
export let options = {
scenarios: {
my_scenario: {
executor: 'constant-vus',
// Execution control
startTime: '10s', // Delay start by 10s
gracefulStop: '30s', // Time to finish iterations after duration
// Function to execute
exec: 'myFunction', // Default: 'default'
// Environment variables
env: {
MY_VAR: 'value',
},
// Tags for this scenario's metrics
tags: {
scenario_type: 'api',
team: 'backend',
},
},
},
};
export function myFunction() {
// Custom test logic
}
Staggered Start Times
Start scenarios at different times:
export let options = {
scenarios: {
warmup: {
executor: 'constant-vus',
vus: 5,
duration: '1m',
startTime: '0s', // Start immediately
},
main_load: {
executor: 'ramping-vus',
startVUs: 0,
stages: [
{ duration: '2m', target: 50 },
],
startTime: '1m', // Start after warmup
},
},
};
Each scenario automatically gets tagged:
import http from 'k6/http';
import { check } from 'k6';
export let options = {
scenarios: {
api_test: {
executor: 'constant-vus',
vus: 10,
duration: '30s',
tags: {
test_type: 'api',
version: 'v2',
},
},
},
thresholds: {
// Threshold for specific scenario
'http_req_duration{scenario:api_test}': ['p(95)<500'],
},
};
export default function() {
http.get('https://quickpizza.grafana.com/api/menu');
}
Graceful Stop
Control how iterations finish at the end:
export let options = {
scenarios: {
my_scenario: {
executor: 'constant-vus',
vus: 10,
duration: '1m',
gracefulStop: '30s', // Wait up to 30s for iterations to finish
},
},
};
If iterations don’t complete within the gracefulStop period, they are forcefully terminated.
Scenario Examples
Spike Test
Stress Test
Soak Test
Mixed Workload
export let options = {
scenarios: {
spike: {
executor: 'ramping-vus',
startVUs: 0,
stages: [
{ duration: '10s', target: 10 }, // Normal load
{ duration: '10s', target: 100 }, // Spike!
{ duration: '10s', target: 10 }, // Back to normal
],
gracefulRampDown: '10s',
},
},
};
export let options = {
scenarios: {
stress: {
executor: 'ramping-arrival-rate',
startRate: 10,
timeUnit: '1s',
preAllocatedVUs: 50,
maxVUs: 500,
stages: [
{ duration: '2m', target: 10 }, // Normal
{ duration: '5m', target: 50 }, // Increase
{ duration: '5m', target: 100 }, // High load
{ duration: '5m', target: 200 }, // Very high
{ duration: '2m', target: 10 }, // Recovery
],
},
},
};
export let options = {
scenarios: {
soak: {
executor: 'constant-vus',
vus: 50,
duration: '4h', // Long duration
},
},
};
export let options = {
scenarios: {
read_heavy: {
executor: 'constant-arrival-rate',
exec: 'readData',
rate: 100,
timeUnit: '1s',
duration: '5m',
preAllocatedVUs: 20,
maxVUs: 100,
},
write_light: {
executor: 'constant-arrival-rate',
exec: 'writeData',
rate: 10,
timeUnit: '1s',
duration: '5m',
preAllocatedVUs: 5,
maxVUs: 20,
},
},
};
export function readData() {
http.get('https://quickpizza.grafana.com/api/menu');
}
export function writeData() {
http.post('https://quickpizza.grafana.com/api/orders',
JSON.stringify({ items: ['pizza'] }));
}
Executor Comparison
| Executor | Control Type | Use Case |
|---|
constant-vus | VU count | Steady-state load |
ramping-vus | VU count | Gradual load changes |
constant-arrival-rate | Iteration rate | Fixed throughput |
ramping-arrival-rate | Iteration rate | Gradual throughput changes |
shared-iterations | Total iterations | Quick completion of fixed work |
per-vu-iterations | Per-VU iterations | Precise iteration count |
externally-controlled | External API | Dynamic control |
Best Practices
- Use arrival rate for throughput testing - When you care about requests/second, not VU count
- Set appropriate maxVUs - For arrival rate executors, allocate enough VUs to maintain the rate
- Add graceful stop time - Give long-running iterations time to complete
- Use scenario tags - Tag scenarios for easier metric filtering
- Stagger scenario starts - Avoid overwhelming the system at test start
- Name scenarios descriptively - Use clear names like
api_load, browser_flow, etc.
Scenario Metrics
Filter metrics by scenario:
export let options = {
scenarios: {
api_test: {
executor: 'constant-vus',
vus: 10,
duration: '1m',
},
},
thresholds: {
'http_req_duration{scenario:api_test}': ['p(95)<500'],
'http_req_failed{scenario:api_test}': ['rate<0.01'],
'iterations{scenario:api_test}': ['count>100'],
},
};
Next Steps