import { faker } from "https://cdn.jsdelivr.net/npm/@faker-js/faker@9.4.0/+esm"; import http from "k6/http"; import { check, sleep } from "k6"; import { Trend } from "k6/metrics"; import { textSummary } from 'https://jslib.k6.io/k6-summary/0.0.1/index.js'; import { SharedArray } from 'k6/data'; import encoding from 'k6/encoding'; const petCategory = [ "Dog", "Cat", "Fish", "Bird", "Hamster", "Gerbil", "Guinea Pig", "Rabbit", "Bearded Dragon", "Leopard Gecko", "Corn Snake", ]; const musicGenre = [ "Boy Band", "Soul", "Country & Western", "Hip-Hop", "K-Pop", "New Romantic", ]; const BASE_URL = "https://valentinesong.oliver.digital/back"; const randomItem = (arr) => arr[Math.floor(Math.random() * arr.length)]; // Custom metrics for tracking journey stages const submissionTime = new Trend("submission_duration"); const pollingWaitTime = new Trend("polling_wait_duration"); const resultFetchTime = new Trend("result_fetch_duration"); const totalJourneyTime = new Trend("total_journey_duration"); const pollAttempts = new Trend("poll_attempts_count"); // Load the image file and convert to base64 // Use SharedArray to load file only once and share across VUs const imageBase64 = new SharedArray('dogImage', function() { const binFile = open('./dog-test.jpeg', 'b'); return [encoding.b64encode(binFile)]; }); // Define the load test configuration export const options = { vus: 2, // number of virtual users running simultaneously iterations: 2, // number of journey/iteration to complete across all VUS maxDuration: "10m", // Allow up to 10 mins for the journey to finish }; export default function () { const journeyStartTime = Date.now(); const payload = JSON.stringify({ pet_name: faker.person.firstName(), pet_type: randomItem(petCategory), music_vibe: randomItem(musicGenre), owner_name: faker.person.firstName(), cookie_id: `cookie_test_${faker.string.alphanumeric(10)}`, // photo: "data:image/gif;base64,R0lGODlhAQABAAAAACw=", photo: `data:image/jpeg;base64,${imageBase64[0]}` }); const params = { headers: { "Content-Type": "application/json", Accept: "application/json", }, }; // Perform the POST request const submissionStart = Date.now(); const res = http.post(`${BASE_URL}/api/submissions`, payload, params); const submissionDuration = Date.now() - submissionStart; submissionTime.add(submissionDuration); // Validate the response const submissionCheck = check(res, { "status is 200": (r) => r.status === 201 || r.status === 200, "response time < 2s": (r) => r.timings.duration < 2000, valid_session_id: (r) => r.json().session_id.length > 10, }); if (!submissionCheck) { console.error(`❌ Submission failed for session`); return; // Exit if submission fails } const session_id = res.json().session_id; console.log(`✅ Submission successful. Session ID: ${session_id}`); // 2. Poll for Results let isReady = false; const pollingInterval = 20; // seconds const timeoutInSeconds = 10 * 60; // 10 minutes total wait time const pollingStartTime = Date.now(); let pollCount = 0; while (!isReady) { // 1. Check if we have timed out if ((Date.now() - pollingStartTime) / 1000 > timeoutInSeconds) { console.error(`âąī¸ Polling timed out for session: ${session_id}`); break; } // Wait before next polling sleep(pollingInterval); pollCount++; const pollRes = http.get( `${BASE_URL}/api/submissions/${session_id}/status`, ); check(pollRes, { "polling response received (status = 200)": (r) => r.status === 200, }); if (pollRes.status === 200 && pollRes.json().status === "success") { isReady = true; console.log( `🎉 Result ready after ${pollCount} poll attempts (${((Date.now() - pollingStartTime) / 1000).toFixed(1)}s)`, ); } } const pollingDuration = Date.now() - pollingStartTime; pollingWaitTime.add(pollingDuration); pollAttempts.add(pollCount); // 3. Final Result if (isReady) { const resultFetchStart = Date.now(); const resultRes = http.get(`${BASE_URL}/api/results/${session_id}`); const resultFetchDuration = Date.now() - resultFetchStart; resultFetchTime.add(resultFetchDuration); const resultCheck = check(resultRes, { "result fetched": (r) => r.status === 200, "status msg has success": (r) => r.json().status === "success", "has lyrics": (r) => r.json().lyrics.length > 10, "has video_url": (r) => r.json().video_url.length > 10, }); if (resultCheck) { const totalDuration = Date.now() - journeyStartTime; totalJourneyTime.add(totalDuration); console.log( `✅ Complete journey successful! Total time: ${(totalDuration / 1000).toFixed(1)}s`, ); } else { console.error(`❌ Result validation failed for session: ${session_id}`); } // User stays on result page for a bit sleep(20); } else { console.error( `❌ Journey incomplete - result never became ready for session: ${session_id}`, ); } // Wait 1 second between requests per user sleep(1); } export function handleSummary(data) { console.log('\n' + '='.repeat(80)); console.log('📊 CUSTOM METRICS SUMMARY'); console.log('â„šī¸ All durations are in milliseconds (ms). 1 second = 1,000ms | 1 minute = 60,000ms'); console.log('='.repeat(80) + '\n'); return { 'stdout': textSummary(data, { indent: ' ', enableColors: true }), }; }