inspector/scripts/lib/browser-timeouts.mjs
cliffhall 3f7ba20ae6 chore: state the smoke-helper and CI wall-clock budgets (#2333)
The five rows #2323 left as "assess", each either stated with its
reasoning and measurement, or shared with a constant that already has one.

- F4/F5: `driveAppFlow` and `connectViaDeepLink` default to
  `BROWSER_TIMEOUTS.ui` / `.roundTrip` instead of restating 30_000 /
  45_000 by hand; values unchanged, now one place to raise. A test in each
  helper pins the defaults.
- F1: `announced-child.mjs` states `DEFAULTS` ({ timeoutMs: 30_000,
  pollMs: 250 }) with the measurement — boot-to-announce of the composable
  test server is 135ms median at load average 15, and a passing start pays
  one poll interval, not the budget.
- F7: `pty.mjs` names `SCRIPT_PROBE_TIMEOUT_MS` (5s; the probe spawns in
  1.3ms median, 3.6ms max under load) and stops folding ETIMEDOUT into
  "no script(1)": a starved probe now fails `smoke:tui` loudly instead of
  turning it into an exit-0 skip.
- G1: every job in `.github/workflows/main.yml` declares
  `timeout-minutes` as a hung-job guard — roughly twice the slowest run
  observed over the last 40 push runs (and the last three releases for the
  publish jobs), rounded up to the next five minutes, with the range stated
  per job.

AGENTS.md's timeout section now points at where these live instead of at
this issue.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PsmUAZ6aLUAFqenjJKSSRp
Signed-off-by: cliffhall <cliff@futurescale.com>
2026-09-13 01:50:33 -04:00

59 lines
2.9 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. The shared flow helpers (`deep-link-connect.mjs`,
* `mcp-app-flow.mjs`) default their budgets to these too (#2333); they used to
* carry `ui`/`roundTrip` as literals of their own.
*
* Three of the four are ceilings on a poll rather than sleeps — `ui`,
* `roundTrip` and `nested` each return the instant their locator condition
* holds, so a passing smoke pays only the time the app actually took. What they
* absorb is a smoke running against a cold `dist/` build on a machine already
* carrying other agent sessions' gates.
*
* ⚠️ `bestEffort` is the exception and is not free: its expiry is caught and
* ignored, so a smoke that never reaches network idle spends the full budget on
* an otherwise **passing** run. That is why it is the smallest of the four, and
* why it should stay that way.
*
* ⚠️ Raising any of these does delay the report when the thing waited on never
* arrives — a larger budget is a longer wait before the failure. What the
* launcher-death race in `smoke-web-browser.mjs` and the `waitForStage`
* diagnostics in the other two buy is a better *eventual* message, naming a
* cause instead of a locator; they do not make the wait shorter. Weigh both
* when changing a value here.
*/
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,
});