@@ -27,6 +27,11 @@ import { setupBrowserConfiguration } from './browser-provider';
2727import { findVitestBaseConfig } from './configuration' ;
2828import { createVitestConfigPlugin , createVitestPlugins } from './plugins' ;
2929
30+ enum DebugLogLevel {
31+ Info = 1 ,
32+ Verbose = 2 ,
33+ }
34+
3035export class VitestExecutor implements TestExecutor {
3136 private vitest : Vitest | undefined ;
3237 private normalizePath : ( ( id : string ) => string ) | undefined ;
@@ -40,6 +45,7 @@ export class VitestExecutor implements TestExecutor {
4045 explicitBrowser : [ ] ,
4146 explicitServer : [ ] ,
4247 } ;
48+ private readonly debugLevel : number ;
4349
4450 // This is a reverse map of the entry points created in `build-options.ts`.
4551 // It is used by the in-memory provider plugin to map the requested test file
@@ -54,27 +60,63 @@ export class VitestExecutor implements TestExecutor {
5460 testEntryPointMappings : Map < string , string > | undefined ,
5561 logger : BuilderContext [ 'logger' ] ,
5662 ) {
63+ const level = parseInt ( process . env [ 'NG_TEST_LOG' ] ?? '0' , 10 ) ;
64+ this . debugLevel = isNaN ( level ) ? 0 : level ;
65+
5766 this . projectName = projectName ;
5867 this . options = options ;
5968 this . logger = logger ;
6069
70+ this . debugLog ( DebugLogLevel . Info , 'VitestExecutor instantiated.' ) ;
71+ this . debugLog ( DebugLogLevel . Verbose , 'NormalizedUnitTestBuilderOptions:' , options ) ;
72+
6173 if ( testEntryPointMappings ) {
6274 for ( const [ entryPoint , testFile ] of testEntryPointMappings ) {
6375 this . testFileToEntryPoint . set ( testFile , entryPoint ) ;
6476 this . entryPointToTestFile . set ( entryPoint + '.js' , testFile ) ;
6577 }
78+ this . debugLog (
79+ DebugLogLevel . Verbose ,
80+ 'Test entry point mappings:' ,
81+ Object . fromEntries ( testEntryPointMappings ) ,
82+ ) ;
6683 }
6784 }
6885
86+ private debugLog ( level : DebugLogLevel , message : string , data ?: object ) {
87+ if ( this . debugLevel < level ) {
88+ return ;
89+ }
90+
91+ const formattedMessage = `[VitestExecutor:${ DebugLogLevel [ level ] } ] ${ message } ` ;
92+ // Custom formatting for data object to ensure it's readable
93+ const logData = data ? JSON . stringify ( data , null , 2 ) : '' ;
94+ this . logger . info ( `${ formattedMessage } ${ logData ? `\n${ logData } ` : '' } ` ) ;
95+ }
96+
6997 async * execute ( buildResult : FullResult | IncrementalResult ) : AsyncIterable < BuilderOutput > {
98+ this . debugLog ( DebugLogLevel . Info , `Executing test run (kind: ${ buildResult . kind } ).` ) ;
7099 this . normalizePath ??= ( await import ( 'vite' ) ) . normalizePath ;
71100
72101 if ( buildResult . kind === ResultKind . Full ) {
73102 this . buildResultFiles . clear ( ) ;
74103 for ( const [ path , file ] of Object . entries ( buildResult . files ) ) {
75104 this . buildResultFiles . set ( this . normalizePath ( path ) , file ) ;
76105 }
106+ this . debugLog (
107+ DebugLogLevel . Info ,
108+ `Full build results received. Total files: ${ this . buildResultFiles . size } .` ,
109+ ) ;
77110 } else {
111+ this . debugLog (
112+ DebugLogLevel . Info ,
113+ `Incremental build results received.` +
114+ `Added: ${ buildResult . added . length } , Modified: ${ buildResult . modified . length } , Removed: ${ buildResult . removed . length } .` ,
115+ ) ;
116+ this . debugLog ( DebugLogLevel . Verbose , 'Added files:' , buildResult . added ) ;
117+ this . debugLog ( DebugLogLevel . Verbose , 'Modified files:' , buildResult . modified ) ;
118+ this . debugLog ( DebugLogLevel . Verbose , 'Removed files:' , buildResult . removed ) ;
119+
78120 for ( const file of buildResult . removed ) {
79121 this . buildResultFiles . delete ( this . normalizePath ( file . path ) ) ;
80122 }
@@ -84,6 +126,7 @@ export class VitestExecutor implements TestExecutor {
84126 }
85127
86128 updateExternalMetadata ( buildResult , this . externalMetadata , undefined , true ) ;
129+ this . debugLog ( DebugLogLevel . Verbose , 'Updated external metadata:' , this . externalMetadata ) ;
87130
88131 // Reset the exit code to allow for a clean state.
89132 // This is necessary because Vitest may set the exit code on failure, which can
@@ -103,7 +146,16 @@ export class VitestExecutor implements TestExecutor {
103146 // We need to find the original source file path to pass to Vitest.
104147 const source = this . entryPointToTestFile . get ( modifiedFile ) ;
105148 if ( source ) {
149+ this . debugLog (
150+ DebugLogLevel . Verbose ,
151+ `Mapped output file '${ modifiedFile } ' to source file '${ source } ' for re-run.` ,
152+ ) ;
106153 modifiedSourceFiles . add ( source ) ;
154+ } else {
155+ this . debugLog (
156+ DebugLogLevel . Verbose ,
157+ `Could not map output file '${ modifiedFile } ' to a source file. It may not be a test file.` ,
158+ ) ;
107159 }
108160 vitest . invalidateFile (
109161 this . normalizePath ( path . join ( this . options . workspaceRoot , modifiedFile ) ) ,
@@ -120,28 +172,41 @@ export class VitestExecutor implements TestExecutor {
120172 }
121173
122174 if ( specsToRerun . length > 0 ) {
175+ this . debugLog ( DebugLogLevel . Info , `Re-running ${ specsToRerun . length } test specifications.` ) ;
176+ this . debugLog ( DebugLogLevel . Verbose , 'Specs to rerun:' , specsToRerun ) ;
123177 testResults = await vitest . rerunTestSpecifications ( specsToRerun ) ;
178+ } else {
179+ this . debugLog ( DebugLogLevel . Info , 'No test specifications to rerun.' ) ;
124180 }
125181 }
126182
127183 // Check if all the tests pass to calculate the result
128184 const testModules = testResults ?. testModules ?? this . vitest . state . getTestModules ( ) ;
129185
130186 let success = testModules . every ( ( testModule ) => testModule . ok ( ) ) ;
187+ let finalResultReason = 'All tests passed.' ;
188+
131189 // Vitest does not return a failure result when coverage thresholds are not met.
132190 // Instead, it sets the process exit code to 1.
133191 // We check this exit code to determine if the test run should be considered a failure.
134192 if ( success && process . exitCode === 1 ) {
135193 success = false ;
194+ finalResultReason = 'Test run failed due to unmet coverage thresholds.' ;
136195 // Reset the exit code to prevent it from carrying over to subsequent runs/builds
137196 process . exitCode = 0 ;
138197 }
139198
199+ this . debugLog (
200+ DebugLogLevel . Info ,
201+ `Test run finished with success: ${ success } . Reason: ${ finalResultReason } ` ,
202+ ) ;
140203 yield { success } ;
141204 }
142205
143206 async [ Symbol . asyncDispose ] ( ) : Promise < void > {
207+ this . debugLog ( DebugLogLevel . Info , 'Disposing VitestExecutor: Closing Vitest instance.' ) ;
144208 await this . vitest ?. close ( ) ;
209+ this . debugLog ( DebugLogLevel . Info , 'Vitest instance closed.' ) ;
145210 }
146211
147212 private prepareSetupFiles ( ) : string [ ] {
@@ -154,10 +219,13 @@ export class VitestExecutor implements TestExecutor {
154219 testSetupFiles . unshift ( 'polyfills.js' ) ;
155220 }
156221
222+ this . debugLog ( DebugLogLevel . Info , 'Prepared setup files:' , testSetupFiles ) ;
223+
157224 return testSetupFiles ;
158225 }
159226
160227 private async initializeVitest ( ) : Promise < Vitest > {
228+ this . debugLog ( DebugLogLevel . Info , 'Initializing Vitest.' ) ;
161229 const {
162230 coverage,
163231 reporters,
@@ -180,6 +248,10 @@ export class VitestExecutor implements TestExecutor {
180248 vitestNodeModule = await import ( 'vitest/node' ) ;
181249 } catch ( error : unknown ) {
182250 assertIsError ( error ) ;
251+ this . debugLog (
252+ DebugLogLevel . Info ,
253+ `Failed to import 'vitest/node'. Error code: ${ error . code } ` ,
254+ ) ;
183255 if ( error . code !== 'ERR_MODULE_NOT_FOUND' ) {
184256 throw error ;
185257 }
@@ -198,6 +270,9 @@ export class VitestExecutor implements TestExecutor {
198270 browserViewport ,
199271 ) ;
200272 if ( browserOptions . errors ?. length ) {
273+ this . debugLog ( DebugLogLevel . Info , 'Browser configuration errors found.' , {
274+ errors : browserOptions . errors ,
275+ } ) ;
201276 throw new Error ( browserOptions . errors . join ( '\n' ) ) ;
202277 }
203278
@@ -206,7 +281,14 @@ export class VitestExecutor implements TestExecutor {
206281 this . logger . info ( message ) ;
207282 }
208283 }
284+ this . debugLog ( DebugLogLevel . Info , 'Browser configuration complete.' , {
285+ config : browserOptions . browser ,
286+ } ) ;
209287
288+ this . debugLog (
289+ DebugLogLevel . Info ,
290+ `Verifying build results. File count: ${ this . buildResultFiles . size } .` ,
291+ ) ;
210292 assert (
211293 this . buildResultFiles . size > 0 ,
212294 'buildResult must be available before initializing vitest' ,
@@ -234,6 +316,10 @@ export class VitestExecutor implements TestExecutor {
234316 ? await findVitestBaseConfig ( [ projectRoot , workspaceRoot ] )
235317 : runnerConfig ;
236318
319+ this . debugLog ( DebugLogLevel . Info , 'External Vitest configuration path:' , {
320+ externalConfigPath,
321+ } ) ;
322+
237323 let project = projectName ;
238324 if ( debug && browserOptions . browser ?. instances ) {
239325 if ( browserOptions . browser . instances . length > 1 ) {
@@ -245,6 +331,9 @@ export class VitestExecutor implements TestExecutor {
245331 // When running browser tests, Vitest appends the browser name to the project identifier.
246332 // The project name must match this augmented name to ensure the correct project is targeted.
247333 project = `${ projectName } (${ browserOptions . browser . instances [ 0 ] . browser } )` ;
334+ this . debugLog ( DebugLogLevel . Info , 'Adjusted project name for debugging with browser:' , {
335+ project,
336+ } ) ;
248337 }
249338
250339 // Filter internal entries and setup files from the include list
@@ -255,43 +344,46 @@ export class VitestExecutor implements TestExecutor {
255344 ! internalEntries . some ( ( internal ) => entry . startsWith ( internal ) ) && ! setupFileSet . has ( entry )
256345 ) ;
257346 } ) ;
347+ this . debugLog ( DebugLogLevel . Verbose , 'Included test files (after filtering):' , include ) ;
258348
259- return startVitest (
260- 'test' ,
261- undefined ,
262- {
263- config : externalConfigPath ,
264- root : workspaceRoot ,
265- project,
266- outputFile,
267- cache : cacheOptions . enabled ? undefined : false ,
268- testNamePattern : this . options . filter ,
269- watch,
270- ...( typeof ui === 'boolean' ? { ui } : { } ) ,
271- ...debugOptions ,
272- } ,
273- {
274- // Note `.vitest` is auto appended to the path.
275- cacheDir : cacheOptions . path ,
276- server : {
277- // Disable the actual file watcher. The boolean watch option above should still
278- // be enabled as it controls other internal behavior related to rerunning tests.
279- watch : null ,
280- } ,
281- plugins : [
282- await createVitestConfigPlugin ( {
283- browser : browserOptions . browser ,
284- coverage,
285- projectName,
286- projectSourceRoot,
287- optimizeDepsInclude : this . externalMetadata . implicitBrowser ,
288- reporters,
289- setupFiles : testSetupFiles ,
290- projectPlugins,
291- include,
292- } ) ,
293- ] ,
349+ const vitestConfig = {
350+ config : externalConfigPath ,
351+ root : workspaceRoot ,
352+ project,
353+ outputFile,
354+ cache : cacheOptions . enabled ? undefined : ( false as const ) ,
355+ testNamePattern : this . options . filter ,
356+ watch,
357+ ...( typeof ui === 'boolean' ? { ui } : { } ) ,
358+ ...debugOptions ,
359+ } ;
360+ const vitestServerConfig = {
361+ // Note `.vitest` is auto appended to the path.
362+ cacheDir : cacheOptions . path ,
363+ server : {
364+ // Disable the actual file watcher. The boolean watch option above should still
365+ // be enabled as it controls other internal behavior related to rerunning tests.
366+ watch : null ,
294367 } ,
295- ) ;
368+ plugins : [
369+ await createVitestConfigPlugin ( {
370+ browser : browserOptions . browser ,
371+ coverage,
372+ projectName,
373+ projectSourceRoot,
374+ optimizeDepsInclude : this . externalMetadata . implicitBrowser ,
375+ reporters,
376+ setupFiles : testSetupFiles ,
377+ projectPlugins,
378+ include,
379+ } ) ,
380+ ] ,
381+ } ;
382+
383+ this . debugLog ( DebugLogLevel . Info , 'Calling startVitest with final configuration.' ) ;
384+ this . debugLog ( DebugLogLevel . Verbose , 'Vitest config:' , vitestConfig ) ;
385+ this . debugLog ( DebugLogLevel . Verbose , 'Vitest server config:' , vitestServerConfig ) ;
386+
387+ return startVitest ( 'test' , undefined , vitestConfig , vitestServerConfig ) ;
296388 }
297389}
0 commit comments