@@ -266,10 +266,96 @@ if (RANDOM_SEED !== 0) {
266266 }
267267}
268268
269- // Current test context (set by Jest hooks)
269+ // Current test context (set by Jest hooks or Vitest worker )
270270let currentTestName = null ;
271271let currentTestPath = null ; // Test file path from Jest
272272
273+ /**
274+ * Get the current test name from Vitest's internal worker API.
275+ * Vitest doesn't inject beforeEach as a global, so the Jest-style hook doesn't fire.
276+ * Instead, we query Vitest's worker directly for the current test name.
277+ *
278+ * In Vitest's fork pool, `__vitest_worker__.current` is the current task object
279+ * with properties: name, fullName, fullTestName, suite, type, file, etc.
280+ * Also, `expect.getState().currentTestName` works from within a test.
281+ *
282+ * @returns {string|null } The current test name, or null if not in Vitest
283+ */
284+ function getVitestTestName ( ) {
285+ // Prefer expect.getState().currentTestName — returns full path including describe blocks
286+ // e.g., "Performance tests > should return true for basic HTML tags"
287+ // This matches what Jest's beforeEach hook would set.
288+ try {
289+ if ( typeof expect !== 'undefined' && expect . getState ) {
290+ const state = expect . getState ( ) ;
291+ if ( state ?. currentTestName ) {
292+ return state . currentTestName ;
293+ }
294+ }
295+ } catch ( e ) {
296+ // expect not available
297+ }
298+ // Fallback: Vitest worker API — worker.current.fullTestName includes describe path
299+ try {
300+ const worker = globalThis . __vitest_worker__ ;
301+ if ( worker ?. current ?. fullTestName ) {
302+ return worker . current . fullTestName ;
303+ }
304+ if ( worker ?. current ?. fullName ) {
305+ return worker . current . fullName ;
306+ }
307+ if ( worker ?. current ?. name ) {
308+ return worker . current . name ;
309+ }
310+ } catch ( e ) {
311+ // Not in Vitest context
312+ }
313+ return null ;
314+ }
315+
316+ /**
317+ * Get the current test file path from Vitest's internal worker API.
318+ * @returns {string|null } The current test file path, or null if not in Vitest
319+ */
320+ function getVitestTestPath ( ) {
321+ try {
322+ const worker = globalThis . __vitest_worker__ ;
323+ if ( worker ?. filepath ) {
324+ return worker . filepath ;
325+ }
326+ } catch ( e ) {
327+ // Not in Vitest context
328+ }
329+ // Fallback: try expect.getState() for testPath
330+ try {
331+ if ( typeof expect !== 'undefined' && expect . getState ) {
332+ const state = expect . getState ( ) ;
333+ if ( state ?. testPath ) {
334+ return state . testPath ;
335+ }
336+ }
337+ } catch ( e ) {
338+ // expect not available
339+ }
340+ return null ;
341+ }
342+
343+ /**
344+ * Get the effective test name, trying Jest hooks first, then Vitest API, then fallback.
345+ * @returns {string } The current test name
346+ */
347+ function getEffectiveTestName ( ) {
348+ return currentTestName || getVitestTestName ( ) || 'unknown' ;
349+ }
350+
351+ /**
352+ * Get the effective test path, trying Jest hooks first, then Vitest API, then fallback.
353+ * @returns {string|null } The current test file path
354+ */
355+ function getEffectiveTestPath ( ) {
356+ return currentTestPath || getVitestTestPath ( ) || null ;
357+ }
358+
273359// Invocation counter map: tracks how many times each testId has been seen
274360// Key: testId (testModule:testClass:testFunction:lineId:loopIndex)
275361// Value: count (starts at 0, increments each time same key is seen)
@@ -549,13 +635,14 @@ function capture(funcName, lineId, fn, ...args) {
549635
550636 // Get test context (raw values for SQLite storage)
551637 // Use TEST_MODULE env var if set, otherwise derive from test file path
638+ const effectiveTestPath = getEffectiveTestPath ( ) ;
552639 let testModulePath ;
553640 if ( TEST_MODULE ) {
554641 testModulePath = TEST_MODULE ;
555- } else if ( currentTestPath ) {
642+ } else if ( effectiveTestPath ) {
556643 // Get relative path from cwd and convert to module-style path
557644 const path = require ( 'path' ) ;
558- const relativePath = path . relative ( process . cwd ( ) , currentTestPath ) ;
645+ const relativePath = path . relative ( process . cwd ( ) , effectiveTestPath ) ;
559646 // Convert to Python module-style path (e.g., "tests/test_foo.test.js" -> "tests.test_foo.test")
560647 // This matches what Jest's junit XML produces
561648 testModulePath = relativePath
@@ -564,10 +651,10 @@ function capture(funcName, lineId, fn, ...args) {
564651 . replace ( / \. t e s t $ / , '.test' ) // Keep .test suffix
565652 . replace ( / \/ / g, '.' ) ; // Convert path separators to dots
566653 } else {
567- testModulePath = currentTestName || 'unknown' ;
654+ testModulePath = getEffectiveTestName ( ) ;
568655 }
569656 const testClassName = null ; // Jest doesn't use classes like Python
570- const testFunctionName = currentTestName || 'unknown' ;
657+ const testFunctionName = getEffectiveTestName ( ) ;
571658
572659 // Sanitized versions for stdout tags (avoid regex conflicts)
573660 const safeModulePath = sanitizeTestId ( testModulePath ) ;
@@ -583,8 +670,8 @@ function capture(funcName, lineId, fn, ...args) {
583670 // Format stdout tag (matches Python format, uses sanitized names)
584671 const testStdoutTag = `${ safeModulePath } :${ testClassName ? testClassName + '.' : '' } ${ safeTestFunctionName } :${ funcName } :${ LOOP_INDEX } :${ invocationId } ` ;
585672
586- // Print start tag
587- console . log ( `!$######${ testStdoutTag } ######$!` ) ;
673+ // Print start tag (use process.stdout.write to bypass test framework console interception)
674+ process . stdout . write ( `!$######${ testStdoutTag } ######$!\n ` ) ;
588675
589676 // Timing with nanosecond precision
590677 const startTime = getTimeNs ( ) ;
@@ -602,14 +689,14 @@ function capture(funcName, lineId, fn, ...args) {
602689 const durationNs = getDurationNs ( startTime , endTime ) ;
603690 recordResult ( testModulePath , testClassName , testFunctionName , funcName , invocationId , args , resolved , null , durationNs ) ;
604691 // Print end tag (no duration for behavior mode)
605- console . log ( `!######${ testStdoutTag } ######!` ) ;
692+ process . stdout . write ( `!######${ testStdoutTag } ######!\n ` ) ;
606693 return resolved ;
607694 } ,
608695 ( err ) => {
609696 const endTime = getTimeNs ( ) ;
610697 const durationNs = getDurationNs ( startTime , endTime ) ;
611698 recordResult ( testModulePath , testClassName , testFunctionName , funcName , invocationId , args , null , err , durationNs ) ;
612- console . log ( `!######${ testStdoutTag } ######!` ) ;
699+ process . stdout . write ( `!######${ testStdoutTag } ######!\n ` ) ;
613700 throw err ;
614701 }
615702 ) ;
@@ -623,7 +710,7 @@ function capture(funcName, lineId, fn, ...args) {
623710 recordResult ( testModulePath , testClassName , testFunctionName , funcName , invocationId , args , returnValue , error , durationNs ) ;
624711
625712 // Print end tag (no duration for behavior mode, matching Python)
626- console . log ( `!######${ testStdoutTag } ######!` ) ;
713+ process . stdout . write ( `!######${ testStdoutTag } ######!\n ` ) ;
627714
628715 if ( error ) throw error ;
629716 return returnValue ;
@@ -656,22 +743,24 @@ function capturePerf(funcName, lineId, fn, ...args) {
656743 const shouldLoop = getPerfLoopCount ( ) > 1 && ! checkSharedTimeLimit ( ) ;
657744
658745 // Get test context (computed once, reused across batch)
746+ // Uses Vitest worker API as fallback when Jest-style beforeEach hook doesn't fire
747+ const effectiveTestPath = getEffectiveTestPath ( ) ;
659748 let testModulePath ;
660749 if ( TEST_MODULE ) {
661750 testModulePath = TEST_MODULE ;
662- } else if ( currentTestPath ) {
751+ } else if ( effectiveTestPath ) {
663752 const path = require ( 'path' ) ;
664- const relativePath = path . relative ( process . cwd ( ) , currentTestPath ) ;
753+ const relativePath = path . relative ( process . cwd ( ) , effectiveTestPath ) ;
665754 testModulePath = relativePath
666755 . replace ( / \\ / g, '/' )
667756 . replace ( / \. j s $ / , '' )
668757 . replace ( / \. t e s t $ / , '.test' )
669758 . replace ( / \/ / g, '.' ) ;
670759 } else {
671- testModulePath = currentTestName || 'unknown' ;
760+ testModulePath = getEffectiveTestName ( ) ;
672761 }
673762 const testClassName = null ;
674- const testFunctionName = currentTestName || 'unknown' ;
763+ const testFunctionName = getEffectiveTestName ( ) ;
675764
676765 const safeModulePath = sanitizeTestId ( testModulePath ) ;
677766 const safeTestFunctionName = sanitizeTestId ( testFunctionName ) ;
@@ -767,8 +856,8 @@ function capturePerf(funcName, lineId, fn, ...args) {
767856 lastError = e ;
768857 }
769858
770- // Print end tag with timing
771- console . log ( `!######${ testStdoutTag } :${ durationNs } ######!` ) ;
859+ // Print end tag with timing (use process.stdout.write to bypass test framework console interception)
860+ process . stdout . write ( `!######${ testStdoutTag } :${ durationNs } ######!\n ` ) ;
772861
773862 // Update shared loop counter
774863 sharedPerfState . totalLoopsCompleted ++ ;
@@ -808,7 +897,7 @@ function capturePerf(funcName, lineId, fn, ...args) {
808897 * @private
809898 */
810899function _recordAsyncTiming ( startTime , testStdoutTag , durationNs , runtimes ) {
811- console . log ( `!######${ testStdoutTag } :${ durationNs } ######!` ) ;
900+ process . stdout . write ( `!######${ testStdoutTag } :${ durationNs } ######!\n ` ) ;
812901 sharedPerfState . totalLoopsCompleted ++ ;
813902 if ( durationNs > 0 ) {
814903 runtimes . push ( durationNs / 1000 ) ;
0 commit comments