@@ -40,6 +40,7 @@ export class VitestExecutor implements TestExecutor {
4040 explicitBrowser : [ ] ,
4141 explicitServer : [ ] ,
4242 } ;
43+ private readonly logLevel : number ;
4344
4445 // This is a reverse map of the entry points created in `build-options.ts`.
4546 // It is used by the in-memory provider plugin to map the requested test file
@@ -54,27 +55,55 @@ export class VitestExecutor implements TestExecutor {
5455 testEntryPointMappings : Map < string , string > | undefined ,
5556 logger : BuilderContext [ 'logger' ] ,
5657 ) {
58+ const level = parseInt ( process . env [ 'NG_TEST_LOG' ] ?? '0' , 10 ) ;
59+ this . logLevel = isNaN ( level ) ? 0 : level ;
60+
5761 this . projectName = projectName ;
5862 this . options = options ;
5963 this . logger = logger ;
6064
65+ this . log ( 1 , 'VitestExecutor instantiated.' ) ;
66+ this . log ( 2 , 'NormalizedUnitTestBuilderOptions:' , options ) ;
67+
6168 if ( testEntryPointMappings ) {
6269 for ( const [ entryPoint , testFile ] of testEntryPointMappings ) {
6370 this . testFileToEntryPoint . set ( testFile , entryPoint ) ;
6471 this . entryPointToTestFile . set ( entryPoint + '.js' , testFile ) ;
6572 }
73+ this . log ( 2 , 'Test entry point mappings:' , Object . fromEntries ( testEntryPointMappings ) ) ;
6674 }
6775 }
6876
77+ private log ( level : number , message : string , data ?: object ) {
78+ if ( this . logLevel < level ) {
79+ return ;
80+ }
81+
82+ const formattedMessage = `[VitestExecutor:L${ level } ] ${ message } ` ;
83+ // Custom formatting for data object to ensure it's readable
84+ const logData = data ? JSON . stringify ( data , null , 2 ) : '' ;
85+ this . logger . info ( `${ formattedMessage } ${ logData ? `\n${ logData } ` : '' } ` ) ;
86+ }
87+
6988 async * execute ( buildResult : FullResult | IncrementalResult ) : AsyncIterable < BuilderOutput > {
89+ this . log ( 1 , `Executing test run (kind: ${ buildResult . kind } ).` ) ;
7090 this . normalizePath ??= ( await import ( 'vite' ) ) . normalizePath ;
7191
7292 if ( buildResult . kind === ResultKind . Full ) {
7393 this . buildResultFiles . clear ( ) ;
7494 for ( const [ path , file ] of Object . entries ( buildResult . files ) ) {
7595 this . buildResultFiles . set ( this . normalizePath ( path ) , file ) ;
7696 }
97+ this . log ( 1 , `Full build results received. Total files: ${ this . buildResultFiles . size } .` ) ;
7798 } else {
99+ this . log (
100+ 1 ,
101+ `Incremental build results received. Added: ${ buildResult . added . length } , Modified: ${ buildResult . modified . length } , Removed: ${ buildResult . removed . length } .` ,
102+ ) ;
103+ this . log ( 2 , 'Added files:' , buildResult . added ) ;
104+ this . log ( 2 , 'Modified files:' , buildResult . modified ) ;
105+ this . log ( 2 , 'Removed files:' , buildResult . removed ) ;
106+
78107 for ( const file of buildResult . removed ) {
79108 this . buildResultFiles . delete ( this . normalizePath ( file . path ) ) ;
80109 }
@@ -84,6 +113,7 @@ export class VitestExecutor implements TestExecutor {
84113 }
85114
86115 updateExternalMetadata ( buildResult , this . externalMetadata , undefined , true ) ;
116+ this . log ( 2 , 'Updated external metadata:' , this . externalMetadata ) ;
87117
88118 // Reset the exit code to allow for a clean state.
89119 // This is necessary because Vitest may set the exit code on failure, which can
@@ -103,7 +133,16 @@ export class VitestExecutor implements TestExecutor {
103133 // We need to find the original source file path to pass to Vitest.
104134 const source = this . entryPointToTestFile . get ( modifiedFile ) ;
105135 if ( source ) {
136+ this . log (
137+ 2 ,
138+ `Mapped output file '${ modifiedFile } ' to source file '${ source } ' for re-run.` ,
139+ ) ;
106140 modifiedSourceFiles . add ( source ) ;
141+ } else {
142+ this . log (
143+ 2 ,
144+ `Could not map output file '${ modifiedFile } ' to a source file. It may not be a test file.` ,
145+ ) ;
107146 }
108147 vitest . invalidateFile (
109148 this . normalizePath ( path . join ( this . options . workspaceRoot , modifiedFile ) ) ,
@@ -120,28 +159,38 @@ export class VitestExecutor implements TestExecutor {
120159 }
121160
122161 if ( specsToRerun . length > 0 ) {
162+ this . log ( 1 , `Re-running ${ specsToRerun . length } test specifications.` ) ;
163+ this . log ( 2 , 'Specs to rerun:' , specsToRerun ) ;
123164 testResults = await vitest . rerunTestSpecifications ( specsToRerun ) ;
165+ } else {
166+ this . log ( 1 , 'No test specifications to rerun.' ) ;
124167 }
125168 }
126169
127170 // Check if all the tests pass to calculate the result
128171 const testModules = testResults ?. testModules ?? this . vitest . state . getTestModules ( ) ;
129172
130173 let success = testModules . every ( ( testModule ) => testModule . ok ( ) ) ;
174+ let finalResultReason = 'All tests passed.' ;
175+
131176 // Vitest does not return a failure result when coverage thresholds are not met.
132177 // Instead, it sets the process exit code to 1.
133178 // We check this exit code to determine if the test run should be considered a failure.
134179 if ( success && process . exitCode === 1 ) {
135180 success = false ;
181+ finalResultReason = 'Test run failed due to unmet coverage thresholds.' ;
136182 // Reset the exit code to prevent it from carrying over to subsequent runs/builds
137183 process . exitCode = 0 ;
138184 }
139185
186+ this . log ( 1 , `Test run finished with success: ${ success } . Reason: ${ finalResultReason } ` ) ;
140187 yield { success } ;
141188 }
142189
143190 async [ Symbol . asyncDispose ] ( ) : Promise < void > {
191+ this . log ( 1 , 'Disposing VitestExecutor: Closing Vitest instance.' ) ;
144192 await this . vitest ?. close ( ) ;
193+ this . log ( 1 , 'Vitest instance closed.' ) ;
145194 }
146195
147196 private prepareSetupFiles ( ) : string [ ] {
@@ -154,10 +203,12 @@ export class VitestExecutor implements TestExecutor {
154203 testSetupFiles . unshift ( 'polyfills.js' ) ;
155204 }
156205
206+ this . log ( 1 , 'Prepared setup files:' , testSetupFiles ) ;
157207 return testSetupFiles ;
158208 }
159209
160210 private async initializeVitest ( ) : Promise < Vitest > {
211+ this . log ( 1 , 'Initializing Vitest.' ) ;
161212 const {
162213 coverage,
163214 reporters,
@@ -180,6 +231,7 @@ export class VitestExecutor implements TestExecutor {
180231 vitestNodeModule = await import ( 'vitest/node' ) ;
181232 } catch ( error : unknown ) {
182233 assertIsError ( error ) ;
234+ this . log ( 1 , `Failed to import 'vitest/node'. Error code: ${ error . code } ` ) ;
183235 if ( error . code !== 'ERR_MODULE_NOT_FOUND' ) {
184236 throw error ;
185237 }
@@ -199,6 +251,7 @@ export class VitestExecutor implements TestExecutor {
199251 browserViewport ,
200252 ) ;
201253 if ( browserOptions . errors ?. length ) {
254+ this . log ( 1 , 'Browser configuration errors found.' , { errors : browserOptions . errors } ) ;
202255 throw new Error ( browserOptions . errors . join ( '\n' ) ) ;
203256 }
204257
@@ -207,7 +260,9 @@ export class VitestExecutor implements TestExecutor {
207260 this . logger . info ( message ) ;
208261 }
209262 }
263+ this . log ( 1 , 'Browser configuration complete.' , { config : browserOptions . browser } ) ;
210264
265+ this . log ( 1 , `Verifying build results. File count: ${ this . buildResultFiles . size } .` ) ;
211266 assert (
212267 this . buildResultFiles . size > 0 ,
213268 'buildResult must be available before initializing vitest' ,
@@ -235,6 +290,8 @@ export class VitestExecutor implements TestExecutor {
235290 ? await findVitestBaseConfig ( [ projectRoot , workspaceRoot ] )
236291 : runnerConfig ;
237292
293+ this . log ( 1 , 'External Vitest configuration path:' , { externalConfigPath } ) ;
294+
238295 let project = projectName ;
239296 if ( debug && browserOptions . browser ?. instances ) {
240297 if ( browserOptions . browser . instances . length > 1 ) {
@@ -246,6 +303,7 @@ export class VitestExecutor implements TestExecutor {
246303 // When running browser tests, Vitest appends the browser name to the project identifier.
247304 // The project name must match this augmented name to ensure the correct project is targeted.
248305 project = `${ projectName } (${ browserOptions . browser . instances [ 0 ] . browser } )` ;
306+ this . log ( 1 , 'Adjusted project name for debugging with browser:' , { project } ) ;
249307 }
250308
251309 // Filter internal entries and setup files from the include list
@@ -256,43 +314,46 @@ export class VitestExecutor implements TestExecutor {
256314 ! internalEntries . some ( ( internal ) => entry . startsWith ( internal ) ) && ! setupFileSet . has ( entry )
257315 ) ;
258316 } ) ;
317+ this . log ( 2 , 'Included test files (after filtering):' , include ) ;
259318
260- return startVitest (
261- 'test' ,
262- undefined ,
263- {
264- config : externalConfigPath ,
265- root : workspaceRoot ,
266- project,
267- outputFile,
268- cache : cacheOptions . enabled ? undefined : false ,
269- testNamePattern : this . options . filter ,
270- watch,
271- ...( typeof ui === 'boolean' ? { ui } : { } ) ,
272- ...debugOptions ,
273- } ,
274- {
275- // Note `.vitest` is auto appended to the path.
276- cacheDir : cacheOptions . path ,
277- server : {
278- // Disable the actual file watcher. The boolean watch option above should still
279- // be enabled as it controls other internal behavior related to rerunning tests.
280- watch : null ,
281- } ,
282- plugins : [
283- await createVitestConfigPlugin ( {
284- browser : browserOptions . browser ,
285- coverage,
286- projectName,
287- projectSourceRoot,
288- optimizeDepsInclude : this . externalMetadata . implicitBrowser ,
289- reporters,
290- setupFiles : testSetupFiles ,
291- projectPlugins,
292- include,
293- } ) ,
294- ] ,
319+ const vitestConfig = {
320+ config : externalConfigPath ,
321+ root : workspaceRoot ,
322+ project,
323+ outputFile,
324+ cache : cacheOptions . enabled ? undefined : ( false as const ) ,
325+ testNamePattern : this . options . filter ,
326+ watch,
327+ ...( typeof ui === 'boolean' ? { ui } : { } ) ,
328+ ...debugOptions ,
329+ } ;
330+ const vitestServerConfig = {
331+ // Note `.vitest` is auto appended to the path.
332+ cacheDir : cacheOptions . path ,
333+ server : {
334+ // Disable the actual file watcher. The boolean watch option above should still
335+ // be enabled as it controls other internal behavior related to rerunning tests.
336+ watch : null ,
295337 } ,
296- ) ;
338+ plugins : [
339+ await createVitestConfigPlugin ( {
340+ browser : browserOptions . browser ,
341+ coverage,
342+ projectName,
343+ projectSourceRoot,
344+ optimizeDepsInclude : this . externalMetadata . implicitBrowser ,
345+ reporters,
346+ setupFiles : testSetupFiles ,
347+ projectPlugins,
348+ include,
349+ } ) ,
350+ ] ,
351+ } ;
352+
353+ this . log ( 1 , 'Calling startVitest with final configuration.' ) ;
354+ this . log ( 2 , 'Vitest config:' , vitestConfig ) ;
355+ this . log ( 2 , 'Vitest server config:' , vitestServerConfig ) ;
356+
357+ return startVitest ( 'test' , undefined , vitestConfig , vitestServerConfig ) ;
297358 }
298359}
0 commit comments