From 084481808568933dd80f3c1484b23eb1588ec9f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20=C3=81ngel?= Date: Wed, 29 Jul 2026 00:38:13 +0000 Subject: [PATCH 1/2] fix(cli): honor check navigation timeout --- docs/packages/cli.mdx | 2 +- .../src/capture/captureCompositionFrame.ts | 5 ++++- packages/cli/src/commands/check.ts | 3 ++- packages/cli/src/utils/checkBrowser.test.ts | 22 +++++++++++++++++++ packages/cli/src/utils/checkBrowser.ts | 2 ++ packages/cli/src/utils/renderArgs.test.ts | 11 ++++++++++ packages/cli/src/utils/renderArgs.ts | 12 ++++++++-- skills-manifest.json | 2 +- .../references/lint-validate-inspect.md | 2 +- 9 files changed, 54 insertions(+), 7 deletions(-) diff --git a/docs/packages/cli.mdx b/docs/packages/cli.mdx index ced38a57fc..efd4b331a2 100644 --- a/docs/packages/cli.mdx +++ b/docs/packages/cli.mdx @@ -542,7 +542,7 @@ Word-level transcripts (whisper output) are grouped into readable caption cues o | `--snapshots` | Write overview frames (annotated with labeled finding boxes when there are errors) plus `finding-NN-.png` crops | | `--samples` / `--at` / `--at-transitions` | Control the seek grid (default 9 samples; `--at-transitions` adds tween boundaries) | | `--tolerance` | Allowed overflow in px before reporting (default 2) | - | `--timeout` | Initial settle budget in ms (default 3000) | + | `--timeout` | Initial render-ready budget in ms; also raises the page-navigation budget above its 10s floor (default 3000) | | `--no-contrast` | Skip the WCAG audit while iterating | | `--strict` | Exit non-zero on warnings too (default: only errors) | | `--caption-zone ""` | Opt-in band gate: flags content whose center sits inside the fractional band (optional `severity`, `seek`) | diff --git a/packages/cli/src/capture/captureCompositionFrame.ts b/packages/cli/src/capture/captureCompositionFrame.ts index 167ebd892e..fcc6dd2d2b 100644 --- a/packages/cli/src/capture/captureCompositionFrame.ts +++ b/packages/cli/src/capture/captureCompositionFrame.ts @@ -61,6 +61,9 @@ export interface SettledCompositionPage { } export interface OpenSettledCompositionPageOptions { + // Separate from the post-navigation render-ready budget. Diagnostic callers + // without their own navigation knob keep the historical 10-second minimum. + navigationTimeoutMs?: number; renderReadyTimeoutMs: number; renderReadyWarningSuffix: string; // Screenshot paths take the engine's software-GPU default; validate/check @@ -182,7 +185,7 @@ export async function openSettledCompositionPage( await options.beforeNavigate?.(page); await page.goto(url, { waitUntil: "domcontentloaded", - timeout: resolveDiagnosticNavigationTimeoutMs(), + timeout: resolveDiagnosticNavigationTimeoutMs(process.env, options.navigationTimeoutMs), }); const renderReadyTimedOut = !(await waitForCompositionSettle(page, options)); return { browser: chromeBrowser, page, renderReadyTimedOut }; diff --git a/packages/cli/src/commands/check.ts b/packages/cli/src/commands/check.ts index dd5a5085ee..d23350e209 100644 --- a/packages/cli/src/commands/check.ts +++ b/packages/cli/src/commands/check.ts @@ -86,7 +86,8 @@ export function createCheckCommand( }, timeout: { type: "string", - description: "Ms to wait for scripts and media to settle initially (default: 3000)", + description: + "Initial render-ready timeout in ms; also sets the navigation minimum (10s floor, default: 3000)", default: "3000", }, contrast: { diff --git a/packages/cli/src/utils/checkBrowser.test.ts b/packages/cli/src/utils/checkBrowser.test.ts index 3af4cad8a3..ad94e169f2 100644 --- a/packages/cli/src/utils/checkBrowser.test.ts +++ b/packages/cli/src/utils/checkBrowser.test.ts @@ -155,6 +155,28 @@ it("carries raw browser geometry through the page driver and pipeline", async () expect(mocks.serverClose).toHaveBeenCalledOnce(); }); +it("uses check --timeout for both navigation and render-ready settling", async () => { + mountCanvasFixture(); + const page = fakePage(); + installSessionMock(page); + + await runBrowserCheck( + PROJECT, + { ...DEFAULT_CHECK_OPTIONS, samples: 1, contrast: false, timeout: 30_000 }, + { kind: "none" }, + runAuditGrid, + ); + + expect(openSettledCompositionPage).toHaveBeenCalledWith( + "", + "http://127.0.0.1:3000", + expect.objectContaining({ + navigationTimeoutMs: 30_000, + renderReadyTimeoutMs: 30_000, + }), + ); +}); + it("round-trips the browser script's raw contrast candidates back into finish", async () => { // The U2 regression class: Node parses prepare's candidates for reporting, // but must hand the UNTOUCHED objects back to __contrastAuditFinish — the diff --git a/packages/cli/src/utils/checkBrowser.ts b/packages/cli/src/utils/checkBrowser.ts index eb105e164c..3af73878b1 100644 --- a/packages/cli/src/utils/checkBrowser.ts +++ b/packages/cli/src/utils/checkBrowser.ts @@ -165,6 +165,7 @@ export async function runBrowserCheck( try { const launchSettleStart = Date.now(); const session = await openSettledCompositionPage(html, server.url, { + navigationTimeoutMs: options.timeout, renderReadyTimeoutMs: options.timeout, renderReadyWarningSuffix: "checking the current page state", browserGpuMode: resolveCliChromeGpuMode(), @@ -225,6 +226,7 @@ export async function captureFindingCrops( const written: string[] = []; try { const session = await openSettledCompositionPage(html, server.url, { + navigationTimeoutMs: options.timeout, renderReadyTimeoutMs: options.timeout, renderReadyWarningSuffix: "capturing finding crops", browserGpuMode: resolveCliChromeGpuMode(), diff --git a/packages/cli/src/utils/renderArgs.test.ts b/packages/cli/src/utils/renderArgs.test.ts index 864e5f3e4e..c3ace10f44 100644 --- a/packages/cli/src/utils/renderArgs.test.ts +++ b/packages/cli/src/utils/renderArgs.test.ts @@ -118,6 +118,17 @@ describe("resolveDiagnosticNavigationTimeoutMs", () => { resolveDiagnosticNavigationTimeoutMs({ PRODUCER_PAGE_NAVIGATION_TIMEOUT_MS: "invalid" }), ).toBe(10_000); }); + + it("raises the diagnostic navigation budget to a larger caller-provided minimum", () => { + expect(resolveDiagnosticNavigationTimeoutMs({}, 30_000)).toBe(30_000); + expect(resolveDiagnosticNavigationTimeoutMs({}, 3_000)).toBe(10_000); + expect( + resolveDiagnosticNavigationTimeoutMs( + { PRODUCER_PAGE_NAVIGATION_TIMEOUT_MS: "90000" }, + 30_000, + ), + ).toBe(90_000); + }); }); describe("parseCompositionEntryArg", () => { diff --git a/packages/cli/src/utils/renderArgs.ts b/packages/cli/src/utils/renderArgs.ts index 10c98fe00b..618426387e 100644 --- a/packages/cli/src/utils/renderArgs.ts +++ b/packages/cli/src/utils/renderArgs.ts @@ -118,12 +118,20 @@ export function resolveBrowserTimeoutMsArg(raw: string | undefined): number | un return result.value; } -/** Navigation budget shared by snapshot/check/inspect browser diagnostics. */ +/** + * Navigation budget shared by snapshot/check/inspect browser diagnostics. + * + * The environment variable remains the historical global override. Callers + * with their own timeout knob can supply a minimum without shortening that + * override or the existing 10-second default. + */ export function resolveDiagnosticNavigationTimeoutMs( env: Record = process.env, + minimumTimeoutMs = 0, ): number { const parsed = Number(env.PRODUCER_PAGE_NAVIGATION_TIMEOUT_MS); - return Number.isFinite(parsed) && parsed > 0 ? parsed : 10_000; + const configured = Number.isFinite(parsed) && parsed > 0 ? parsed : 10_000; + return Math.max(configured, minimumTimeoutMs); } // ── --composition ────────────────────────────────────────────────────── diff --git a/skills-manifest.json b/skills-manifest.json index 27534f19e8..6f1585a92c 100644 --- a/skills-manifest.json +++ b/skills-manifest.json @@ -26,7 +26,7 @@ "files": 121 }, "hyperframes-cli": { - "hash": "966972db5ab8f932", + "hash": "42b621ca580541b6", "files": 11 }, "hyperframes-core": { diff --git a/skills/hyperframes-cli/references/lint-validate-inspect.md b/skills/hyperframes-cli/references/lint-validate-inspect.md index 5d3feecc16..1420306af5 100644 --- a/skills/hyperframes-cli/references/lint-validate-inspect.md +++ b/skills/hyperframes-cli/references/lint-validate-inspect.md @@ -36,7 +36,7 @@ npx hyperframes check --samples 15 # denser timeline sweep (default 9) npx hyperframes check --at 1.5,4,7.25 # explicit hero-frame timestamps npx hyperframes check --at-transitions # also sample every tween start/end boundary npx hyperframes check --tolerance 4 # allowed overflow px before reporting (default 2) -npx hyperframes check --timeout 5000 # ms for the initial settle (default 3000) +npx hyperframes check --timeout 30000 # initial render-ready + navigation minimum in ms (defaults: 3000 / 10000) npx hyperframes check --no-contrast # skip the WCAG audit while iterating npx hyperframes check --strict # exit non-zero on warnings too (default: only errors) ``` From 9e4ce442bf0b33145136c9ef6de32d29afb42aa2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20=C3=81ngel?= Date: Wed, 29 Jul 2026 18:27:44 +0000 Subject: [PATCH 2/2] test(cli): clarify diagnostic timeout precedence --- packages/cli/src/utils/renderArgs.test.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/cli/src/utils/renderArgs.test.ts b/packages/cli/src/utils/renderArgs.test.ts index c3ace10f44..5088192c70 100644 --- a/packages/cli/src/utils/renderArgs.test.ts +++ b/packages/cli/src/utils/renderArgs.test.ts @@ -121,7 +121,13 @@ describe("resolveDiagnosticNavigationTimeoutMs", () => { it("raises the diagnostic navigation budget to a larger caller-provided minimum", () => { expect(resolveDiagnosticNavigationTimeoutMs({}, 30_000)).toBe(30_000); + }); + + it("does not let a caller-provided minimum shorten the default budget", () => { expect(resolveDiagnosticNavigationTimeoutMs({}, 3_000)).toBe(10_000); + }); + + it("does not let a caller-provided minimum shorten the environment override", () => { expect( resolveDiagnosticNavigationTimeoutMs( { PRODUCER_PAGE_NAVIGATION_TIMEOUT_MS: "90000" },