Skip to content

feat!: unify timeout config and apply always-on budget clamp - #10667

Draft
hi-ogawa wants to merge 13 commits into
vitest-dev:mainfrom
hi-ogawa:feat/unify-timeout-auto-budget
Draft

feat!: unify timeout config and apply always-on budget clamp#10667
hi-ogawa wants to merge 13 commits into
vitest-dev:mainfrom
hi-ogawa:feat/unify-timeout-auto-budget

Conversation

@hi-ogawa

@hi-ogawa hi-ogawa commented Jun 26, 2026

Copy link
Copy Markdown
Collaborator

Description

See #9751 (comment) for the scope of this PR.

TODO

  • intervals: number[] based backoff everywhere
  • vi.setTimeout
  • always budget clamp
  • polish timeoutDescription
  • unify
    • expect.poll
    • expect.element
    • vi.waitFor/waitUntil
    • locator action (triggerCommand)
    • browser.expect.toMatchScreenshot.timeout
    • pollMatchDomain
  • ALS for timeout context tracking on node
  • docs + migration docs
  • test

Please don't delete this checklist! Before submitting the PR, please make sure you do the following:

  • It's really useful if your PR references an issue where it is discussed ahead of time. If the feature is substantial or introduces breaking changes without a discussion, PR might be closed.
  • Ideally, include a test that fails without this PR but passes with it.
  • Please, don't make changes to pnpm-lock.yaml unless you introduce a new test example.
  • Please check Allow edits by maintainers to make review process faster. Note that this option is not available for repositories that are owned by Github organizations.

Tests

  • Run the tests with pnpm test:ci.

Documentation

  • If you introduce new functionality, document it. You can run documentation with pnpm run docs command.

Changesets

  • Changes in changelog are generated from PR name. Please, make sure that it explains your changes in an understandable manner. Please, prefix changeset messages with feat:, fix:, perf:, docs:, or chore:.

Add a `timeout` namespace (test/hook/teardown/action/poll/wait) with the
old flat options (`testTimeout`/`hookTimeout`/`teardownTimeout`,
`expect.poll.timeout`) kept as deprecated aliases.

Per-operation timeouts (`expect.poll`, `vi.waitFor`/`vi.waitUntil`,
browser actions/locators) are now always clamped to the remaining
test/hook budget, so they fail just before the test timer with a
descriptive, source-mapped error. `action` defaults to `'auto'`
(rides the budget); `poll`/`wait` default to `1000` with `'auto'`
opt-in. Adds `vi.setTimeout()` and `--timeout.*` CLI flags.

Refs vitest-dev#9751

Co-authored-by: OpenCode (claude-opus-4-8) <noreply@opencode.ai>
@netlify

netlify Bot commented Jun 26, 2026

Copy link
Copy Markdown

Deploy Preview for vitest-dev ready!

Built without sensitive environment variables

Name Link
🔨 Latest commit 3b9da99
🔍 Latest deploy log https://app.netlify.com/projects/vitest-dev/deploys/6a449f2c5c164d000810ac54
😎 Deploy Preview https://deploy-preview-10667--vitest-dev.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

Comment thread docs/config/timeout.md Outdated
Comment thread docs/config/timeout.md Outdated
hi-ogawa and others added 7 commits June 29, 2026 13:25
Replace the global `interval` config and per-call `interval` option for
`expect.poll`/`vi.waitFor`/`vi.waitUntil` with a per-call `intervals: number[]`
ascending backoff (default `[0, 25, 50, 100, 250, 500]`). `timeout.poll` and
`timeout.wait` are now scalar `number | 'auto'`. The domain snapshot stability
poll also uses `intervals`.

Co-authored-by: OpenCode (claude-opus-4-8) <noreply@opencode.ai>
Co-authored-by: OpenCode (claude-opus-4-8) <noreply@opencode.ai>
The always-on clamp work renamed the `makeTimeoutError` hint from
`testTimeout`/`hookTimeout` to `timeout.test`/`timeout.hook`; refresh the
stale e2e snapshots accordingly.

Co-authored-by: OpenCode (claude-opus-4-8) <noreply@opencode.ai>
…lumn

The poll/wait timeout errors now include a budget-aware description
(`... timed out in 50ms (test.timeout.poll)`); refresh the stale unit
snapshots. Also bump the `expect.poll` stack-column assertion shifted by
the `interval` -> `intervals` rename.

Co-authored-by: OpenCode (claude-opus-4-8) <noreply@opencode.ai>
Co-authored-by: OpenCode (claude-opus-4-8) <noreply@opencode.ai>
Comment on lines +31 to +35
timeout: {
action: config.timeout?.action ?? 'auto',
poll: config.timeout?.poll ?? 1000,
wait: config.timeout?.wait ?? 1000,
},

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

todo: normalization shouldn't happen here.

