k6-node
Version:
CLI tool that enables k6 installation via npm packages
273 lines (272 loc) • 10.2 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.K6TestBuilder = void 0;
const child_process_1 = require("child_process");
const fs_1 = __importDefault(require("fs"));
const os_1 = __importDefault(require("os"));
const path_1 = __importDefault(require("path"));
const k6path_1 = require("../k6path");
/**
* Builder class for creating and executing k6 load tests
* This class generates k6 JavaScript code and executes it using the k6 binary
*/
class K6TestBuilder {
constructor() {
this.config = {
scenarios: [],
};
this.imports = new Set([
"import http from 'k6/http';",
"import { check, sleep } from 'k6';",
]);
}
/**
* Add imports to the k6 test script
* In k6, imports bring in modules like http, websockets, or custom metrics
*
* @param imports - Array of import statements for k6 modules
* @returns This builder instance for method chaining
*/
addImports(...imports) {
imports.forEach((imp) => this.imports.add(imp));
return this;
}
/**
* Set global options for the k6 test
* These options configure test-wide settings like VUs, duration, and thresholds
*
* @param options - k6 options object defining test execution parameters
* @returns This builder instance for method chaining
*/
setOptions(options) {
this.config.options = { ...this.config.options, ...options };
return this;
}
/**
* Add a scenario to the k6 test
* Scenarios in k6 allow different execution patterns and workload models
*
* @param scenario - Scenario configuration with steps and executor
* @returns This builder instance for method chaining
*/
addScenario(scenario) {
this.config.scenarios.push(scenario);
return this;
}
/**
* Create a simple scenario with steps for k6 execution
*
* @param name - Unique name for the scenario
* @param steps - Array of steps to execute in k6
* @param executor - k6 executor type defining how VUs execute iterations
* @returns This builder instance for method chaining
*/
createScenario(name, steps, executor) {
return this.addScenario({
name,
steps,
executor: executor,
});
}
/**
* Set raw JavaScript code for k6 setup function
* In k6, setup runs once at test start and can return data to the main function
*
* @param setupCode - JavaScript code string for k6 setup function
* @returns This builder instance for method chaining
*/
setRawSetupCode(setupCode) {
this.config.setup = setupCode;
return this;
}
/**
* Set raw JavaScript code for k6 teardown function
* In k6, teardown runs once at test end for cleanup operations
*
* @param teardownCode - JavaScript code string for k6 teardown function
* @returns This builder instance for method chaining
*/
setRawTeardownCode(teardownCode) {
this.config.teardown = teardownCode;
return this;
}
/**
* Generate the complete k6 test script as JavaScript code
* This creates executable k6 code with imports, options, and scenario functions
*
* @returns Complete k6 test script as a string
*/
generateScript() {
let script = Array.from(this.imports).join('\n') + '\n\n';
if (this.config.options) {
script += `export const options = ${JSON.stringify(this.config.options, null, 2)};\n\n`;
}
if (this.config.setup) {
script += `export function setup() {\n${this.config.setup}\n}\n\n`;
}
if (this.config.teardown) {
script += `export function teardown(data) {\n${this.config.teardown}\n}\n\n`;
}
if (this.config.scenarios && this.config.scenarios.length > 0) {
if (this.config.scenarios.length === 1) {
// single scenario - use default function
const scenario = this.config.scenarios[0];
script += `export default function() {\n${this.generateScenarioCode(scenario)}\n}`;
}
else {
//multiple scenarios
script += `export const options = {\n`;
this.config.scenarios.forEach((scenario, index) => {
script += ` ${scenario.name}: {\n`;
script += ` executor: '${scenario.executor || 'shared-iterations'}',\n`;
// add other scenario-specific options
Object.keys(scenario).forEach((key) => {
if (!['name', 'steps', 'executor'].includes(key)) {
script += ` ${key}: ${JSON.stringify(scenario[key])},\n`;
}
});
script += ` }${index < this.config.scenarios.length - 1 ? ',' : ''}\n`;
});
script += `};\n\n`;
// fenerate scenario functions
this.config.scenarios.forEach((scenario) => {
script += `export function ${scenario.name}() {\n${this.generateScenarioCode(scenario)}\n}\n\n`;
});
}
}
else {
// default empty function if no scenarios
script += `export default function() {\n throw new Error("No scenarios defined");\n}`;
}
return script;
}
/**
* Generate k6 JavaScript code for a single scenario
* This creates the actual HTTP requests and checks that k6 will execute
*
* @param scenario - Scenario configuration to generate code for
* @returns JavaScript code string for the scenario
*/
generateScenarioCode(scenario) {
let code = '';
scenario.steps.forEach((step) => {
if (step.name) {
code += ` // ${step.name}\n`;
}
//
const method = step.request.method.toLowerCase();
const bodyParam = step.request.body
? `, ${JSON.stringify(step.request.body)}`
: '';
const paramsParam = step.request.params
? `, { ${Object.entries(step.request.params)
.map(([key, value]) => `${key}: ${JSON.stringify(value)}`)
.join(', ')} }`
: '';
code += ` let res = http.${method}('${step.request.url}'${bodyParam}${paramsParam});\n`;
//
if (step.checks && step.checks.length > 0) {
const checksObject = {};
step.checks.forEach((check) => {
checksObject[`'${check.name}'`] = check.condition.toString();
});
code += ` check(res, ${JSON.stringify(checksObject).replace(/"/g, '')});\n`;
}
//
if (step.sleep) {
code += ` sleep(${step.sleep});\n`;
}
code += '\n';
});
return code;
}
/**
* Execute the k6 test using the k6 binary
* This method generates the script, runs it with k6, and handles cleanup
*
* @param options - Execution options for k6 including output and environment
* @returns Promise that resolves when k6 execution completes
*/
async run(options = {}) {
const script = this.generateScript();
const tempDir = os_1.default.tmpdir();
const scriptPath = path_1.default.join(tempDir, `k6-test-${Date.now()}.js`);
//
fs_1.default.writeFileSync(scriptPath, script);
try {
const bin = await (0, k6path_1.getK6BinaryPath)();
const args = ['run', scriptPath];
//
if (options.output) {
args.push('--out', options.output);
}
//
if (options.quiet) {
args.push('--quiet');
}
if (options.verbose) {
args.push('--verbose');
}
console.log(`Running k6 test: ${scriptPath}`);
if (!options.quiet) {
console.log('Generated script:');
console.log('---');
console.log(script);
console.log('---');
}
return new Promise((resolve, reject) => {
const env = {
...process.env,
...options.environment,
};
const childProcess = (0, child_process_1.spawn)(bin, args, {
stdio: 'inherit',
env,
});
childProcess.on('close', (code) => {
// destroy temporary file
try {
fs_1.default.unlinkSync(scriptPath);
}
catch (e) {
// cleanupp errors is not important
}
if (code === 0) {
resolve();
}
else {
reject(new Error(`k6 exited with code ${code}`));
}
});
childProcess.on('error', (error) => {
try {
fs_1.default.unlinkSync(scriptPath);
}
catch (e) { }
reject(error);
});
});
}
catch (error) {
try {
fs_1.default.unlinkSync(scriptPath);
}
catch (e) { }
throw error;
}
}
/**
* Save the generated k6 script to a file for later use
*
* @param filePath - Path where to save the k6 script file
*/
saveScript(filePath) {
const script = this.generateScript();
fs_1.default.writeFileSync(filePath, script);
console.log(`Script saved to: ${filePath}`);
}
}
exports.K6TestBuilder = K6TestBuilder;