import http from "k6/http"; import { check, sleep, group } from "k6"; import { Counter, Trend } from "k6/metrics"; // ---------------- Configuration ---------------- export const options = { // Adjust vus/duration for your environment scenarios: { steady_mutations: { executor: "constant-vus", vus: __ENV.VUS ? parseInt(__ENV.VUS, 10) : 20, duration: __ENV.DURATION || "5m", gracefulStop: "30s", }, ramp_up: { executor: "ramping-vus", startVUs: 0, stages: [ { duration: "1m", target: __ENV.RAMP_TARGET ? parseInt(__ENV.RAMP_TARGET, 10) : 50, }, { duration: "1m", target: __ENV.RAMP_TARGET ? parseInt(__ENV.RAMP_TARGET, 10) : 50, }, { duration: "1m", target: 0 }, ], gracefulStop: "30s", startTime: "5m", }, }, thresholds: { http_req_failed: ["rate<0.02"], // < 2% failures http_req_duration: ["p(90)<800", "p(99)<1500"], // latency SLO "cart_add_item_duration{op:add}": ["p(90)<800"], "cart_fetch_duration{op:get}": ["p(90)<600"], }, summaryTrendStats: ["avg", "min", "med", "max", "p(90)", "p(95)", "p(99)"], }; // ---------------- Metrics ---------------- const addItemTrend = new Trend("cart_add_item_duration", true); const fetchTrend = new Trend("cart_fetch_duration", true); const checkoutTrend = new Trend("cart_checkout_duration", true); const addedItemsCounter = new Counter("cart_items_added"); const checkoutCounter = new Counter("cart_checkout_calls"); // ---------------- SKUs ---------------- const SKUS = [ "778290", "778345", "778317", "778277", "778267", "778376", "778244", "778384", "778365", "778377", "778255", "778286", "778246", "778270", "778266", "778285", "778329", "778425", "778407", "778418", "778430", "778469", "778358", "778351", "778319", "778307", "778278", "778251", "778253", "778261", "778263", "778273", "778281", "778294", "778297", "778302", ]; // ---------------- Helpers ---------------- function randomSku() { return SKUS[Math.floor(Math.random() * SKUS.length)]; } function randomQty() { return 1 + Math.floor(Math.random() * 3); // 1..3 } function baseUrl() { const u = __ENV.BASE_URL || "http://localhost:8080/cart"; // Allow user to pass either root host or full /cart path return u.endsWith("/cart") ? u : u.replace(/\/+$/, "") + "/cart"; } function extractCookie(res, name) { const cookies = res.cookies[name]; if (!cookies || cookies.length === 0) return null; return cookies[0].value; } function withCookies(headers, cookieJar) { if (!cookieJar || Object.keys(cookieJar).length === 0) return headers; const cookieStr = Object.entries(cookieJar) .map(([k, v]) => `${k}=${v}`) .join("; "); return { ...headers, Cookie: cookieStr }; } // Maintain cart + owner cookies per VU let cartState = { cartid: null, cartowner: null, }; // Refresh cookies from response function updateCookies(res) { const cid = extractCookie(res, "cartid"); if (cid) cartState.cartid = cid; const owner = extractCookie(res, "cartowner"); if (owner) cartState.cartowner = owner; } // Build headers function headers() { const h = { "Content-Type": "application/json" }; const jar = {}; if (cartState.cartid) jar["cartid"] = cartState.cartid; if (cartState.cartowner) jar["cartowner"] = cartState.cartowner; return withCookies(h, jar); } // Ensure cart exists (GET /) function ensureCart() { if (cartState.cartid) return; const res = http.get(baseUrl() + "/", { headers: headers() }); updateCookies(res); check(res, { "ensure cart status 200": (r) => r.status === 200, "ensure cart has id": () => !!cartState.cartid, }); } // Add random item function addRandomItem() { const payload = JSON.stringify({ sku: randomSku(), quantity: randomQty(), country: "no", }); const start = Date.now(); const res = http.post(baseUrl() + "/", payload, { headers: headers() }); const dur = Date.now() - start; addItemTrend.add(dur, { op: "add" }); if (res.status === 200) { addedItemsCounter.add(1); } updateCookies(res); check(res, { "add item status ok": (r) => r.status === 200, }); } // Fetch cart state function fetchCart() { const start = Date.now(); const res = http.get(baseUrl() + "/", { headers: headers() }); const dur = Date.now() - start; fetchTrend.add(dur, { op: "get" }); updateCookies(res); check(res, { "fetch status ok": (r) => r.status === 200 }); } // Occasional checkout trigger function maybeCheckout() { if (!cartState.cartid) return; // Small probability if (Math.random() < 0.02) { const start = Date.now(); const res = http.get(baseUrl() + "/checkout", { headers: headers() }); const dur = Date.now() - start; checkoutTrend.add(dur, { op: "checkout" }); updateCookies(res); if (res.status === 200) checkoutCounter.add(1); check(res, { "checkout status ok": (r) => r.status === 200 }); } } // ---------------- k6 lifecycle ---------------- export function setup() { // Provide SKU list length for summary return { skuCount: SKUS.length }; } export default function (data) { group("cart flow", () => { // Create or reuse cart ensureCart(); // Random number of item mutations per iteration (1..5) const ops = 1 + Math.floor(Math.random() * 5); for (let i = 0; i < ops; i++) { addRandomItem(); } // Fetch state fetchCart(); // Optional checkout attempt maybeCheckout(); }); // Small think time sleep(Math.random() * 0.5); } export function teardown(data) { // Optionally we could GET confirmation or clear cart cookie // Not implemented for load purpose. console.log(`Test complete. SKU count: ${data.skuCount}`); } // ---------------- Summary ---------------- export function handleSummary(data) { return { stdout: JSON.stringify( { metrics: { mutations_avg: data.metrics.cart_add_item_duration?.avg, mutations_p95: data.metrics.cart_add_item_duration?.p(95), fetch_p95: data.metrics.cart_fetch_duration?.p(95), checkout_count: data.metrics.cart_checkout_calls?.count, }, checks: data.root_checks, }, null, 2, ), }; }