import { chromium, type BrowserContext, type Request } from "playwright"; import { existsSync } from "node:fs"; import { resolve } from "node:path"; import { readConfig } from "./config"; interface CodeQueuePerfOptions { url: string; username: string; password: string; timeoutMs: number; targetMs: number; headless: boolean; json: boolean; } interface ApiTiming { url: string; method: string; status: number; durationMs: number; } function argValue(args: string[], name: string): string | null { const index = args.indexOf(name); if (index === -1) return null; const value = args[index + 1]; if (value === undefined || value.startsWith("--")) throw new Error(`${name} requires a value`); return value; } function numberArg(args: string[], name: string, fallback: number): number { const raw = argValue(args, name); if (raw === null) return fallback; const value = Number(raw); if (!Number.isFinite(value) || value <= 0) throw new Error(`${name} must be a positive number`); return Math.floor(value); } function normalizeBaseUrl(value: string): string { return value.replace(/\/+$/u, ""); } function readOptions(): CodeQueuePerfOptions { const config = readConfig(); const args = process.argv.slice(2); return { url: normalizeBaseUrl(argValue(args, "--url") ?? `http://${config.network.publicHost}:${config.network.frontend.port}`), username: argValue(args, "--username") ?? config.auth.username, password: argValue(args, "--password") ?? config.auth.password, timeoutMs: numberArg(args, "--timeout-ms", 90_000), targetMs: numberArg(args, "--target-ms", 1_000), headless: !args.includes("--headed"), json: args.includes("--json"), }; } async function authenticateSession(context: BrowserContext, options: CodeQueuePerfOptions): Promise { const response = await fetch(`${options.url}/login`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ username: options.username, password: options.password }), signal: AbortSignal.timeout(options.timeoutMs), }); if (!response.ok) { throw new Error(`login failed: HTTP ${response.status} ${await response.text()}`); } const setCookie = response.headers.get("set-cookie"); const cookiePair = setCookie?.split(";", 1)[0] ?? ""; const separator = cookiePair.indexOf("="); if (separator <= 0) throw new Error("login response did not set a session cookie"); await context.addCookies([{ name: cookiePair.slice(0, separator), value: cookiePair.slice(separator + 1), url: options.url, }]); } function requestPath(request: Request): string { try { const url = new URL(request.url()); return `${url.pathname}${url.search}`; } catch { return request.url(); } } function parseNumberAttr(value: string | null): number | null { const parsed = Number(value); return Number.isFinite(parsed) ? parsed : null; } async function runCodeQueuePerf(options: CodeQueuePerfOptions): Promise> { const browsersPath = process.env.PLAYWRIGHT_BROWSERS_PATH || ".state/playwright-browsers"; const fullChromePath = resolve(browsersPath, "chromium-1217/chrome-linux64/chrome"); const launchArgs = [ "--no-sandbox", "--disable-setuid-sandbox", "--disable-dev-shm-usage", ]; const launchOptions: Parameters[0] = { headless: options.headless, args: launchArgs }; if (existsSync(fullChromePath)) { launchOptions.executablePath = fullChromePath; } const browser = await chromium.launch(launchOptions); const apiStartedAt = new Map(); const apiTimings: ApiTiming[] = []; const consoleErrors: string[] = []; const targetUrl = `${options.url}/app/code-queue/`; const measuredAt = new Date().toISOString(); try { const context = await browser.newContext(); await authenticateSession(context, options); const page = await context.newPage(); await page.route("**/favicon.ico", (route) => route.abort()); page.on("console", (message) => { if (message.type() === "error") consoleErrors.push(message.text().slice(0, 500)); }); page.on("request", (request) => { const path = requestPath(request); if (path.startsWith("/api/") || path === "/app.js") apiStartedAt.set(request, performance.now()); }); page.on("response", (response) => { const request = response.request(); const started = apiStartedAt.get(request); if (started === undefined) return; apiStartedAt.delete(request); apiTimings.push({ url: requestPath(request), method: request.method(), status: response.status(), durationMs: Math.round((performance.now() - started) * 10) / 10, }); }); const startedAt = performance.now(); await page.goto(targetUrl, { waitUntil: "domcontentloaded", timeout: options.timeoutMs }); const domContentLoadedMs = Math.round((performance.now() - startedAt) * 10) / 10; await page.waitForSelector('[data-testid="code-queue-page"][data-load-state="complete"]', { timeout: options.timeoutMs }); const completeAt = performance.now(); const dom = await page.evaluate(() => { const pageElement = document.querySelector('[data-testid="code-queue-page"]') as HTMLElement | null; const output = document.querySelector('[data-testid="codex-output"]') as HTMLElement | null; const tasks = document.querySelectorAll('[data-testid^="codex-task-codex_"]'); return { loadState: pageElement?.dataset.loadState ?? "", componentLoadMs: pageElement?.dataset.loadTotalMs ?? "", queueMs: pageElement?.dataset.loadQueueMs ?? "", detailMs: pageElement?.dataset.loadDetailMs ?? "", transcriptRows: pageElement?.dataset.loadTranscriptRows ?? "", taskId: pageElement?.dataset.loadTaskId ?? "", partial: pageElement?.dataset.loadPartial === "true", visibleTaskCount: tasks.length, outputChars: output?.textContent?.length ?? 0, }; }); let networkIdleReached = true; try { await page.waitForLoadState("networkidle", { timeout: 5_000 }); } catch { networkIdleReached = false; } const networkIdleMs = Math.round((performance.now() - startedAt) * 10) / 10; const playwrightObservedMs = Math.round((completeAt - startedAt) * 10) / 10; const browserLoadMs = parseNumberAttr(dom.componentLoadMs); const totalLoadMs = browserLoadMs ?? playwrightObservedMs; const slowestApi = apiTimings.slice().sort((left, right) => right.durationMs - left.durationMs).slice(0, 8); const result = { ok: true, measuredAt, url: targetUrl, targetMs: options.targetMs, withinTarget: totalLoadMs <= options.targetMs, status: totalLoadMs <= options.targetMs ? "passed" : "slow", wallMs: totalLoadMs, totalLoadMs, domContentLoadedMs, appCompleteMs: playwrightObservedMs, playwrightObservedMs, networkIdleMs, networkIdleReached, componentLoadMs: browserLoadMs, queueMs: parseNumberAttr(dom.queueMs), detailMs: parseNumberAttr(dom.detailMs), transcriptRows: parseNumberAttr(dom.transcriptRows), partial: dom.partial, selectedTaskId: dom.taskId || null, visibleTaskCount: dom.visibleTaskCount, outputChars: dom.outputChars, apiRequestCount: apiTimings.length, slowestApi, consoleErrors, }; await context.close(); return result; } catch (error) { return { ok: false, measuredAt, url: targetUrl, targetMs: options.targetMs, status: "failed", error: error instanceof Error ? error.message : String(error), apiRequestCount: apiTimings.length, slowestApi: apiTimings.slice().sort((left, right) => right.durationMs - left.durationMs).slice(0, 8), consoleErrors, }; } finally { await browser.close(); } } const options = readOptions(); const result = await runCodeQueuePerf(options); if (options.json) { console.log(JSON.stringify(result)); } else { console.log(JSON.stringify(result, null, 2)); } if (result.ok !== true) process.exitCode = 1;