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:
parent
336e045210
commit
58e53d5a92
2 changed files with 1276 additions and 394 deletions
49
scripts/lib/browser-timeouts.mjs
Normal file
49
scripts/lib/browser-timeouts.mjs
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
/**
|
||||
* Playwright wall-clock budgets for the web smokes (#2323).
|
||||
*
|
||||
* Before this, `smoke-web-tabs.mjs`, `smoke-web-elicitation.mjs` and
|
||||
* `smoke-web-browser.mjs` carried 15 locator budgets between them across three
|
||||
* independently-chosen scales, with no config anywhere to raise. They are the
|
||||
* same three or four decisions repeated, so they are named once here and the
|
||||
* scripts import them — the same shape `render-smoke.mjs`'s `DEFAULTS` already
|
||||
* has, and for the same reason: a budget nobody can find is a budget nobody
|
||||
* revisits.
|
||||
*
|
||||
* These are generous on purpose and cost a passing run nothing. Playwright
|
||||
* budgets are ceilings on a poll, not sleeps: every one of them returns the
|
||||
* instant its locator condition holds. What they have to absorb is a smoke
|
||||
* running against a cold `dist/` build on a machine already carrying three
|
||||
* other agent sessions' gates.
|
||||
*
|
||||
* ⚠️ Raising one of these does NOT make a smoke wait longer for a server that
|
||||
* is never coming: the launcher-death race in `smoke-web-browser.mjs` and the
|
||||
* `waitForStage` diagnostics in the other two are what report that, and they
|
||||
* report it with a cause rather than a timeout.
|
||||
*/
|
||||
|
||||
export const BROWSER_TIMEOUTS = Object.freeze({
|
||||
/**
|
||||
* Load the page, or act on a control the app is already showing — a click on
|
||||
* a rendered tab or list row, a wait for the first meaningful frame. What it
|
||||
* absorbs is the browser's own scheduling under load, not a server.
|
||||
*/
|
||||
ui: 30_000,
|
||||
/**
|
||||
* Wait for something that only appears after a round trip to an MCP server:
|
||||
* an elicitation modal, a tool result panel. Longer than `ui` because a test
|
||||
* server has to boot, negotiate and answer before the DOM can change.
|
||||
*/
|
||||
roundTrip: 45_000,
|
||||
/**
|
||||
* Wait on a sub-element of something already on screen — a row inside a
|
||||
* populated panel, a section of an open accordion. The enclosing wait has
|
||||
* already paid for the round trip, so this only covers the render.
|
||||
*/
|
||||
nested: 15_000,
|
||||
/**
|
||||
* A wait whose expiry is caught and ignored, so it must stay short: it is
|
||||
* pure added latency on every passing run. `networkidle` is the only one —
|
||||
* the Google Fonts request may never idle on a restricted network.
|
||||
*/
|
||||
bestEffort: 5_000,
|
||||
});
|
||||
27
scripts/lib/browser-timeouts.test.mjs
Normal file
27
scripts/lib/browser-timeouts.test.mjs
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
import { BROWSER_TIMEOUTS } from "./browser-timeouts.mjs";
|
||||
|
||||
test("every budget is a positive finite number of milliseconds", () => {
|
||||
const entries = Object.entries(BROWSER_TIMEOUTS);
|
||||
assert.ok(entries.length > 0);
|
||||
for (const [name, ms] of entries) {
|
||||
assert.equal(typeof ms, "number", `${name} is not a number`);
|
||||
assert.ok(Number.isFinite(ms) && ms > 0, `${name} is not a usable budget`);
|
||||
}
|
||||
});
|
||||
|
||||
test("the object is frozen, so a smoke cannot mutate a shared budget", () => {
|
||||
assert.ok(Object.isFrozen(BROWSER_TIMEOUTS));
|
||||
});
|
||||
|
||||
test("the budgets are ordered by what each one has to absorb", () => {
|
||||
// Not decoration: the ordering IS the semantics documented on each key. A
|
||||
// `nested` wait that outlived its enclosing `roundTrip` one, or a
|
||||
// `bestEffort` wait long enough to be felt on every passing run, would mean
|
||||
// the names had stopped describing the values.
|
||||
assert.ok(BROWSER_TIMEOUTS.bestEffort < BROWSER_TIMEOUTS.nested);
|
||||
assert.ok(BROWSER_TIMEOUTS.nested < BROWSER_TIMEOUTS.ui);
|
||||
assert.ok(BROWSER_TIMEOUTS.ui < BROWSER_TIMEOUTS.roundTrip);
|
||||
});
|
||||
Loading…
Reference in a new issue