[READ-ONLY] Mirror of https://github.com/vitest-dev/vitest. Next generation testing framework powered by Vite. vitest.dev
test testing-tools vite
12

Configure Feed

Select the types of activity you want to include in your feed.

feat(vitest): add run summary in GitHub Actions Reporter (#9579)

Co-authored-by: jhnance <joshua.h.nance@gmail.com>

authored by

Raul Macarie
jhnance
and committed by
GitHub
(Mar 3, 2026, 9:33 AM +0100) 96bfc834 97dae8a7

+582 -5
+61
docs/guide/reporters.md
··· 576 576 }) 577 577 ``` 578 578 579 + The GitHub Actions reporter automatically generates a [Job Summary](https://github.blog/news-insights/product-news/supercharging-github-actions-with-job-summaries/) with an overview of your test results. The summary includes test file and test case statistics, and highlights flaky tests that required retries. 580 + 581 + <img alt="GitHub Actions Job Summary" img-dark src="/github-actions-job-summary-dark.png"> 582 + <img alt="GitHub Actions Job Summary" img-light src="/github-actions-job-summary-light.png"> 583 + 584 + The job summary is enabled by default and writes to the path specified by `$GITHUB_STEP_SUMMARY`. You can override it by using the `jobSummary.outputPath` option: 585 + 586 + ```ts 587 + export default defineConfig({ 588 + test: { 589 + reporters: [ 590 + ['github-actions', { 591 + jobSummary: { 592 + outputPath: '/home/runner/jobs/summary/step', 593 + }, 594 + }], 595 + ], 596 + }, 597 + }) 598 + ``` 599 + 600 + To disable the job summary: 601 + 602 + ```ts 603 + export default defineConfig({ 604 + test: { 605 + reporters: [ 606 + ['github-actions', { jobSummary: { enabled: false } }], 607 + ], 608 + }, 609 + }) 610 + ``` 611 + 612 + The flaky tests section of the summary includes permalink URLs that link test names directly to the relevant source lines on GitHub. These links are generated automatically using environment variables that GitHub Actions provides (`$GITHUB_REPOSITORY`, `$GITHUB_SHA`, and `$GITHUB_WORKSPACE`), so no configuration is needed in most cases. 613 + 614 + If you need to override these values — for example, when running in a container or a custom environment — you can customize them via the `fileLinks` option: 615 + 616 + - `repository`: the GitHub repository in `owner/repo` format. Defaults to `process.env.GITHUB_REPOSITORY`. 617 + - `commitHash`: the commit SHA to use in permalink URLs. Defaults to `process.env.GITHUB_SHA`. 618 + - `workspacePath`: the absolute path to the root of the repository on disk. Used to compute relative file paths for the permalink URLs. Defaults to `process.env.GITHUB_WORKSPACE`. 619 + 620 + All three values must be available for the links to be generated. 621 + 622 + ```ts 623 + export default defineConfig({ 624 + test: { 625 + reporters: [ 626 + ['github-actions', { 627 + jobSummary: { 628 + fileLinks: { 629 + repository: 'owner/repo', 630 + commitHash: 'abcdefg', 631 + workspacePath: '/home/runner/work/repo/', 632 + }, 633 + }, 634 + }], 635 + ], 636 + }, 637 + }) 638 + ``` 639 + 579 640 ### Blob Reporter 580 641 581 642 Stores test results on the machine so they can be later merged using [`--merge-reports`](/guide/cli#merge-reports) command.
docs/public/github-actions-job-summary-dark.png

This is a binary file and will not be displayed.

docs/public/github-actions-job-summary-light.png

This is a binary file and will not be displayed.

+149
test/cli/test/reporters/github-actions.test.ts
··· 1 + import { randomUUID } from 'node:crypto' 2 + import { access, readFile, rm } from 'node:fs/promises' 3 + import { tmpdir } from 'node:os' 1 4 import { sep } from 'node:path' 2 5 import { runVitest } from '#test-utils' 3 6 import { resolve } from 'pathe' ··· 58 61 " 59 62 `) 60 63 expect(stderr).toBe('') 64 + }) 65 + 66 + describe('summary', () => { 67 + it('writes one when enabled', async ({ onTestFinished }) => { 68 + const outputPath = resolve(tmpdir(), randomUUID()) 69 + 70 + onTestFinished(async () => { 71 + await rm(outputPath).catch(() => { 72 + console.error(`Could not remove ${outputPath}`) 73 + }) 74 + }) 75 + 76 + const workspacePath = resolve(import.meta.dirname, '..', '..', '..', '..') 77 + 78 + await runVitest({ 79 + reporters: new GithubActionsReporter({ 80 + jobSummary: { 81 + outputPath, 82 + fileLinks: { 83 + commitHash: 'aaa', 84 + repository: 'owner/repo', 85 + workspacePath, 86 + }, 87 + }, 88 + }), 89 + root: './fixtures/reporters/github-actions', 90 + }) 91 + 92 + const summary = await readFile(outputPath, 'utf8') 93 + 94 + expect(summary).toMatchInlineSnapshot(` 95 + "## Vitest Test Report 96 + 97 + ### Summary 98 + 99 + - **Test Files**: ❌ **1 failure** · ✅ **1 pass** · 2 total 100 + - **Test Results**: ❌ **1 failure** · ✅ **9 passes** · 🔵 **1 expected failure** · 11 total 101 + - **Other**: 1 skip · 1 todo · 2 total 102 + 103 + ### Flaky Tests 104 + 105 + These tests passed only after one or more retries, indicating potential instability. 106 + 107 + ##### \`flaky/math.spec.ts\` (5 flaky tests) 108 + 109 + - [\`should multiply numbers correctly\`](https://github.com/owner/repo/blob/aaa/test/cli/fixtures/reporters/github-actions/flaky/math.spec.ts) (**passed on retry 5 out of 5**) 110 + - [\`should handle edge cases\`](https://github.com/owner/repo/blob/aaa/test/cli/fixtures/reporters/github-actions/flaky/math.spec.ts) (**passed on retry 4 out of 5**) 111 + - [\`should validate input properly\`](https://github.com/owner/repo/blob/aaa/test/cli/fixtures/reporters/github-actions/flaky/math.spec.ts) (**passed on retry 4 out of 5**) 112 + - [\`should divide numbers correctly\`](https://github.com/owner/repo/blob/aaa/test/cli/fixtures/reporters/github-actions/flaky/math.spec.ts) (passed on retry 2 out of 5) 113 + - [\`should subtract numbers correctly\`](https://github.com/owner/repo/blob/aaa/test/cli/fixtures/reporters/github-actions/flaky/math.spec.ts) (passed on retry 1 out of 5) 114 + 115 + ##### \`flaky/network.spec.ts\` (3 flaky tests) 116 + 117 + - [\`network > should handle network timeouts gracefully\`](https://github.com/owner/repo/blob/aaa/test/cli/fixtures/reporters/github-actions/flaky/network.spec.ts) (**passed on retry 4 out of 4**) 118 + - [\`network > should fetch user data from API\`](https://github.com/owner/repo/blob/aaa/test/cli/fixtures/reporters/github-actions/flaky/network.spec.ts) (passed on retry 2 out of 3) 119 + - [\`network > should retry failed requests\`](https://github.com/owner/repo/blob/aaa/test/cli/fixtures/reporters/github-actions/flaky/network.spec.ts) (passed on retry 1 out of 3) 120 + " 121 + `) 122 + }) 123 + 124 + it.for([{ enabled: false }, { outputPath: undefined }] as const)('does not write one when disabled or without `outputPath`', async (options) => { 125 + const outputPath = resolve(tmpdir(), randomUUID()) 126 + 127 + const workspacePath = resolve(import.meta.dirname, '..', '..', '..', '..') 128 + 129 + await runVitest({ 130 + reporters: new GithubActionsReporter({ 131 + jobSummary: { 132 + outputPath, 133 + ...options, 134 + fileLinks: { 135 + commitHash: 'aaa', 136 + repository: 'owner/repo', 137 + workspacePath, 138 + }, 139 + }, 140 + }), 141 + root: './fixtures/reporters/github-actions', 142 + }) 143 + 144 + const summary = await access(outputPath).then(() => true).catch(() => false) 145 + 146 + expect(summary).toBe(false) 147 + }) 148 + 149 + it.for([ 150 + { commitHash: undefined }, 151 + { repository: undefined }, 152 + { workspacePath: undefined }, 153 + ] as const)('writes one without links when one of `commitHash`, `repository` or `workspacePath` are not provided', async (options, { onTestFinished }) => { 154 + const outputPath = resolve(tmpdir(), randomUUID()) 155 + 156 + onTestFinished(async () => { 157 + await rm(outputPath).catch(() => { 158 + console.error(`Could not remove ${outputPath}`) 159 + }) 160 + }) 161 + 162 + const workspacePath = resolve(import.meta.dirname, '..', '..', '..', '..') 163 + 164 + await runVitest({ 165 + reporters: new GithubActionsReporter({ 166 + jobSummary: { 167 + outputPath, 168 + fileLinks: { 169 + commitHash: 'aaa', 170 + repository: 'owner/repo', 171 + workspacePath, 172 + ...options, 173 + }, 174 + }, 175 + }), 176 + root: './fixtures/reporters/github-actions', 177 + }) 178 + 179 + const summary = await readFile(outputPath, 'utf8') 180 + 181 + expect(summary).toMatchInlineSnapshot(` 182 + "## Vitest Test Report 183 + 184 + ### Summary 185 + 186 + - **Test Files**: ❌ **1 failure** · ✅ **1 pass** · 2 total 187 + - **Test Results**: ❌ **1 failure** · ✅ **9 passes** · 🔵 **1 expected failure** · 11 total 188 + - **Other**: 1 skip · 1 todo · 2 total 189 + 190 + ### Flaky Tests 191 + 192 + These tests passed only after one or more retries, indicating potential instability. 193 + 194 + ##### \`flaky/math.spec.ts\` (5 flaky tests) 195 + 196 + - \`should multiply numbers correctly\` (**passed on retry 5 out of 5**) 197 + - \`should handle edge cases\` (**passed on retry 4 out of 5**) 198 + - \`should validate input properly\` (**passed on retry 4 out of 5**) 199 + - \`should divide numbers correctly\` (passed on retry 2 out of 5) 200 + - \`should subtract numbers correctly\` (passed on retry 1 out of 5) 201 + 202 + ##### \`flaky/network.spec.ts\` (3 flaky tests) 203 + 204 + - \`network > should handle network timeouts gracefully\` (**passed on retry 4 out of 4**) 205 + - \`network > should fetch user data from API\` (passed on retry 2 out of 3) 206 + - \`network > should retry failed requests\` (passed on retry 1 out of 3) 207 + " 208 + `) 209 + }) 61 210 }) 62 211 })
+307 -5
packages/vitest/src/node/reporters/github-actions.ts
··· 4 4 import type { TestProject } from '../project' 5 5 import type { Reporter } from '../types/reporter' 6 6 import type { TestCase, TestModule } from './reported-tasks' 7 + import { writeFileSync } from 'node:fs' 7 8 import { stripVTControlCharacters } from 'node:util' 8 9 import { getFullName, getTasks } from '@vitest/runner/utils' 10 + import { deepMerge } from '@vitest/utils/helpers' 11 + import { relative } from 'pathe' 9 12 import { capturePrintError } from '../printError' 13 + import { noun } from './renderers/utils' 10 14 11 15 export interface GithubActionsReporterOptions { 12 16 onWritePath?: (path: string) => string ··· 14 18 * @default true 15 19 */ 16 20 displayAnnotations?: boolean 21 + /** 22 + * Configuration for the GitHub Actions Job Summary. 23 + * 24 + * When enabled, a markdown summary of test results is written to the path specified by `outputPath`. 25 + */ 26 + jobSummary?: Partial<JobSummaryOptions> 27 + } 28 + 29 + interface JobSummaryOptions { 30 + /** 31 + * Whether to generate the summary. 32 + * 33 + * @default true 34 + */ 35 + enabled: boolean 36 + /** 37 + * File path to write the summary to. 38 + * 39 + * @default process.env.GITHUB_STEP_SUMMARY 40 + */ 41 + outputPath: string | undefined 42 + /** 43 + * Configuration for generating permalink URLs to source files in the GitHub repository. 44 + * 45 + * When all three values are available (either from this config or the defaults picked from environment variables), test names in the summary will link to the relevant source lines. 46 + */ 47 + fileLinks: { 48 + /** 49 + * The GitHub repository in `owner/repo` format. 50 + * 51 + * @default process.env.GITHUB_REPOSITORY 52 + */ 53 + repository?: string | undefined 54 + /** 55 + * The commit SHA to use in permalink URLs. 56 + * 57 + * @default process.env.GITHUB_SHA 58 + */ 59 + commitHash?: string | undefined 60 + /** 61 + * The absolute path to the root of the repository on disk. 62 + * 63 + * This value is used to compute relative file paths for the permalink URLs. 64 + * 65 + * @default process.env.GITHUB_WORKSPACE 66 + */ 67 + workspacePath?: string | undefined 68 + } 69 + } 70 + 71 + type ResolvedOptions = Required<GithubActionsReporterOptions> 72 + 73 + const defaultOptions: ResolvedOptions = { 74 + onWritePath: defaultOnWritePath, 75 + displayAnnotations: true, 76 + jobSummary: { 77 + enabled: true, 78 + outputPath: process.env.GITHUB_STEP_SUMMARY, 79 + fileLinks: { 80 + repository: process.env.GITHUB_REPOSITORY, 81 + commitHash: process.env.GITHUB_SHA, 82 + workspacePath: process.env.GITHUB_WORKSPACE, 83 + }, 84 + }, 17 85 } 18 86 19 87 export class GithubActionsReporter implements Reporter { 20 88 ctx: Vitest = undefined! 21 - options: GithubActionsReporterOptions 89 + options: ResolvedOptions 22 90 23 91 constructor(options: GithubActionsReporterOptions = {}) { 24 - this.options = options 92 + this.options = deepMerge(Object.create(null), defaultOptions, options) 25 93 } 26 94 27 95 onInit(ctx: Vitest): void { ··· 88 156 } 89 157 } 90 158 91 - const onWritePath = this.options.onWritePath ?? defaultOnWritePath 92 - 93 159 // format errors via `printError` 94 160 for (const { project, title, error, file } of projectErrors) { 95 161 const result = capturePrintError(error, this.ctx, { project, task: file }) ··· 100 166 const formatted = formatMessage({ 101 167 command: 'error', 102 168 properties: { 103 - file: onWritePath(stack.file), 169 + file: this.options.onWritePath(stack.file), 104 170 title, 105 171 line: String(stack.line), 106 172 column: String(stack.column), ··· 108 174 message: stripVTControlCharacters(result.output), 109 175 }) 110 176 this.ctx.logger.log(`\n${formatted}`) 177 + } 178 + 179 + if (this.options.jobSummary.enabled === true && this.options.jobSummary.outputPath) { 180 + const summary = renderSummary(collectSummaryData(testModules), this.options.jobSummary.fileLinks) 181 + 182 + try { 183 + writeFileSync( 184 + this.options.jobSummary.outputPath, 185 + summary, 186 + { flag: 'a' }, 187 + ) 188 + } 189 + catch (error) { 190 + this.ctx.logger.warn('Could not write summary to `options.summary.outputPath`', error) 191 + } 111 192 } 112 193 } 113 194 } ··· 164 245 .replace(/\n/g, '%0A') 165 246 .replace(/:/g, '%3A') 166 247 .replace(/,/g, '%2C') 248 + } 249 + 250 + type SummaryTestsStats = Record<'failed' | 'passed' | 'expectedFail' | 'skipped' | 'todo', number> 251 + 252 + interface SummaryData { 253 + fileStats: Pick<SummaryTestsStats, 'failed' | 'passed'> 254 + testsStats: SummaryTestsStats 255 + flakyTests: Array<{ 256 + path: { 257 + relative: string 258 + absolute: string 259 + } 260 + tests: Array<{ 261 + testName: string 262 + line: number | undefined 263 + retries: { 264 + allowed: number 265 + count: number 266 + ratio: number 267 + } 268 + }> 269 + }> 270 + } 271 + 272 + function collectSummaryData(testModules: ReadonlyArray<TestModule>): SummaryData { 273 + const summaryData: SummaryData = { 274 + fileStats: { 275 + failed: 0, 276 + passed: 0, 277 + }, 278 + testsStats: { 279 + failed: 0, 280 + passed: 0, 281 + expectedFail: 0, 282 + skipped: 0, 283 + todo: 0, 284 + }, 285 + flakyTests: [], 286 + } 287 + 288 + for (const module of testModules) { 289 + const flakyTests: SummaryData['flakyTests'][number] = { 290 + path: { relative: module.relativeModuleId, absolute: module.moduleId }, 291 + tests: [], 292 + } 293 + 294 + switch (module.task.result?.state) { 295 + case 'fail': { 296 + summaryData.fileStats.failed += 1 297 + break 298 + } 299 + case 'pass': { 300 + summaryData.fileStats.passed += 1 301 + break 302 + } 303 + } 304 + 305 + for (const test of module.children.allTests()) { 306 + switch (test.task.mode) { 307 + case 'skip': { 308 + summaryData.testsStats.skipped += 1 309 + break 310 + } 311 + case 'todo': { 312 + summaryData.testsStats.todo += 1 313 + break 314 + } 315 + default: { 316 + switch (test.task.result?.state) { 317 + case 'fail': { 318 + summaryData.testsStats.failed += 1 319 + break 320 + } 321 + case 'pass': { 322 + if (test.task.fails) { 323 + summaryData.testsStats.expectedFail += 1 324 + } 325 + else { 326 + summaryData.testsStats.passed += 1 327 + } 328 + 329 + break 330 + } 331 + } 332 + } 333 + } 334 + 335 + const diagnostic = test.diagnostic() 336 + 337 + if (diagnostic?.flaky) { 338 + const retriesAllowed = typeof test.options.retry === 'number' 339 + ? test.options.retry 340 + : (test.options.retry?.count 341 + // falling back to `retryCount` as this is used as the denominator to compute `retryRatio` 342 + ?? diagnostic.retryCount) 343 + const retriesRatio = diagnostic.retryCount / retriesAllowed 344 + 345 + flakyTests.tests.push({ 346 + retries: { 347 + allowed: retriesAllowed, 348 + count: diagnostic.retryCount, 349 + ratio: retriesRatio, 350 + }, 351 + line: test.task.location?.line, 352 + testName: test.task.fullTestName, 353 + }) 354 + } 355 + } 356 + 357 + if (flakyTests.tests.length > 0) { 358 + flakyTests.tests.sort((a, b) => b.retries.ratio - a.retries.ratio) 359 + 360 + summaryData.flakyTests.push(flakyTests) 361 + } 362 + } 363 + 364 + return summaryData 365 + } 366 + 367 + function createGitHubFileLinkCreator(fileLinks?: JobSummaryOptions['fileLinks']): (path: string, line?: number) => string | null { 368 + const repository = fileLinks?.repository 369 + const commitHash = fileLinks?.commitHash 370 + const workspacePath = fileLinks?.workspacePath 371 + 372 + if (repository !== undefined && commitHash !== undefined && workspacePath !== undefined) { 373 + return (path, line) => { 374 + const lineFragment = line !== undefined ? `#L${line}` : '' 375 + 376 + return `https://github.com/${repository}/blob/${commitHash}/${relative(workspacePath, path)}${lineFragment}` 377 + } 378 + } 379 + 380 + return () => null 381 + } 382 + 383 + function mdLink(text: string, url: string | null): string { 384 + return url === null ? text : `[${text}](${url})` 385 + } 386 + 387 + function renderStats({ fileStats, testsStats }: SummaryData): string { 388 + const SEPARATOR_SYMBOL = ' · ' 389 + 390 + const fileInfoTotal = fileStats.failed + fileStats.passed 391 + const primaryInfoTotal = testsStats.failed + testsStats.passed + testsStats.expectedFail 392 + const secondaryInfoTotal = testsStats.skipped + testsStats.todo 393 + 394 + const fileInfo: string[] = [] 395 + const primaryInfo: string[] = [] 396 + const secondaryInfo: string[] = [] 397 + 398 + if (fileStats.failed > 0) { 399 + fileInfo.push(`❌ **${fileStats.failed} ${noun(fileStats.failed, 'failure', 'failures')}**`) 400 + } 401 + 402 + if (fileStats.passed > 0) { 403 + fileInfo.push(`✅ **${fileStats.passed} ${noun(fileStats.passed, 'pass', 'passes')}**`) 404 + } 405 + 406 + fileInfo.push(`${fileInfoTotal} total`) 407 + 408 + if (testsStats.failed > 0) { 409 + primaryInfo.push(`❌ **${testsStats.failed} ${noun(testsStats.failed, 'failure', 'failures')}**`) 410 + } 411 + 412 + if (testsStats.passed > 0) { 413 + primaryInfo.push(`✅ **${testsStats.passed} ${noun(testsStats.passed, 'pass', 'passes')}**`) 414 + } 415 + 416 + if (testsStats.expectedFail > 0) { 417 + primaryInfo.push(`🔵 **${testsStats.expectedFail} expected ${noun(testsStats.expectedFail, 'failure', 'failures')}**`) 418 + } 419 + 420 + primaryInfo.push(`${primaryInfoTotal} total`) 421 + 422 + if (testsStats.skipped > 0) { 423 + secondaryInfo.push(`${testsStats.skipped} ${noun(testsStats.skipped, 'skip', 'skips')}`) 424 + } 425 + 426 + if (testsStats.todo > 0) { 427 + secondaryInfo.push(`${testsStats.todo} ${noun(testsStats.todo, 'todo', 'todos')}`) 428 + } 429 + 430 + let output = `\n### Summary\n\n- **Test Files**: ${fileInfo.join(SEPARATOR_SYMBOL)}\n- **Test Results**: ${primaryInfo.join(SEPARATOR_SYMBOL)}\n` 431 + 432 + if (secondaryInfo.length > 0) { 433 + secondaryInfo.push(`${secondaryInfoTotal} total`) 434 + 435 + output += `- **Other**: ${secondaryInfo.join(SEPARATOR_SYMBOL)}\n` 436 + } 437 + 438 + return output 439 + } 440 + 441 + const SUMMARY_HEADER = '## Vitest Test Report\n' 442 + 443 + function renderSummary(summaryData: SummaryData, fileLinks?: JobSummaryOptions['fileLinks']): string { 444 + const fileLinkCreator = createGitHubFileLinkCreator(fileLinks) 445 + 446 + let summary = `${SUMMARY_HEADER}${renderStats(summaryData)}` 447 + 448 + if (summaryData.flakyTests.length > 0) { 449 + summary += '\n### Flaky Tests\n\nThese tests passed only after one or more retries, indicating potential instability.\n' 450 + 451 + for (const flakyTests of summaryData.flakyTests) { 452 + summary += `\n##### \`${flakyTests.path.relative}\` (${flakyTests.tests.length} flaky tests)\n` 453 + 454 + for (const flakyTest of flakyTests.tests) { 455 + const retriesText = `passed on retry ${flakyTest.retries.count} out of ${flakyTest.retries.allowed}` 456 + 457 + summary += `\n- ${mdLink(`\`${flakyTest.testName}\``, fileLinkCreator(flakyTests.path.absolute, flakyTest.line))} (${flakyTest.retries.ratio >= 0.8 ? `**${retriesText}**` : retriesText})` 458 + } 459 + 460 + summary += '\n' 461 + } 462 + } 463 + 464 + if (!summary.endsWith('\n')) { 465 + summary += '\n' 466 + } 467 + 468 + return summary 167 469 }
+11
packages/vitest/src/node/reporters/renderers/utils.ts
··· 284 284 function capitalize<T extends string>(text: T) { 285 285 return `${text[0].toUpperCase()}${text.slice(1)}` as Capitalize<T> 286 286 } 287 + 288 + /** 289 + * Returns the singular or plural form of a word based on the count. 290 + */ 291 + export function noun(count: number, singular: string, plural: string): string { 292 + if (count === 1) { 293 + return singular 294 + } 295 + 296 + return plural 297 + }
+39
test/cli/fixtures/reporters/github-actions/flaky/math.spec.ts
··· 1 + import { test } from 'vitest' 2 + 3 + test('should add numbers correctly', { retry: 5 }, ({ expect, task }) => { 4 + expect(task.result?.retryCount).toBe(0) 5 + }) 6 + 7 + test('should subtract numbers correctly', { retry: 5 }, ({ expect, task }) => { 8 + expect(task.result?.retryCount).toBe(1) 9 + }) 10 + 11 + test('should multiply numbers correctly', { retry: 5 }, ({ expect, task }) => { 12 + expect(task.result?.retryCount).toBe(5) 13 + }) 14 + 15 + test('should divide numbers correctly', { retry: 5 }, ({ expect, task }) => { 16 + expect(task.result?.retryCount).toBe(2) 17 + }) 18 + 19 + test('should handle edge cases', { retry: 5 }, ({ expect, task }) => { 20 + expect(task.result?.retryCount).toBe(4) 21 + }) 22 + 23 + test('should validate input properly', { retry: 5 }, ({ expect, task }) => { 24 + expect(task.result?.retryCount).toBe(4) 25 + }) 26 + 27 + test.todo('should compute percentages') 28 + 29 + test.skip('should divide by zero', ({ expect }) => { 30 + expect.unreachable() 31 + }) 32 + 33 + test.fails('should work with linear equations', ({ expect }) => { 34 + expect(true).toBe(false) 35 + }) 36 + 37 + test('should compute square root of negative numbers', ({ expect }) => { 38 + expect.unreachable() 39 + })
+15
test/cli/fixtures/reporters/github-actions/flaky/network.spec.ts
··· 1 + import { describe, test } from 'vitest' 2 + 3 + describe('network', () => { 4 + test('should fetch user data from API', { retry: 3 }, ({ expect, task }) => { 5 + expect(task.result?.retryCount).toBe(2) 6 + }) 7 + 8 + test('should handle network timeouts gracefully', { retry: 4 }, ({ expect, task }) => { 9 + expect(task.result?.retryCount).toBe(4) 10 + }) 11 + 12 + test('should retry failed requests', { retry: 3 }, ({ expect, task }) => { 13 + expect(task.result?.retryCount).toBe(1) 14 + }) 15 + })