import http from "k6/http"; import { check, sleep } from "k6"; const BASE = "https://school.second-brain.ru"; // Реальные lesson IDs курса obsidian (опубликованные уроки) const LESSONS = [ "c729fjgtrl0tfowh49jh55uak", "ctxca16mjamn5bh2exa3dxltg", "c1f130hwjgks3zm4ohrcneueh", "cn3bahic20cdxj9ih4cxr8tjl", "c2usfe6rwoqcombd9veaalvgj", "clil8czg79uqmqtexw8e5cede", "c0ej1a3wrueg60d1oew2j8ky6", "cypv15bq07deuyi2tjb556n52", "c7v4qdnowy7i6y7pp361dwne3", "c3l9ox9xvd5qyv5mt2pd7if2x", ]; const TEST_USER = { email: "loadtest@second-brain.ru", password: "LoadTest2025!", }; const BROWSER_HEADERS = { "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", "Accept-Language": "ru-RU,ru;q=0.9", }; export const options = { stages: [ { duration: "30s", target: 10 }, // разгон до 10 пользователей { duration: "1m", target: 50 }, // разгон до 50 { duration: "3m", target: 50 }, // держим 50 три минуты { duration: "30s", target: 0 }, // спад ], thresholds: { http_req_duration: ["p(95)<3000"], // 95% запросов быстрее 3 секунд http_req_failed: ["rate<0.05"], // ошибок меньше 5% (было 1%, но до фикса Kinescope) }, }; // Переменная уровня VU — логин один раз на весь жизненный цикл VU. let isLoggedIn = false; export default function () { // http.cookieJar() без аргументов — jar уровня VU, сохраняется между итерациями. const jar = http.cookieJar(); // 1. Логин — только при первой итерации VU if (!isLoggedIn) { const loginRes = http.post( `${BASE}/api/auth/sign-in/email`, JSON.stringify({ email: TEST_USER.email, password: TEST_USER.password }), { headers: { "Content-Type": "application/json" }, jar } ); check(loginRes, { "login 200": (r) => r.status === 200 }); if (loginRes.status !== 200) { sleep(5); // пауза при неудаче, не штурмуем auth endpoint return; } isLoggedIn = true; sleep(1); } // 2. Дашборд студента const dashRes = http.get(`${BASE}/dashboard`, { jar, headers: BROWSER_HEADERS }); check(dashRes, { "dashboard 200": (r) => r.status === 200 }); sleep(1); // 3. Страница курса const courseRes = http.get(`${BASE}/courses/obsidian`, { jar, headers: BROWSER_HEADERS }); check(courseRes, { "course page 200": (r) => r.status === 200 }); sleep(2); // 4. Открыть 3 случайных урока (имитация чтения) for (let i = 0; i < 3; i++) { const lessonId = LESSONS[Math.floor(Math.random() * LESSONS.length)]; const lessonRes = http.get( `${BASE}/courses/obsidian/lessons/${lessonId}`, { jar, headers: BROWSER_HEADERS } ); check(lessonRes, { "lesson page 200": (r) => r.status === 200 }); sleep(Math.random() * 3 + 2); // студент "читает" 2-5 секунд } }