diff --git a/packages/vitest/src/runtime/runner/run.ts b/packages/vitest/src/runtime/runner/run.ts index cbaffcae2..0edc13a5e 100644 --- a/packages/vitest/src/runtime/runner/run.ts +++ b/packages/vitest/src/runtime/runner/run.ts @@ -587,6 +587,7 @@ async function runTest(test: Test, runner: VitestRunner): Promise { state: 'run', startTime: unixNow(), retryCount: 0, + attempts: [], } updateTask('test-prepare', test, runner) @@ -597,7 +598,6 @@ async function runTest(test: Test, runner: VitestRunner): Promise { const $ = runner.trace! const repeats = test.repeats ?? 0 - const attempts: TaskResultAttempt[] = [] let hasFailedRepeat = false for (let repeatCount = 0; repeatCount <= repeats; repeatCount++) { // Force widening to TaskState because TypeScript cannot track mutations made by hooks and the test. @@ -609,7 +609,7 @@ async function runTest(test: Test, runner: VitestRunner): Promise { const attemptErrorsStart = test.result.errors?.length ?? 0 const recordAttempt = (state: TaskResultAttempt['state']) => { const errors = test.result!.errors?.slice(attemptErrorsStart) - attempts.push({ + test.result!.attempts!.push({ state, errors: errors?.length ? errors : undefined, duration: now() - attemptStart, @@ -716,7 +716,7 @@ async function runTest(test: Test, runner: VitestRunner): Promise { note: test.result?.note, pending: true, duration: now() - start, - attempts, + attempts: test.result.attempts, } updateTask('test-finished', test, runner) setCurrentTest(undefined) @@ -774,7 +774,6 @@ async function runTest(test: Test, runner: VitestRunner): Promise { if (hasFailedRepeat) { test.result.state = 'fail' } - test.result.attempts = attempts cleanupRunningTest() setCurrentTest(undefined) diff --git a/test/e2e/test/retry.test.ts b/test/e2e/test/retry.test.ts index 88cb9c2a8..054c0adef 100644 --- a/test/e2e/test/retry.test.ts +++ b/test/e2e/test/retry.test.ts @@ -219,6 +219,47 @@ test('expected failures can recover through a retry in every repeat', async () = }) }) +test('attempts grow while the test runs', async () => { + const { stderr, errorTree } = await runInlineTests({ + 'attempts.test.js': ` + import { afterAll, expect, it, onTestFailed } from 'vitest' + + const seen = [] + let runs = 0 + const summarize = (task) => + task.result.attempts.map(a => [a.repeatIndex, a.retryIndex, a.state]) + + it('flaky', { retry: 2, repeats: 1 }, ({ task }) => { + seen.push(['run', ...summarize(task)]) + onTestFailed(() => { + seen.push(['failed', ...summarize(task)]) + }) + expect(++runs % 2).toBe(0) + }) + + afterAll(() => { + expect(seen).toEqual([ + ['run'], + ['failed'], + ['run', [0, 0, 'fail']], + ['run', [0, 0, 'fail'], [0, 1, 'pass']], + ['failed', [0, 0, 'fail'], [0, 1, 'pass']], + ['run', [0, 0, 'fail'], [0, 1, 'pass'], [1, 0, 'fail']], + ]) + }) + `, + }) + + expect(stderr).toBe('') + expect(errorTree()).toMatchInlineSnapshot(` + { + "attempts.test.js": { + "flaky": "passed", + }, + } + `) +}) + test('syntax errors remain failures after successful repeats', async () => { const { errorTree } = await runInlineTests({ 'repeats.test.js': `