fix: state every test-gate timeout instead of inheriting a library default

Three of the six Vitest projects ran on Vitest's own testTimeout: 5000 and
five on its hookTimeout/teardownTimeout: 10000, and all 788 Testing Library
waits in the web unit project ran on asyncUtilTimeout: 1000. None of those
numbers was chosen here, and all of them were sized for an idle machine rather
than for three or four concurrent worktree sessions each running a full
local:gate on eight cores. #2292, #2278, #2250, #1942 and #1742 were each that
class, diagnosed one site at a time.

This is the complement of #1596, not a retreat from it: every fix that issue
produced stands, and no race is hidden here. What changes is that a correct,
deterministic test is no longer cut off by a budget nobody picked.

- vitest.shared.mts exports frozen TIMEOUTS / INTEGRATION_TIMEOUTS; all six
  projects spread one of them, clients/launcher importing the shared module for
  the first time to do so.
- Both web projects configure Testing Library's asyncUtilTimeout. The storybook
  one needs its own setup file, deliberately outside .storybook/ and free of
  setProjectAnnotations so the automatic preview-annotation provisioning
  (#1898) is untouched.
- 28 per-suite restatements of the integration project's own budget are gone,
  so raising the shared value now moves every site under it. The handful that
  genuinely differ — cross-process lock contention, a full OAuth round trip —
  keep their budget as a named constant that says why.
- AppRenderer's waitFor no longer asks for exactly its enclosing budget, which
  it could never win (#2292's shape); cli's e2e child timer drops below the
  project budget so the bound carrying the diagnostic is the one that fires.
- The transition auto-settle's slack term is named RAF_SLACK_MS and raised;
  the term derived from HEADER_ANIM_MS is untouched. The two answer to
  different things and must not be scaled as one number.
- The 19 Playwright budgets scattered across three web smokes become four
  named constants in scripts/lib/browser-timeouts.mjs.

scripts/verify-test-timeouts.mjs resolves each project through Vitest itself
and asserts the budgets, the asyncUtilTimeout sites, and that retry stays
unset. It was observed red against the pre-change config: unit/tui/launcher at
testTimeout 5000, five projects at hookTimeout 10000, both asyncUtilTimeout
sites unmet.

Closes #2323

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PsmUAZ6aLUAFqenjJKSSRp
Signed-off-by: cliffhall <cliff@futurescale.com>
This commit is contained in:
cliffhall 2026-09-12 08:32:34 -04:00
commit 58e53d5a92

View file

@ -91,6 +91,7 @@ import {
connectViaDeepLink,
} from "./lib/deep-link-connect.mjs";
import { startMcpAppServer } from "./lib/mcp-app-flow.mjs";
import { BROWSER_TIMEOUTS } from "./lib/browser-timeouts.mjs";
const repoRoot = resolve(import.meta.dirname, "..");
@ -173,7 +174,7 @@ async function openTab(page, name) {
await page
.locator(`label[for$="-${name}"]`)
.first()
.click({ timeout: 30_000 });
.click({ timeout: BROWSER_TIMEOUTS.ui });
}
/**
@ -184,7 +185,7 @@ async function waitForStage(page, { selector, screen, attrs, what }) {
try {
await page
.locator(selector)
.waitFor({ state: "attached", timeout: 45_000 });
.waitFor({ state: "attached", timeout: BROWSER_TIMEOUTS.roundTrip });
} catch {
const root = page.locator(screen);
const present = (await root.count()) > 0;
@ -244,10 +245,10 @@ try {
});
await page
.getByRole("button", { name: TOOL, exact: true })
.click({ timeout: 30_000 });
.click({ timeout: BROWSER_TIMEOUTS.ui });
await page
.getByRole("button", { name: /execute tool/i })
.click({ timeout: 30_000 });
.click({ timeout: BROWSER_TIMEOUTS.ui });
await waitForStage(page, {
selector: '[data-testid="tools-screen"][data-call-status="ok"]',
screen: '[data-testid="tools-screen"]',
@ -259,7 +260,7 @@ try {
// the data.
await page
.locator('[data-testid="structured-output"]')
.waitFor({ state: "attached", timeout: 15_000 });
.waitFor({ state: "attached", timeout: BROWSER_TIMEOUTS.nested });
// ── Resources ──────────────────────────────────────────────────────────
await openTab(page, "Resources");
@ -280,12 +281,12 @@ try {
// is closed rather than clicking blind, which would toggle it shut.
const uris = page.getByRole("button", { name: /^URIs/ });
if ((await uris.getAttribute("aria-expanded")) !== "true") {
await uris.click({ timeout: 15_000 });
await uris.click({ timeout: BROWSER_TIMEOUTS.nested });
}
// Selecting a resource reads it — there is no separate Read button here.
await page
.getByRole("button", { name: RESOURCE, exact: true })
.click({ timeout: 30_000 });
.click({ timeout: BROWSER_TIMEOUTS.ui });
await waitForStage(page, {
// Both the RPC status AND the rendered preview. The status flips the
// moment `resources/read` resolves, so stopping there would stay green
@ -311,7 +312,7 @@ try {
// submit step follows.
await page
.getByRole("button", { name: new RegExp(`^${PROMPT}`) })
.click({ timeout: 30_000 });
.click({ timeout: BROWSER_TIMEOUTS.ui });
await waitForStage(page, {
// As with the resource preview: the messages panel must have rendered,
// not merely `prompts/get` returned (Copilot, #2148).