Skip to main content
Scenarios provide fine-grained control over how k6 executes your test workload. They let you model diverse traffic patterns and complex user behaviors in a single test.

What are scenarios?

Scenarios configure how VUs (virtual users) and iterations are scheduled during test execution. Each scenario can:
  • Execute a different JavaScript function
  • Use a different executor to control workload patterns
  • Run in parallel or sequence with other scenarios
  • Have independent environment variables and tags

Why use scenarios?

Scenarios provide several benefits over simple VU/duration configuration:

Realistic traffic patterns

Model different user behaviors with arrival rates, iterations, or ramping patterns.

Multiple workloads

Run different test functions in parallel or sequence within one test.

Better organization

Separate different test phases or user types into distinct scenarios.

Granular analysis

Tag and analyze each scenario independently with scenario-specific metrics.

Basic scenario configuration

Define scenarios in the options.scenarios object:
import http from 'k6/http';

export const options = {
  scenarios: {
    my_scenario: {
      executor: 'constant-vus',
      vus: 10,
      duration: '30s',
    },
  },
};

export default function () {
  http.get('https://test.k6.io/');
}
Each scenario requires:
  • A unique name (my_scenario)
  • An executor property specifying the workload pattern
  • Executor-specific configuration (like vus and duration)

Scenario executors

Executors determine how k6 schedules VUs and iterations. Choose the executor that best matches your workload:

By iterations

A fixed number of iterations shared between VUs.
export const options = {
  scenarios: {
    shared_iters: {
      executor: 'shared-iterations',
      vus: 10,
      iterations: 100,  // Total across all VUs
    },
  },
};
Use when: You need exactly 100 iterations, distributed among VUs.

By VUs

A constant number of VUs for a duration.
export const options = {
  scenarios: {
    constant_load: {
      executor: 'constant-vus',
      vus: 50,
      duration: '1m',
    },
  },
};
Use when: You want steady load with 50 VUs for 1 minute.

By arrival rate

Fixed iteration rate (open model).
export const options = {
  scenarios: {
    constant_rate: {
      executor: 'constant-arrival-rate',
      rate: 100,           // 100 iterations per timeUnit
      timeUnit: '1s',      // Per second
      duration: '1m',
      preAllocatedVUs: 50, // Initial VUs
      maxVUs: 100,         // Maximum VUs
    },
  },
};
Use when: You need exactly 100 requests per second, regardless of response time.

Scenario options

All scenarios support these common options:
OptionTypeDescriptionDefault
executorstringExecutor type (required)-
startTimestringDelay before starting"0s"
gracefulStopstringTime to wait for iterations to finish"30s"
execstringFunction name to execute"default"
envobjectScenario-specific environment variables{}
tagsobjectScenario-specific tags{}

Multiple scenarios

Run multiple scenarios in parallel:
import http from 'k6/http';

export const options = {
  scenarios: {
    load_test: {
      executor: 'constant-vus',
      vus: 50,
      duration: '5m',
      exec: 'loadTest',
    },
    stress_test: {
      executor: 'ramping-vus',
      startTime: '5m',  // Start after load_test
      startVUs: 0,
      stages: [
        { duration: '2m', target: 100 },
        { duration: '5m', target: 100 },
        { duration: '2m', target: 0 },
      ],
      exec: 'stressTest',
    },
  },
};

export function loadTest() {
  http.get('https://test.k6.io/');
}

export function stressTest() {
  http.get('https://test.k6.io/');
  http.post('https://test.k6.io/login', { username: 'test' });
}
This runs a load test for 5 minutes, then immediately starts a stress test.

Scenario-specific tags and environment

Tag metrics by scenario:
import http from 'k6/http';

export const options = {
  scenarios: {
    api_test: {
      executor: 'constant-vus',
      vus: 10,
      duration: '1m',
      tags: { 
        test_type: 'api',
        priority: 'high',
      },
      env: {
        BASE_URL: 'https://api.example.com',
      },
    },
    web_test: {
      executor: 'constant-vus',
      vus: 20,
      duration: '1m',
      tags: { 
        test_type: 'web',
        priority: 'medium',
      },
      env: {
        BASE_URL: 'https://www.example.com',
      },
    },
  },
  thresholds: {
    'http_req_duration{test_type:api}': ['p(95)<500'],
    'http_req_duration{test_type:web}': ['p(95)<1000'],
  },
};

export default function () {
  http.get(__ENV.BASE_URL);
}

Complete example

Here’s a realistic scenario combining multiple patterns:
import http from 'k6/http';
import { sleep, check } from 'k6';

export const options = {
  scenarios: {
    // Warm-up phase
    warmup: {
      executor: 'constant-vus',
      vus: 5,
      duration: '30s',
      tags: { phase: 'warmup' },
    },
    
    // Main load test
    load: {
      executor: 'ramping-vus',
      startTime: '30s',
      startVUs: 0,
      stages: [
        { duration: '1m', target: 50 },
        { duration: '5m', target: 50 },
        { duration: '1m', target: 0 },
      ],
      tags: { phase: 'load' },
    },
    
    // Spike test
    spike: {
      executor: 'constant-arrival-rate',
      startTime: '7m30s',
      rate: 100,
      timeUnit: '1s',
      duration: '30s',
      preAllocatedVUs: 100,
      maxVUs: 200,
      tags: { phase: 'spike' },
    },
  },
  
  thresholds: {
    'http_req_duration{phase:load}': ['p(95)<500'],
    'http_req_duration{phase:spike}': ['p(95)<1000'],
    'http_req_failed': ['rate<0.01'],
  },
};

const baseUrl = 'https://quickpizza.grafana.com';

export default function () {
  const res = http.get(baseUrl);
  
  check(res, {
    'status is 200': (r) => r.status === 200,
  });
  
  sleep(1);
}

Scenario execution output

When running scenarios, k6 displays scenario information:
  scenarios: (100.00%) 2 scenarios, 20 max VUs, 10m40s max duration:
           * shared_iter_scenario: 100 iterations shared among 10 VUs
           * per_vu_scenario: 10 iterations for each of 10 VUs (startTime: 10s)
Results are shown both overall and per-scenario:
 TOTAL RESULTS
    http_req_duration..........: avg=115.25ms
    http_reqs..................: 200

 SCENARIO: shared_iter_scenario
    http_req_duration..........: avg=115.65ms
    http_reqs..................: 100

 SCENARIO: per_vu_scenario
    http_req_duration..........: avg=114.86ms
    http_reqs..................: 100

Best practices

Choose the right executor

Use VU-based executors for closed models, arrival-rate executors for open models.

Use meaningful names

Name scenarios descriptively: “warmup”, “load_test”, “spike” not “scenario1”.

Tag for analysis

Add scenario-specific tags to enable granular threshold and analysis.

Sequence with startTime

Use startTime to create sequential test phases within one test run.

Next steps

Executors Reference

Detailed documentation for each executor type with advanced options

Build docs developers (and LLMs) love