The inline finding was a real bug: main() never called the discovery check, so the deny-by-default claim rested on test:scripts happening to run the same comparison — a different command, which `npm run verify:test-timeouts` does not invoke. `failures` is now seeded from it, and a probe config under clients/ was observed to fail the guard. Three suppressed findings, all correct. The afterEach in oauth-interactive restated the shared hookTimeout and labelled it as a round trip, when it only stops a server and unlinks a file; it now carries no budget. The file-lock constants were named for the opposite of what they bound — 90s covers retries running out against a holder that never yields, 60s covers the stale-lock takeover that succeeds — so they are LOCK_RETRIES_EXHAUSTED_MS and STALE_LOCK_TAKEOVER_MS. The browser-timeouts header said 15 budgets; it is 19. Separately, and more importantly: the asyncUtilTimeout raise is reverted, on measurement. Verifying round 2 turned up intermittent 15s timeouts across unrelated unit files, so three arms of three full unit runs were taken in this worktree at comparable load — unmodified v2/main 3/3 green, this change with 5000 producing 1, 4 and 0 timing-out files, and the same change with that one line reverted 3/3 green. A wait that is MEANT to expire spends its whole budget on the happy path, so a 5x raise is a 5x cost on exactly those tests and saturates the worker pool, starving tests that were never slow. Both web setup files therefore state asyncUtilTimeout at the library's own value rather than raising it, which is what #2323's rule actually asks for: a budget nobody chose is the defect, not a budget that is small. The tests that assert absence by letting a wait expire are the real problem and are #2335. 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>
50 lines
2.3 KiB
JavaScript
50 lines
2.3 KiB
JavaScript
/**
|
|
* 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 19 budgets between them across three
|
|
* independently-chosen scales, with no config anywhere to raise — 17 locator
|
|
* operations plus a `page.goto` and a `waitForLoadState`. 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,
|
|
});
|