feat!: unify timeout config and apply always-on budget clamp - #10667
Draft
hi-ogawa wants to merge 13 commits into
Draft
feat!: unify timeout config and apply always-on budget clamp#10667hi-ogawa wants to merge 13 commits into
hi-ogawa wants to merge 13 commits into
Conversation
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>
✅ Deploy Preview for vitest-dev ready!Built without sensitive environment variables
To edit notification comments on pull requests, go to your Netlify project configuration. |
sheremet-va
reviewed
Jun 26, 2026
hi-ogawa
commented
Jun 29, 2026
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>
…vitest into feat/unify-timeout-auto-budget
hi-ogawa
commented
Jul 1, 2026
Comment on lines
+31
to
+35
| timeout: { | ||
| action: config.timeout?.action ?? 'auto', | ||
| poll: config.timeout?.poll ?? 1000, | ||
| wait: config.timeout?.wait ?? 1000, | ||
| }, |
Collaborator
Author
There was a problem hiding this comment.
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.') |
Collaborator
Author
There was a problem hiding this comment.
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 |
|
|
||
| /** | ||
| * Fixed timeout (ms) used for `'auto'` operations when there is no test budget | ||
| * to ride (outside a test, or the budget is disabled). |
Collaborator
Author
There was a problem hiding this comment.
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 |
… 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>
hi-ogawa
commented
Jul 1, 2026
Collaborator
Author
There was a problem hiding this comment.
todo: should somehow unify instead of duplicating
Collaborator
Author
There was a problem hiding this comment.
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>
hi-ogawa
commented
Jul 1, 2026
|
|
||
| - **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). |
Collaborator
Author
There was a problem hiding this comment.
tbd: should we deprecate or just remove?
hi-ogawa
commented
Jul 1, 2026
| * `expect.poll` does not clamp the budget a second time. | ||
| */ | ||
| interface PollOptionsInternal { | ||
| __resolved?: BudgetedTimeout & { description: string } |
Collaborator
Author
There was a problem hiding this comment.
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
See #9751 (comment) for the scope of this PR.
TODO
intervals: number[]based backoff everywherevi.setTimeoutPlease don't delete this checklist! Before submitting the PR, please make sure you do the following:
pnpm-lock.yamlunless you introduce a new test example.Tests
pnpm test:ci.Documentation
pnpm run docscommand.Changesets
feat:,fix:,perf:,docs:, orchore:.