[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.

fix: annotation location always points to the test file (#8315)

authored by

Vladimir and committed by
GitHub
(Jul 15, 2025, 4:10 PM +0200) 88071a8f 50da421c

+85 -53
+10 -9
packages/runner/src/context.ts
··· 12 12 WriteableTestContext, 13 13 } from './types/tasks' 14 14 import { getSafeTimers } from '@vitest/utils' 15 - import { parseSingleStack } from '@vitest/utils/source-map' 16 15 import { PendingError } from './errors' 17 16 import { finishSendTasksUpdate } from './run' 18 17 import { getRunner } from './suite' 18 + import { findTestFileStackTrace } from './utils' 19 19 20 20 const now = Date.now 21 21 ··· 200 200 throw new Error(`Cannot annotate tests outside of the test run. The test "${test.name}" finished running with the "${test.result.state}" state already.`) 201 201 } 202 202 203 + const stack = findTestFileStackTrace( 204 + test.file.filepath, 205 + new Error('STACK_TRACE').stack!, 206 + ) 207 + 203 208 let location: undefined | TestAnnotationLocation 204 209 205 - const stack = new Error('STACK_TRACE').stack! 206 - const index = stack.includes('STACK_TRACE') ? 2 : 1 207 - const stackLine = stack.split('\n')[index] 208 - const parsed = parseSingleStack(stackLine) 209 - if (parsed) { 210 + if (stack) { 210 211 location = { 211 - file: parsed.file, 212 - line: parsed.line, 213 - column: parsed.column, 212 + file: stack.file, 213 + line: stack.line, 214 + column: stack.column, 214 215 } 215 216 } 216 217
+11 -20
packages/runner/src/suite.ts
··· 25 25 objectAttr, 26 26 toArray, 27 27 } from '@vitest/utils' 28 - import { parseSingleStack } from '@vitest/utils/source-map' 29 28 import { 30 29 abortIfTimeout, 31 30 collectorContext, ··· 37 36 import { mergeContextFixtures, mergeScopedFixtures, withFixtures } from './fixture' 38 37 import { getHooks, setFn, setHooks, setTestFixture } from './map' 39 38 import { getCurrentTest } from './test-state' 39 + import { findTestFileStackTrace } from './utils' 40 40 import { createChainable } from './utils/chain' 41 41 42 42 /** ··· 366 366 367 367 if (runner.config.includeTaskLocation) { 368 368 const error = stackTraceError.stack! 369 - const stack = findTestFileStackTrace(error) 369 + const stack = findTestFileStackTrace(currentTestFilepath, error) 370 370 if (stack) { 371 - task.location = stack 371 + task.location = { 372 + line: stack.line, 373 + column: stack.column, 374 + } 372 375 } 373 376 } 374 377 ··· 460 463 Error.stackTraceLimit = 15 461 464 const error = new Error('stacktrace').stack! 462 465 Error.stackTraceLimit = limit 463 - const stack = findTestFileStackTrace(error) 466 + const stack = findTestFileStackTrace(currentTestFilepath, error) 464 467 if (stack) { 465 - suite.location = stack 468 + suite.location = { 469 + line: stack.line, 470 + column: stack.column, 471 + } 466 472 } 467 473 } 468 474 ··· 886 892 res.push(oneCase) 887 893 } 888 894 return res 889 - } 890 - 891 - function findTestFileStackTrace(error: string) { 892 - const testFilePath = getTestFilepath() 893 - // first line is the error message 894 - const lines = error.split('\n').slice(1) 895 - for (const line of lines) { 896 - const stack = parseSingleStack(line) 897 - if (stack && stack.file === testFilePath) { 898 - return { 899 - line: stack.line, 900 - column: stack.column, 901 - } 902 - } 903 - } 904 895 }
+50 -24
test/cli/test/annotations.test.ts
··· 2 2 import { describe, expect, test } from 'vitest' 3 3 import { runInlineTests } from '../../test-utils' 4 4 5 + const test3Content = /* ts */` 6 + export async function externalAnnotate(annotate) { 7 + await annotate('external') 8 + } 9 + ` 10 + 5 11 const annotationTest = /* ts */` 6 12 import { test, describe } from 'vitest' 13 + import { externalAnnotate } from './test-3.js' 7 14 8 15 test('simple', async ({ annotate }) => { 9 16 await annotate('1') 10 17 await annotate('2', 'warning') 11 18 await annotate('3', { path: './test-3.js' }) 12 19 await annotate('4', 'warning', { path: './test-4.js' }) 20 + await externalAnnotate(annotate) 13 21 }) 14 22 15 23 describe('suite', () => { ··· 42 50 const { stderr } = await runInlineTests( 43 51 { 44 52 'basic.test.ts': annotationTest, 45 - 'test-3.js': '', 53 + 'test-3.js': test3Content, 46 54 'test-4.js': '', 47 55 }, 48 56 { ··· 92 100 "[annotate] simple 2 warning undefined", 93 101 "[annotate] simple 3 notice <root>/.vitest-attachments/3-<hash>.js", 94 102 "[annotate] simple 4 warning <root>/.vitest-attachments/4-<hash>.js", 103 + "[annotate] simple external notice undefined", 95 104 "[result] simple", 96 105 "[ready] second", 97 106 "[annotate] second 5 notice undefined", ··· 107 116 "location": { 108 117 "column": 11, 109 118 "file": "<root>/basic.test.ts", 110 - "line": 13, 119 + "line": 15, 111 120 }, 112 121 "message": "5", 113 122 "type": "notice", ··· 119 128 "location": { 120 129 "column": 11, 121 130 "file": "<root>/basic.test.ts", 122 - "line": 14, 131 + "line": 16, 123 132 }, 124 133 "message": "6", 125 134 "type": "notice", ··· 130 139 "location": { 131 140 "column": 9, 132 141 "file": "<root>/basic.test.ts", 133 - "line": 5, 142 + "line": 6, 134 143 }, 135 144 "message": "1", 136 145 "type": "notice", ··· 139 148 "location": { 140 149 "column": 9, 141 150 "file": "<root>/basic.test.ts", 142 - "line": 6, 151 + "line": 7, 143 152 }, 144 153 "message": "2", 145 154 "type": "warning", ··· 152 161 "location": { 153 162 "column": 9, 154 163 "file": "<root>/basic.test.ts", 155 - "line": 7, 164 + "line": 8, 156 165 }, 157 166 "message": "3", 158 167 "type": "notice", ··· 165 174 "location": { 166 175 "column": 9, 167 176 "file": "<root>/basic.test.ts", 168 - "line": 8, 177 + "line": 9, 169 178 }, 170 179 "message": "4", 171 180 "type": "warning", 181 + }, 182 + { 183 + "location": { 184 + "column": 9, 185 + "file": "<root>/basic.test.ts", 186 + "line": 10, 187 + }, 188 + "message": "external", 189 + "type": "notice", 172 190 }, 173 191 ], 174 192 } ··· 198 216 const { stdout } = await runInlineTests( 199 217 { 200 218 'basic.test.ts': annotationTest, 201 - 'test-3.js': '', 219 + 'test-3.js': test3Content, 202 220 'test-4.js': '', 203 221 }, 204 222 { reporters: ['tap'] }, ··· 214 232 # warning: 2 215 233 # notice: 3 216 234 # warning: 4 235 + # notice: external 217 236 ok 2 - suite # time=<time> { 218 237 1..1 219 238 ok 1 - second # time=<time> ··· 229 248 const { stdout } = await runInlineTests( 230 249 { 231 250 'basic.test.ts': annotationTest, 232 - 'test-3.js': '', 251 + 'test-3.js': test3Content, 233 252 'test-4.js': '', 234 253 }, 235 254 { reporters: ['tap-flat'] }, ··· 243 262 # warning: 2 244 263 # notice: 3 245 264 # warning: 4 265 + # notice: external 246 266 ok 2 - basic.test.ts > suite > second # time=<time> 247 267 # notice: 5 248 268 # notice: 6 ··· 254 274 const { stdout } = await runInlineTests( 255 275 { 256 276 'basic.test.ts': annotationTest, 257 - 'test-3.js': '', 277 + 'test-3.js': test3Content, 258 278 'test-4.js': '', 259 279 }, 260 280 { reporters: ['junit'] }, ··· 279 299 </property> 280 300 <property name="warning" value="4"> 281 301 </property> 302 + <property name="notice" value="external"> 303 + </property> 282 304 </properties> 283 305 </testcase> 284 306 <testcase classname="basic.test.ts" name="suite &gt; second" time="0"> ··· 299 321 const { stdout, ctx } = await runInlineTests( 300 322 { 301 323 'basic.test.ts': annotationTest, 302 - 'test-3.js': '', 324 + 'test-3.js': test3Content, 303 325 'test-4.js': '', 304 326 }, 305 327 { reporters: ['github-actions'] }, ··· 313 335 .replace(new RegExp(ctx!.config.root, 'g'), '<root>') 314 336 expect(result).toMatchInlineSnapshot(` 315 337 " 316 - ::notice file=<root>/basic.test.ts,line=5,column=9::1 338 + ::notice file=<root>/basic.test.ts,line=6,column=9::1 317 339 318 - ::warning file=<root>/basic.test.ts,line=6,column=9::2 340 + ::warning file=<root>/basic.test.ts,line=7,column=9::2 319 341 320 - ::notice file=<root>/basic.test.ts,line=7,column=9::3 342 + ::notice file=<root>/basic.test.ts,line=8,column=9::3 321 343 322 - ::warning file=<root>/basic.test.ts,line=8,column=9::4 344 + ::warning file=<root>/basic.test.ts,line=9,column=9::4 323 345 324 - ::notice file=<root>/basic.test.ts,line=13,column=11::5 346 + ::notice file=<root>/basic.test.ts,line=10,column=9::external 325 347 326 - ::notice file=<root>/basic.test.ts,line=14,column=11::6 348 + ::notice file=<root>/basic.test.ts,line=15,column=11::5 349 + 350 + ::notice file=<root>/basic.test.ts,line=16,column=11::6 327 351 " 328 352 `) 329 353 }) ··· 332 356 const { stdout } = await runInlineTests( 333 357 { 334 358 'basic.test.ts': annotationTest, 335 - 'test-3.js': '', 359 + 'test-3.js': test3Content, 336 360 'test-4.js': '', 337 361 }, 338 362 { reporters: [['verbose', { isTTY: false }]] }, ··· 348 372 expect(result).toMatchInlineSnapshot(` 349 373 " ✓ basic.test.ts > simple <time> 350 374 351 - ❯ basic.test.ts:5:9 notice 375 + ❯ basic.test.ts:6:9 notice 352 376 ↳ 1 353 - ❯ basic.test.ts:6:9 warning 377 + ❯ basic.test.ts:7:9 warning 354 378 ↳ 2 355 - ❯ basic.test.ts:7:9 notice 379 + ❯ basic.test.ts:8:9 notice 356 380 ↳ 3 357 - ❯ basic.test.ts:8:9 warning 381 + ❯ basic.test.ts:9:9 warning 358 382 ↳ 4 383 + ❯ basic.test.ts:10:9 notice 384 + ↳ external 359 385 360 386 ✓ basic.test.ts > suite > second <time> 361 387 362 - ❯ basic.test.ts:13:11 notice 388 + ❯ basic.test.ts:15:11 notice 363 389 ↳ 5 364 - ❯ basic.test.ts:14:11 notice 390 + ❯ basic.test.ts:16:11 notice 365 391 ↳ 6 366 392 367 393 "
+13
packages/runner/src/utils/collect.ts
··· 1 + import type { ParsedStack } from '@vitest/utils' 1 2 import type { File, Suite, TaskBase } from '../types/tasks' 2 3 import { processError } from '@vitest/utils/error' 4 + import { parseSingleStack } from '@vitest/utils/source-map' 3 5 import { relative } from 'pathe' 4 6 import { setFileContext } from '../context' 5 7 ··· 209 211 projectName: string | undefined, 210 212 ): string { 211 213 return generateHash(`${file}${projectName || ''}`) 214 + } 215 + 216 + export function findTestFileStackTrace(testFilePath: string, error: string): ParsedStack | undefined { 217 + // first line is the error message 218 + const lines = error.split('\n').slice(1) 219 + for (const line of lines) { 220 + const stack = parseSingleStack(line) 221 + if (stack && stack.file === testFilePath) { 222 + return stack 223 + } 224 + } 212 225 }
+1
packages/runner/src/utils/index.ts
··· 2 2 export { 3 3 calculateSuiteHash, 4 4 createFileTask, 5 + findTestFileStackTrace, 5 6 generateFileHash, 6 7 generateHash, 7 8 interpretTaskModes,