diff --git a/packages/cli/src/capture/captureCompositionFrame.ts b/packages/cli/src/capture/captureCompositionFrame.ts index 2b0d164094..0b0f44601a 100644 --- a/packages/cli/src/capture/captureCompositionFrame.ts +++ b/packages/cli/src/capture/captureCompositionFrame.ts @@ -2,6 +2,7 @@ import { spawn } from "node:child_process"; import type { Browser, Page } from "puppeteer-core"; import { c } from "../ui/colors.js"; import { resolveCompositionViewportFromHtml } from "../utils/compositionViewport.js"; +import { resolveDiagnosticNavigationTimeoutMs } from "../utils/renderArgs.js"; const SHADER_TRANSITIONS_TIMEOUT_MS = 90_000; const CAPTURE_SETTLE_MS = 1500; @@ -170,7 +171,10 @@ export async function openSettledCompositionPage( await installPageFunctionGuard(page); await page.setViewport(viewport); await options.beforeNavigate?.(page); - await page.goto(url, { waitUntil: "domcontentloaded", timeout: 10000 }); + await page.goto(url, { + waitUntil: "domcontentloaded", + timeout: resolveDiagnosticNavigationTimeoutMs(), + }); const renderReadyTimedOut = !(await waitForCompositionSettle(page, options)); return { browser: chromeBrowser, page, renderReadyTimedOut }; } catch (err) { diff --git a/packages/cli/src/commands/layout.ts b/packages/cli/src/commands/layout.ts index a7025d787e..5879f21290 100644 --- a/packages/cli/src/commands/layout.ts +++ b/packages/cli/src/commands/layout.ts @@ -5,6 +5,7 @@ import { fileURLToPath } from "node:url"; import type { Example } from "./_examples.js"; import { c } from "../ui/colors.js"; import { resolveProject } from "../utils/project.js"; +import { resolveDiagnosticNavigationTimeoutMs } from "../utils/renderArgs.js"; import { normalizeErrorMessage } from "../utils/errorMessage.js"; import { serveStaticProjectHtml } from "../utils/staticProjectServer.js"; import { printDeprecationNotice, withMeta } from "../utils/updateCheck.js"; @@ -180,7 +181,10 @@ async function alignViewportToComposition( }); await page.setViewport(size); - await page.goto(url, { waitUntil: "domcontentloaded", timeout: 10000 }); + await page.goto(url, { + waitUntil: "domcontentloaded", + timeout: resolveDiagnosticNavigationTimeoutMs(), + }); } async function runLayoutAudit( @@ -217,7 +221,10 @@ async function runLayoutAudit( const page = await chromeBrowser.newPage(); await installPageFunctionGuard(page); await page.setViewport({ width: 1920, height: 1080 }); - await page.goto(server.url, { waitUntil: "domcontentloaded", timeout: 10000 }); + await page.goto(server.url, { + waitUntil: "domcontentloaded", + timeout: resolveDiagnosticNavigationTimeoutMs(), + }); await alignViewportToComposition(page, server.url); await page .waitForFunction(() => !!(window as unknown as { __timelines?: unknown }).__timelines, { diff --git a/packages/cli/src/commands/motionShot.ts b/packages/cli/src/commands/motionShot.ts index 0bf1061c6b..a1e93bb634 100644 --- a/packages/cli/src/commands/motionShot.ts +++ b/packages/cli/src/commands/motionShot.ts @@ -11,6 +11,7 @@ // (pure, tested); this file only drives the browser and SAMPLES. import { writeFileSync } from "node:fs"; +import { resolveDiagnosticNavigationTimeoutMs } from "../utils/renderArgs.js"; import { buildOnionSvg, ghostAlphas, @@ -258,7 +259,8 @@ async function openCompositionPage( ], }); const page = await browser.newPage(); - await page.goto(url, { waitUntil: "domcontentloaded", timeout: 10000 }); + const navigationTimeout = resolveDiagnosticNavigationTimeoutMs(); + await page.goto(url, { waitUntil: "domcontentloaded", timeout: navigationTimeout }); const size = await page.evaluate(() => { const root = document.querySelector("[data-composition-id][data-width][data-height]"); const w = root ? parseInt(root.getAttribute("data-width") ?? "", 10) : 0; @@ -269,7 +271,7 @@ async function openCompositionPage( }; }); await page.setViewport(size); - await page.goto(url, { waitUntil: "domcontentloaded", timeout: 10000 }); + await page.goto(url, { waitUntil: "domcontentloaded", timeout: navigationTimeout }); await page .waitForFunction(() => !!(window as unknown as { __timelines?: unknown }).__timelines, { timeout: 10000, diff --git a/packages/cli/src/utils/renderArgs.test.ts b/packages/cli/src/utils/renderArgs.test.ts index 397d8cc51e..9d88e99111 100644 --- a/packages/cli/src/utils/renderArgs.test.ts +++ b/packages/cli/src/utils/renderArgs.test.ts @@ -5,6 +5,7 @@ import { dirname, join, resolve } from "node:path"; import { MAX_PAGE_NAVIGATION_TIMEOUT_SECONDS, parseBrowserTimeoutMsArg, + resolveDiagnosticNavigationTimeoutMs, parseCompositionEntryArg, parseGifLoopArg, resolveDefaultFpsArg, @@ -103,6 +104,21 @@ describe("parseBrowserTimeoutMsArg", () => { }); }); +describe("resolveDiagnosticNavigationTimeoutMs", () => { + it("uses the render navigation timeout env override", () => { + expect( + resolveDiagnosticNavigationTimeoutMs({ PRODUCER_PAGE_NAVIGATION_TIMEOUT_MS: "90000" }), + ).toBe(90_000); + }); + + it("falls back to ten seconds for missing or invalid values", () => { + expect(resolveDiagnosticNavigationTimeoutMs({})).toBe(10_000); + expect( + resolveDiagnosticNavigationTimeoutMs({ PRODUCER_PAGE_NAVIGATION_TIMEOUT_MS: "invalid" }), + ).toBe(10_000); + }); +}); + describe("parseCompositionEntryArg", () => { const PROJECT = "/proj"; const stat = makeStat({ diff --git a/packages/cli/src/utils/renderArgs.ts b/packages/cli/src/utils/renderArgs.ts index f7cd98a5c5..05be2024eb 100644 --- a/packages/cli/src/utils/renderArgs.ts +++ b/packages/cli/src/utils/renderArgs.ts @@ -117,6 +117,14 @@ export function resolveBrowserTimeoutMsArg(raw: string | undefined): number | un return result.value; } +/** Navigation budget shared by snapshot/check/inspect browser diagnostics. */ +export function resolveDiagnosticNavigationTimeoutMs( + env: Record = process.env, +): number { + const parsed = Number(env.PRODUCER_PAGE_NAVIGATION_TIMEOUT_MS); + return Number.isFinite(parsed) && parsed > 0 ? parsed : 10_000; +} + // ── --composition ────────────────────────────────────────────────────── export type CompositionEntryParseError =