| |
| |
|
|
| import http from 'k6/http'; |
| import { check, sleep } from 'k6'; |
| import { login, BASE_URL } from './k6_setup.js'; |
|
|
| |
| export const options = { |
| stages: [ |
| { duration: '3m', target: 20 }, |
| { duration: '5m', target: 20 }, |
| { duration: '1m', target: 0 }, |
| ], |
| thresholds: { |
| http_req_duration: ['p(95)<1000', 'p(99)<1500'], |
| http_req_failed: ['rate<0.10'], |
| checks: ['rate>0.90'], |
| }, |
| }; |
|
|
| export default function () { |
| |
|
|
| |
| let response = http.get(`${BASE_URL}/`, { |
| headers: { |
| 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', |
| 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36', |
| }, |
| }); |
|
|
| check(response, { |
| 'homepage loaded': (r) => r.status === 200 || r.status === 304, |
| }); |
|
|
| sleep(1); |
|
|
| |
| const token = login(); |
|
|
| if (!token) { |
| sleep(2); |
| return; |
| } |
|
|
| sleep(1); |
|
|
| |
| const authHeaders = { |
| headers: { |
| 'Authorization': `Bearer ${token}`, |
| 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', |
| 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36', |
| }, |
| }; |
|
|
| response = http.get(`${BASE_URL}/dashboard`, authHeaders); |
|
|
| check(response, { |
| 'dashboard loaded': (r) => r.status === 200 || r.status === 304, |
| }); |
|
|
| |
| sleep(2); |
|
|
| |
| const apiHeaders = { |
| headers: { |
| 'Authorization': `Bearer ${token}`, |
| 'Content-Type': 'application/json', |
| }, |
| }; |
|
|
| response = http.get(`${BASE_URL}/api/v1/agents`, apiHeaders); |
|
|
| check(response, { |
| 'agents list retrieved': (r) => r.status === 200, |
| 'agents list is array': (r) => { |
| try { |
| const body = r.json(); |
| return Array.isArray(body) || (body.data && Array.isArray(body.data)); |
| } catch { |
| return false; |
| } |
| }, |
| }); |
|
|
| sleep(1); |
|
|
| |
| const agentPayload = JSON.stringify({ |
| query: 'Hello, this is a test query from web UI', |
| agent_id: 'web-ui-test-agent', |
| }); |
|
|
| response = http.post(`${BASE_URL}/api/v1/agents/execute`, agentPayload, { |
| headers: { |
| 'Authorization': `Bearer ${token}`, |
| 'Content-Type': 'application/json', |
| }, |
| }); |
|
|
| check(response, { |
| 'agent execution started': (r) => r.status === 200 || r.status === 202, |
| 'agent execution has response': (r) => r.body.length > 0, |
| }); |
|
|
| sleep(2); |
|
|
| |
| const canvasId = 'web-ui-canvas-' + Math.floor(Math.random() * 10); |
| response = http.get(`${BASE_URL}/api/v1/canvas/${canvasId}`, apiHeaders); |
|
|
| check(response, { |
| 'canvas retrieved': (r) => r.status === 200 || r.status === 404, |
| }); |
|
|
| sleep(1); |
|
|
| |
| const canvasPayload = JSON.stringify({ |
| canvas_id: canvasId, |
| }); |
|
|
| response = http.post(`${BASE_URL}/api/v1/canvas/present`, canvasPayload, { |
| headers: { |
| 'Authorization': `Bearer ${token}`, |
| 'Content-Type': 'application/json', |
| }, |
| }); |
|
|
| check(response, { |
| 'canvas presentation started': (r) => r.status === 200 || r.status === 202, |
| }); |
|
|
| sleep(2); |
|
|
| |
| if (Math.random() < 0.5) { |
| response = http.get(`${BASE_URL}/api/v1/workflows`, apiHeaders); |
|
|
| check(response, { |
| 'workflows list retrieved': (r) => r.status === 200, |
| }); |
|
|
| sleep(1); |
| } |
|
|
| |
| if (Math.random() < 0.3) { |
| const logoutPayload = JSON.stringify({}); |
| response = http.post(`${BASE_URL}/api/auth/logout`, logoutPayload, { |
| headers: { |
| 'Authorization': `Bearer ${token}`, |
| 'Content-Type': 'application/json', |
| }, |
| }); |
|
|
| check(response, { |
| 'logout successful': (r) => r.status === 200 || r.status === 204, |
| }); |
|
|
| sleep(1); |
| } |
| } |
|
|
| |
| export function handleSummary(data) { |
| return { |
| 'stdout': textSummary(data, { indent: ' ', enableColors: true }), |
| }; |
| } |
|
|
| |
| function textSummary(data, options = {}) { |
| const { indent = '', enableColors = false } = options; |
|
|
| let summary = `${indent}Web UI Load Test Summary (20 Users)\n`; |
| summary += `${indent}==================================\n\n`; |
|
|
| |
| const httpReqs = data.metrics.http_reqs; |
| const httpFailed = data.metrics.http_req_failed; |
| const httpDuration = data.metrics.http_req_duration; |
| const checks = data.metrics.checks; |
|
|
| if (httpReqs && httpReqs.values) { |
| summary += `${indent}Total Requests: ${httpReqs.values.count || 0}\n`; |
| } |
|
|
| if (httpFailed && httpFailed.values) { |
| summary += `${indent}Failed Requests: ${httpFailed.values.fails || 0}\n`; |
| summary += `${indent}Failure Rate: ${((httpFailed.values.rate || 0) * 100).toFixed(2)}%\n\n`; |
| } |
|
|
| |
| if (httpDuration && httpDuration.values) { |
| summary += `${indent}Response Times:\n`; |
| summary += `${indent} P50: ${(httpDuration.values['p(50)'] || 0).toFixed(0)}ms\n`; |
| summary += `${indent} P95: ${(httpDuration.values['p(95)'] || 0).toFixed(0)}ms\n`; |
| summary += `${indent} P99: ${(httpDuration.values['p(99)'] || 0).toFixed(0)}ms\n\n`; |
| } |
|
|
| |
| if (checks && checks.values) { |
| summary += `${indent}Checks:\n`; |
| summary += `${indent} Passed: ${checks.values.passes || 0}\n`; |
| summary += `${indent} Failed: ${checks.values.fails || 0}\n`; |
| const passRate = checks.values.count > 0 |
| ? ((checks.values.passes / checks.values.count) * 100).toFixed(2) |
| : '0.00'; |
| summary += `${indent} Pass Rate: ${passRate}%\n`; |
| } |
|
|
| return summary; |
| } |
|
|