From 6363d96a1bb69f2468129674c03036625684119d Mon Sep 17 00:00:00 2001 From: Thomas Camlong Date: Sun, 9 Aug 2026 00:10:35 +0200 Subject: [PATCH] test(benchmarks): fix post-login wait, allow V8 flag injection Sign-in redirects client-side, so page.waitForURL never reached its implicit "load" state and every run failed after the restore step even though authentication had succeeded. Poll window.location instead. STRESS_NODE_OPTIONS injects NODE_OPTIONS directly, which is what makes the V8 heap cap attributable: the merge-base run.sh does not read HOMARR_MAX_OLD_SPACE_SIZE, so the cap can only be A/B'd against that image by passing the flag in. --- scripts/benchmarks/stress-restore.mts | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/scripts/benchmarks/stress-restore.mts b/scripts/benchmarks/stress-restore.mts index 89432e064..e25e34b91 100644 --- a/scripts/benchmarks/stress-restore.mts +++ b/scripts/benchmarks/stress-restore.mts @@ -39,6 +39,7 @@ const backupZip = path.resolve(requireEnv("STRESS_BACKUP_ZIP").replace(/^~/, os. const label = process.env.STRESS_LABEL ?? image.replace(/[^a-z0-9]+/gi, "-"); const memoryLimit = process.env.STRESS_MEMORY_LIMIT ?? "2g"; const heapMb = process.env.STRESS_HEAP_MB ?? ""; +const nodeOptions = process.env.STRESS_NODE_OPTIONS ?? ""; const navIterations = Number(process.env.STRESS_NAV_ITERATIONS ?? 6); const soakMs = Number(process.env.STRESS_SOAK_MS ?? 120_000); const soakIntervalMs = Number(process.env.STRESS_SOAK_INTERVAL_MS ?? 20_000); @@ -151,7 +152,11 @@ const loginAsync = async (page: Page, baseUrl: string) => { await page.getByLabel("Username").fill(username); await page.locator("#password").fill(password); await page.locator("css=button[type='submit']").click(); - await page.waitForURL((url) => !url.pathname.includes("/auth/login"), { timeout: 60_000 }); + // Poll location instead of page.waitForURL: sign-in redirects client-side, so + // waitForURL's implicit "load" state may never be reached for that navigation. + await page.waitForFunction(() => !window.location.pathname.includes("/auth/login"), undefined, { + timeout: 60_000, + }); log(`signed in as ${username}`); }; @@ -223,6 +228,9 @@ const main = async () => { "LOG_LEVEL=warn", ]; if (heapMb) runArgs.push("-e", `HOMARR_MAX_OLD_SPACE_SIZE=${heapMb}`); + // Escape hatch for A/B-ing V8 flags against an image whose run.sh does not read + // HOMARR_MAX_OLD_SPACE_SIZE (e.g. measuring the heap cap against an older image). + if (nodeOptions) runArgs.push("-e", `NODE_OPTIONS=${nodeOptions}`); runArgs.push(image); await execFileAsync("docker", runArgs); @@ -295,7 +303,15 @@ const main = async () => { imageSizeBytes: Number( (await execFileAsync("docker", ["image", "inspect", image, "--format", "{{.Size}}"])).stdout.trim(), ), - config: { memoryLimit, heapMb: heapMb || null, navIterations, soakMs, soakIntervalMs, boardName }, + config: { + memoryLimit, + heapMb: heapMb || null, + nodeOptions: nodeOptions || null, + navIterations, + soakMs, + soakIntervalMs, + boardName, + }, // Recorded so an A/B pair can be rejected if the two runs rendered // different amounts of the board (e.g. more widgets errored on one side). board: boardOutcome,