// flat `testTimeout`/`hookTimeout`/`teardownTimeout` and `expect.poll.*`.
const timeoutOptions = resolved.timeout ?? {}
if (timeoutOptions.test != null && options.testTimeout != null) {
logger.deprecate('Both `test.timeout.test` and the deprecated `test.testTimeout` are set; `test.timeout.test` takes precedence.')

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

todo: don't need test.timeout.test but just timeout.test is fine.

* Buffer (ms) subtracted from the remaining test/hook budget so that a clamped
* operation fails *before* the test timer, producing a descriptive error.
*/
export const TIMEOUT_BUFFER = 300

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tbd


/**
* Fixed timeout (ms) used for `'auto'` operations when there is no test budget
* to ride (outside a test, or the budget is disabled).

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

budget is never disabled?

* `intervals` curve, repeating the last value once exhausted.
*/
export function intervalForAttempt(intervals: number[], attempt: number): number {
return intervals[Math.min(attempt, intervals.length - 1)] ?? 0

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

silly guard

hi-ogawa and others added 3 commits July 1, 2026 12:52
… paths

Deduplicate the budget-clamp logic so every timeout path resolves once and
surfaces a consistent `timeoutDescription`:

- Add a complete, faithful browser-side duplicate of the runtime budget logic
  (`packages/browser/.../budget.ts`): resolve + describe + `resolveActionTimeout`.
- Rewrite `processTimeoutOptions` to delegate to it (drops the partial local
  clamp and its divergent buffer/fallback constants).
- `expect.element` now resolves the action budget once and passes it to
  `expect.poll` via an internal `__resolved` channel, so the budget is no longer
  clamped twice and `expect.element` is governed by `test.timeout.action`.
- `findElement` surfaces the timeout description in its "cannot find element"
  error (new optional param on `getElementError`, reflected in `context.d.ts`).
- Domain snapshot (`pollMatchDomain`) receives `timeoutDescription` via a new
  `_poll.timeoutDescription` flag and uses it in the failure message.

Co-authored-by: OpenCode (claude-opus-4-8) <noreply@opencode.ai>
Record the follow-up plans as inline TODOs:
- CommandsManager.triggerCommand catch: classify provider timeout errors by
  name and prepend the unified `timeoutDescription` (passed in explicitly).
- toMatchScreenshot: fold the stability timeout into `timeout.action`,
  resolve+budget-clamp client-side, and surface `timeoutDescription`; remove
  the fixed `5000` default and `browser.expect.toMatchScreenshot.timeout`.

Co-authored-by: OpenCode (claude-opus-4-8) <noreply@opencode.ai>
…ckoff

The toMatchScreenshot stability loop runs at an effective interval of 0
(spaced only by capture latency), unlike the other retry mechanisms. Record
that this is intentional to keep scope small, rather than a gap to fix.

Co-authored-by: OpenCode (claude-opus-4-8) <noreply@opencode.ai>

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

todo: should somehow unify instead of duplicating

Comment thread docs/config/timeout.md

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

todo: probably element/poll/wait side doc should also get updated.

….action

Fold the `toMatchScreenshot` stability timeout into the unified budget model:
- Resolve it on the client via `resolveActionTimeout` (timeout.action) so it
  rides/clamps to the remaining test budget and carries `timeoutDescription`.
- Send the resolved timeout + description to the node command; drop the fixed
  `5000` default and stop reading `browser.expect.toMatchScreenshot.timeout`
  (removed from the config type; per-call `toMatchScreenshot({ timeout })` stays).
- Surface `timeoutDescription` in the `unstable-screenshot` error, matching
  poll/wait/findElement/domain-snapshot.

BREAKING: the default stability window changes from a fixed 5000ms to `'auto'`
(rides the test budget). `{ timeout: 0 }` still disables it.

Co-authored-by: OpenCode (claude-opus-4-8) <noreply@opencode.ai>
Comment thread docs/config/timeout.md

- **Type:** `object`

Unified timeout configuration. This namespace groups all timeouts in one place and supersedes the standalone [`testTimeout`](/config/testtimeout), [`hookTimeout`](/config/hooktimeout), [`teardownTimeout`](/config/teardowntimeout) and `expect.poll.timeout` options (which remain as deprecated aliases).

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tbd: should we deprecate or just remove?

* `expect.poll` does not clamp the budget a second time.
*/
interface PollOptionsInternal {
__resolved?: BudgetedTimeout & { description: string }

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

todo: really only this way?

…ription

When a budget-clamped provider action times out (Playwright locator ops and
page.screenshot), patch the provider's bare "Timeout <n>ms exceeded" sentence
with the resolved `timeout.action` description while preserving the provider's
API prefix and Call log. This surfaces how long, why (budget-capped), and which
config governs, consistent with poll/wait/findElement/domain-snapshot.

- Classify by err.name ('TimeoutError'), which survives the RPC hop.
- Thread the resolved description explicitly from resolveActionTimeout through
  the base Locator action methods -> triggerCommand -> CommandsManager, plus
  page.screenshot; provider overrides pass it via withActionTimeout.
- Fall back to prefixing when the provider phrasing isn't recognized.

Co-authored-by: OpenCode (claude-opus-4-8) <noreply@opencode.ai>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Unify and simplify timeout configuration

2 participants