-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
feat: add retryDelay, retryCondition, and retryStrategy options for enhanced test retry control (fix #8482) #8812
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
feat: add retryDelay, retryCondition, and retryStrategy options for enhanced test retry control (fix #8482) #8812
Conversation
…nhanced test retry control This adds three new retry options: - retryDelay: milliseconds to wait between retry attempts - retryCondition: string pattern or function to conditionally retry based on error - retryStrategy: 'immediate' (default), 'test-file', or 'deferred' retry timing
packages/runner/src/types/runner.ts
Outdated
| retry: number | ||
| retryDelay?: number | ||
| retryCondition?: string | ((error: Error) => boolean) | ||
| retryStrategy?: 'immediate' | 'test-file' | 'deferred' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we instead have everyting inside retry? It would allow extending it later, like in #7834.
| retry: number | |
| retryDelay?: number | |
| retryCondition?: string | ((error: Error) => boolean) | |
| retryStrategy?: 'immediate' | 'test-file' | 'deferred' | |
| retry: number | { | |
| count?: number | |
| delay?: number | |
| condition?: string | ((error: Error) => boolean) | |
| strategy?: 'immediate' | 'test-file' | 'deferred' | |
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, done!
packages/runner/src/run.ts
Outdated
| // No condition means always retry | ||
| if (!condition) { | ||
| return true | ||
| } | ||
|
|
||
| // No errors means test passed, shouldn't get here but handle it | ||
| if (!errors || errors.length === 0) { | ||
| return false | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think errors check should be done before any check that returns true.
| // No condition means always retry | |
| if (!condition) { | |
| return true | |
| } | |
| // No errors means test passed, shouldn't get here but handle it | |
| if (!errors || errors.length === 0) { | |
| return false | |
| } | |
| // No errors means test passed, shouldn't get here but handle it | |
| if (!errors || errors.length === 0) { | |
| return false | |
| } | |
| // No condition means always retry | |
| if (!condition) { | |
| return true | |
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, that makes sense
packages/runner/src/run.ts
Outdated
| if (task.type === 'test') { | ||
| const test = task as Test | ||
| const retry = test.retry ?? 0 | ||
| const retryCount = test.result?.retryCount ?? 0 | ||
| const testStrategy = test.retryStrategy || 'immediate' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Checking type narrows the typings too:
| if (task.type === 'test') { | |
| const test = task as Test | |
| const retry = test.retry ?? 0 | |
| const retryCount = test.result?.retryCount ?? 0 | |
| const testStrategy = test.retryStrategy || 'immediate' | |
| if (task.type === 'test') { | |
| const retry = task.retry ?? 0 | |
| const retryCount = task.result?.retryCount ?? 0 | |
| const testStrategy = task.retryStrategy || 'immediate' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, thanks!
packages/runner/src/run.ts
Outdated
| else if (task.type === 'suite') { | ||
| const subSuite = task as Suite | ||
| for (const child of subSuite.tasks) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| else if (task.type === 'suite') { | |
| const subSuite = task as Suite | |
| for (const child of subSuite.tasks) { | |
| else if (task.type === 'suite') { | |
| for (const child of task.tasks) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
| /** | ||
| * Condition to determine if a test should be retried based on the error. | ||
| * - If a string, treated as a regular expression to match against error message | ||
| * - If a function, called with the error object; return true to retry | ||
| * | ||
| * @default undefined (retry on all errors) | ||
| */ | ||
| retryCondition?: string | ((error: Error) => boolean) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We cannot pass functions from main thread to test runner threads, as everything needs to be serialized. So the vitest.config.ts cannot support functions in retryCondition.
…bject Refactor retry-related options (retry, retryDelay, retryCondition, retryStrategy) into a single retry configuration object. This provides a cleaner API where retry can be either a number (for simple retry count) or an object with count, delay, condition, and strategy properties. This change improves type safety and makes the retry configuration more intuitive while maintaining backward compatibility with numeric retry values.
✅ Deploy Preview for vitest-dev ready!Built without sensitive environment variables
To edit notification comments on pull requests, go to your Netlify project configuration. |
Summary
This PR implements enhanced retry options for Vitest tests, addressing feature request #8482. The current
retrymechanism only retries a fixed number of times on any kind of error. This enhancement introduces a nested retry configuration object that provides greater flexibility and control over test retry behavior while maintaining full backward compatibility.New Features
The
retryoption now accepts either a number (for backward compatibility) or an object with the following properties:count- Number of retry attemptsnumber0retrynumber optiondelay- Delay between retry attemptsnumber(milliseconds)0condition- Conditional retry based on error typestring | ((error: Error) => boolean)undefined(retry on all errors)trueto retrystrategy- When to retry failed tests'immediate' | 'test-file' | 'deferred''immediate'immediate: Retry immediately after failure (current behavior, default)test-file: Defer retries until after all tests in the current file completedeferred: Defer retries until after all test files completeUsage Examples
Configuration
All options can be configured globally in
vitest.config.ts:Important Notes
Function Conditions in Config
retry.conditioncannot be used invitest.config.tsbecause configurations are serialized when passed to worker threads. If you need conditional retry logic, you have two options:Use a string pattern in your config (works everywhere):
Use a function directly in test files (test-level only):
Implementation Details
Core Changes
shouldRetryTest(): Helper function to evaluate retry conditionscollectDeferredRetryTests(): Collects tests with deferred retry strategiesrunTest(): Handles immediate strategy with delay and conditionrunSuite(): Handles test-file strategy retriesrunFiles(): Handles deferred strategy retriesType Definitions
TaskBaseandTestOptionsto support nested retry structureVitestRunnerConfigto support nested retry structureInlineConfigwith proper documentation and serialization warningsConfiguration Support
Test Coverage
retry.condition(string regex and function)retry.delaytiming validationBackward Compatibility
✅ Fully backward compatible - The existing
retry: numbersyntax continues to work exactly as before:retry: 3is equivalent toretry: { count: 3 }delaydefaults to0(no delay)conditiondefaults toundefined(retry on all errors)strategydefaults to'immediate'(retry immediately)Existing tests with the
retryoption will continue to work exactly as before with no changes required.Test Plan
Related Issues
Closes #8482
Related (future enhancement): #7834
Comparison with Other Test Runners
This implementation provides similar functionality to:
retryTimes(numRetries, { waitBeforeRetry: 100 })→ Ourretry.delayspecFileRetriesDelay→ Ourretry.delayspecFileRetriesDeferred→ Ourretry.strategy: 'deferred'Our implementation goes beyond existing solutions by:
retry.condition