import { check, sleep } from "k6"; import http from "k6/http"; import exec from "k6/execution"; import { Trend } from "k6/metrics"; function required(name) { const value = __ENV[name]; if (value === undefined || value === "") { throw new Error(`${name} is required`); } return value; } function positiveInteger(name) { const value = Number(required(name)); if (!Number.isInteger(value) || value <= 0) { throw new Error(`${name} must be a positive integer`); } return value; } function nonNegativeNumber(name) { const value = Number(required(name)); if (!Number.isFinite(value) || value < 0) { throw new Error(`${name} must be a non-negative number`); } return value; } const baseUrl = required("BASE_URL").replace(/\/+$/, ""); if (baseUrl !== "http://web:4000") { throw new Error("The scale read-only profile is pinned to http://web:4000"); } const sessionCookie = required("SESSION_COOKIE"); const vus = positiveInteger("VUS"); const duration = required("DURATION"); const thinkSeconds = nonNegativeNumber("THINK_SECONDS"); const viewports = [ { path: "/requests?west=22.096886&south=45.034261&east=38.949914&north=55.310025&zoom=5&width=767&height=734&view=split", endpoint: "requests_europe", heading: "Who needs help?", }, { path: "/activities?west=22.096886&south=45.034261&east=38.949914&north=55.310025&zoom=5&width=767&height=734&view=split", endpoint: "activities_europe", heading: "Nearby activities", }, { path: "/requests?west=-179.9&south=-80&east=179.9&north=80&zoom=1&width=1280&height=720&view=map", endpoint: "requests_world", heading: "Who needs help?", }, { path: "/activities?west=-179.9&south=-80&east=179.9&north=80&zoom=1&width=1280&height=720&view=map", endpoint: "activities_world", heading: "Nearby activities", }, ]; const endpointDurations = { requests_europe: new Trend("wnh_requests_europe_duration", true), activities_europe: new Trend("wnh_activities_europe_duration", true), requests_world: new Trend("wnh_requests_world_duration", true), activities_world: new Trend("wnh_activities_world_duration", true), }; export const options = { scenarios: { scale_readonly_http: { executor: "constant-vus", exec: "scaleReadonlyHttp", vus, duration, }, }, thresholds: { checks: [{ threshold: "rate==1", abortOnFail: true }], http_req_failed: [{ threshold: "rate==0", abortOnFail: true }], }, }; export function scaleReadonlyHttp() { const viewport = viewports[exec.scenario.iterationInTest % viewports.length]; const response = http.get(`${baseUrl}${viewport.path}`, { redirects: 0, tags: { endpoint: viewport.endpoint, safety_scope: "persistent_local_scale_readonly", }, headers: { Cookie: sessionCookie, Host: "scale.localhost", "X-Forwarded-Host": "scale.localhost", "X-Forwarded-Port": "4121", "X-Forwarded-Proto": "https", "User-Agent": "WhoNeedHelp-persistent-scale-readonly/1", }, }); endpointDurations[viewport.endpoint].add(response.timings.duration); check(response, { [`GET ${viewport.endpoint} returned 200`]: (candidate) => candidate.status === 200, [`GET ${viewport.endpoint} rendered its loading shell`]: (candidate) => candidate.body.includes(viewport.heading) && candidate.body.includes("Loading results"), }); sleep(thinkSeconds); }