/** * K6 Test Helpers * Reusable helper functions for load tests */ import http from 'k6/http'; import { check } from 'k6'; import { urls, checks } from './config.js'; /** * Authentication helper */ export class AuthHelper { constructor(baseUrl) { this.baseUrl = baseUrl; this.token = null; } /** * Login and store token */ login(email, password) { const res = http.post(`${this.baseUrl}/api/users/login`, JSON.stringify({ email, password, }), { headers: { 'Content-Type': 'application/json', }, }); const success = check(res, { 'login successful': checks.status200, 'received token': (r) => r.json('token') !== undefined, }); if (success) { this.token = res.json('token'); } return { res, success }; } /** * Get auth headers */ getAuthHeaders() { if (!this.token) { return {}; } return { 'Authorization': `Bearer ${this.token}`, }; } /** * Logout and clear token */ logout() { const res = http.post(`${this.baseUrl}/api/users/logout`, null, { headers: this.getAuthHeaders(), }); this.token = null; return res; } } /** * Page load helper */ export class PageHelper { constructor(baseUrl) { this.baseUrl = baseUrl; } /** * Load a page and check response */ loadPage(path) { const url = path.startsWith('http') ? path : `${this.baseUrl}${path}`; const res = http.get(url); check(res, { [`page loaded: ${path}`]: checks.status200, 'response time < 500ms': checks.nfr4ResponseTime, 'has content': checks.hasContent, }); return res; } /** * Load multiple pages randomly */ loadRandomPages(pageList, count = 5) { const pages = []; for (let i = 0; i < count; i++) { const randomPage = pageList[Math.floor(Math.random() * pageList.length)]; pages.push(this.loadPage(randomPage)); } return pages; } } /** * API helper */ export class ApiHelper { constructor(baseUrl) { this.baseUrl = baseUrl; this.token = null; } /** * Set auth token */ setToken(token) { this.token = token; } /** * Get default headers */ getHeaders(additionalHeaders = {}) { const headers = { 'Content-Type': 'application/json', ...additionalHeaders, }; if (this.token) { headers['Authorization'] = `Bearer ${this.token}`; } return headers; } /** * Make a GET request */ get(endpoint, params = {}) { const url = new URL(`${this.baseUrl}${endpoint}`); Object.keys(params).forEach(key => url.searchParams.append(key, params[key])); const res = http.get(url.toString(), { headers: this.getHeaders(), }); check(res, { [`GET ${endpoint} successful`]: checks.status200, 'API response time < 300ms': checks.nfr4ApiResponseTime, }); return res; } /** * Make a POST request */ post(endpoint, data) { const res = http.post(`${this.baseUrl}${endpoint}`, JSON.stringify(data), { headers: this.getHeaders(), }); check(res, { [`POST ${endpoint} successful`]: (r) => [200, 201].includes(r.status), 'API response time < 300ms': checks.nfr4ApiResponseTime, }); return res; } /** * Make a PUT request */ put(endpoint, data) { const res = http.put(`${this.baseUrl}${endpoint}`, JSON.stringify(data), { headers: this.getHeaders(), }); check(res, { [`PUT ${endpoint} successful`]: (r) => [200, 204].includes(r.status), 'API response time < 300ms': checks.nfr4ApiResponseTime, }); return res; } /** * Make a DELETE request */ delete(endpoint) { const res = http.del(`${this.baseUrl}${endpoint}`, null, { headers: this.getHeaders(), }); check(res, { [`DELETE ${endpoint} successful`]: (r) => [200, 204].includes(r.status), 'API response time < 300ms': checks.nfr4ApiResponseTime, }); return res; } /** * GraphQL query helper */ graphql(query, variables = {}) { const res = http.post(`${this.baseUrl}/api/graphql`, JSON.stringify({ query, variables, }), { headers: this.getHeaders(), }); check(res, { 'GraphQL successful': (r) => { if (r.status !== 200) return false; const body = r.json(); return !body.errors; }, 'GraphQL response time < 300ms': checks.nfr4ApiResponseTime, }); return res; } } /** * Think time helper * Simulates real user think time between actions */ export function thinkTime(min = 1, max = 3) { sleep(Math.random() * (max - min) + min); } /** * Random item picker */ export function pickRandom(array) { return array[Math.floor(Math.random() * array.length)]; } /** * Weighted random picker */ export function pickWeighted(items) { const totalWeight = items.reduce((sum, item) => sum + item.weight, 0); let random = Math.random() * totalWeight; for (const item of items) { random -= item.weight; if (random <= 0) { return item.value; } } return items[0].value; } /** * Generate random test data */ export const testData = { // Random string string(length = 10) { const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; let result = ''; for (let i = 0; i < length; i++) { result += chars.charAt(Math.floor(Math.random() * chars.length)); } return result; }, // Random email email() { return `test_${this.string(8)}@example.com`; }, // Random number number(min = 0, max = 100) { return Math.floor(Math.random() * (max - min + 1)) + min; }, // Random phone phone() { return `09${this.number(10000000, 99999999)}`; }, // Random title title() { const adjectives = ['Amazing', 'Awesome', 'Brilliant', 'Creative', 'Dynamic']; const nouns = ['Project', 'Solution', 'Product', 'Service', 'Innovation']; return `${pickRandom(adjectives)} ${pickRandom(nouns)}`; }, // Random content content(paragraphs = 3) { const sentences = [ 'This is a test sentence for load testing purposes.', 'Load testing helps identify performance bottlenecks.', 'We ensure the system can handle expected traffic.', 'Performance is critical for user experience.', 'Testing under load reveals system behavior.', ]; let content = ''; for (let i = 0; i < paragraphs; i++) { content += '\n\n'; for (let j = 0; j < 3; j++) { content += pickRandom(sentences) + ' '; } } return content.trim(); }, }; /** * Metrics helper */ export class MetricsHelper { constructor() { this.metrics = {}; } /** * Record a metric */ record(name, value) { if (!this.metrics[name]) { this.metrics[name] = { count: 0, sum: 0, min: Infinity, max: -Infinity, }; } const metric = this.metrics[name]; metric.count++; metric.sum += value; metric.min = Math.min(metric.min, value); metric.max = Math.max(metric.max, value); } /** * Get metric statistics */ getStats(name) { const metric = this.metrics[name]; if (!metric) { return null; } return { count: metric.count, sum: metric.sum, avg: metric.sum / metric.count, min: metric.min, max: metric.max, }; } /** * Print all metrics */ printAll() { console.log('=== Custom Metrics ==='); Object.keys(this.metrics).forEach(name => { const stats = this.getStats(name); console.log(`${name}:`, stats); }); } } /** * Scenario helper * Define test scenarios with weights */ export class ScenarioHelper { constructor() { this.scenarios = {}; } /** * Register a scenario */ register(name, fn, weight = 1) { this.scenarios[name] = { fn, weight }; } /** * Execute a random scenario based on weights */ execute() { const scenarios = Object.entries(this.scenarios).map(([name, { fn, weight }]) => ({ value: fn, weight, })); const scenario = pickWeighted(scenarios); scenario(); } }