The Independent Blueprint for Modern Load Testing & Grafana k6 Performance Engineering

Architecting an Enterprise k6 Framework on Windows

Posted on May 24, 2026 in Windows Operations

If you are managing performance testing suites across multiple teams, stick-scripting (writing a single massive script that contains configuration, credentials, custom clients, and user paths in one file) is a massive mistake. When APIs change or endpoints are added, you end up manually rewriting dozens of distinct files. To scale load testing across your organization, you must design a modular performance testing framework that decouples configuration profiles, user data matrices, core operational helpers, and user journeys.

Let us look at how to structure a clean, maintainable, and highly efficient k6 performance testing framework on Windows.

Framework Directory Blueprint

To establish a clean separation of concerns, create the following directory structure inside your workspace, for example, C:\k6-performance-framework\:

C:\k6-performance-framework
├── config/
│   ├── smoke.json
│   └── average-load.json
├── data/
│   └── test-users.json
├── helpers/
│   ├── auth.js
│   └── http-client.js
├── scenarios/
│   ├── browse-catalog.js
│   └── checkout-cart.js
├── reports/
└── main.js

1. Configuration Profiles (config/average-load.json)

We decouple configuration profiles from script code. This profile defines execution scenarios and error validation gates using JSON files. We use k6's constant-arrival-rate executor to enforce an open-loop model, which decouples request rate from system latency, preventing coordinated omission:

{
  "scenarios": {
    "api_checkout_load": {
      "executor": "constant-arrival-rate",
      "rate": 25,
      "timeUnit": "1s",
      "duration": "3m",
      "preAllocatedVUs": 50,
      "maxVUs": 500,
      "exec": "executeCheckoutScenario"
    }
  },
  "thresholds": {
    "http_req_failed": ["rate<0.01"],
    "http_req_duration": ["p(95)<300", "p(99)<600"],
    "checkout_success_rate": ["rate>0.98"]
  }
}

2. Parameterized Test Data (data/test-users.json)

Store your parameterized credentials and tenant profiles in this structured JSON format:

[
  { "id": 101, "username": "customer_alpha", "token": "tkn_sec_01a" },
  { "id": 102, "username": "customer_beta", "token": "tkn_sec_02b" },
  { "id": 103, "username": "customer_gamma", "token": "tkn_sec_03c" }
]

3. Shared Authentication Helper (helpers/auth.js)

Encapsulate your handshake and token exchange logic here so it can be reused across different scenarios:

import http from 'k6/http';
import { check } from 'k6';

export function getAuthorizationToken(baseUrl, clientId, authToken) {
  const payload = JSON.stringify({ clientId: clientId, token: authToken });
  const params = { headers: { 'Content-Type': 'application/json' } };
  const response = http.post(`${baseUrl}/api/v1/auth`, payload, params);
  
  const authorized = check(response, { 'auth_status_is_200': (r) => r.status === 200 });
  if (!authorized) return null;
  
  return {
    'Authorization': `Bearer ${response.json().access_token}`,
    'Content-Type': 'application/json'
  };
}

4. Custom HTTP API Client (helpers/http-client.js)

Build a secure class wrapper around k6's core HTTP package. This class automates request headers, handles payload conversions, and tracks custom business success rates automatically:

import http from 'k6/http';
import { Rate } from 'k6/metrics';

export const checkoutRateTracker = new Rate('checkout_success_rate');

export class SecureAPIClient {
  constructor(baseUrl, headers) {
    this.baseUrl = baseUrl;
    this.headers = headers;
  }

  post(endpoint, payload) {
    const url = `${this.baseUrl}${endpoint}`;
    const response = http.post(url, JSON.stringify(payload), { headers: this.headers });
    const isSuccessful = response.status === 201;
    checkoutRateTracker.add(isSuccessful);
    return response;
  }
}

5. Checkout Scenario Implementation (scenarios/checkout-cart.js)

Define your core functional user journey here, keeping it completely clean of database operations or global option checks:

import { getAuthorizationToken } from '../helpers/auth.js';
import { SecureAPIClient } from '../helpers/http-client.js';

export function runCheckoutWorkflow(selectedUser, baseUrl) {
  const headers = getAuthorizationToken(baseUrl, selectedUser.id, selectedUser.token);
  if (!headers) throw new Error(`Failed to authenticate user: ${selectedUser.username}`);

  const client = new SecureAPIClient(baseUrl, headers);
  const orderDetails = {
    productId: Math.floor(Math.random() * 100) + 1,
    quantity: 2,
    expressShipping: true
  };
  client.post('/api/v1/checkout', orderDetails);
}

6. Central Test Execution Orchestrator (main.js)

This master entry point ties the entire framework together. It loads configuration profiles dynamically based on env variables, registers options, loads datasets using memory-optimized SharedArrays, and routes executions to their target scenarios:

import { SharedArray } from 'k6/data';
import { runCheckoutWorkflow } from './scenarios/checkout-cart.js';

// Load configuration profiles dynamically
const configurationProfile = JSON.parse(open(__ENV.CONFIG_PATH || './config/smoke.json'));

export const options = {
  discardResponseBodies: true,
  scenarios: configurationProfile.scenarios,
  thresholds: configurationProfile.thresholds
};

// Allocate credentials dataset once on Go heap, shared across VUs
const userRecords = new SharedArray('active_users', function () {
  return JSON.parse(open('./data/test-users.json'));
});

// Dynamic scenario router
export function executeCheckoutScenario() {
  const host = __ENV.BASE_URL || 'https://api.staging.internal';
  
  // Distribute credentials evenly across VUs using clean iteration math
  const userIndex = (__VU - 1 + __ITER) % userRecords.length;
  const user = userRecords[userIndex];
  
  runCheckoutWorkflow(user, host);
}

By shifting to this decoupled framework pattern, your codebase remains clean, easy to test, and ready to scale across hundreds of custom endpoints without code duplication.

Now that your framework is designed, see how to orchestrate session variables in Windows Environment Variables: Handling State in PowerShell and CMD for k6. Learn how to compile customized local engines in Building custom k6.exe Binaries on Windows using xk6. Learn how to run your workloads and export compressed reports in k6 Workload Execution and static Gzip HTML Report Generation on Windows.