@@ -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,56 @@ 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 ) ) ;
74+ }
75+ }
76+
77+ private log ( level : number , message : string , data ?: object ) {
78+ if ( this . logLevel < level ) {
79+ return ;
6680 }
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 } ` : '' } ` ) ;
6786 }
6887
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.` +
102+ `Added: ${ buildResult . added . length } , Modified: ${ buildResult . modified . length } , Removed: ${ buildResult . removed . length } .` ,
103+ ) ;
104+ this . log ( 2 , 'Added files:' , buildResult . added ) ;
105+ this . log ( 2 , 'Modified files:' , buildResult . modified ) ;
106+ this . log ( 2 , 'Removed files:' , buildResult . removed ) ;
107+
78108 for ( const file of buildResult . removed ) {
79109 this . buildResultFiles . delete ( this . normalizePath ( file . path ) ) ;
80110 }
@@ -84,6 +114,7 @@ export class VitestExecutor implements TestExecutor {
84114 }
85115
86116 updateExternalMetadata ( buildResult , this . externalMetadata , undefined , true ) ;
117+ this . log ( 2 , 'Updated external metadata:' , this . externalMetadata ) ;
87118
88119 // Reset the exit code to allow for a clean state.
89120 // This is necessary because Vitest may set the exit code on failure, which can
@@ -103,7 +134,16 @@ export class VitestExecutor implements TestExecutor {
103134 // We need to find the original source file path to pass to Vitest.
104135 const source = this . entryPointToTestFile . get ( modifiedFile ) ;
105136 if ( source ) {
137+ this . log (
138+ 2 ,
139+ `Mapped output file '${ modifiedFile } ' to source file '${ source } ' for re-run.` ,
140+ ) ;
106141 modifiedSourceFiles . add ( source ) ;
142+ } else {
143+ this . log (
144+ 2 ,
145+ `Could not map output file '${ modifiedFile } ' to a source file. It may not be a test file.` ,
146+ ) ;
107147 }
108148 vitest . invalidateFile (
109149 this . normalizePath ( path . join ( this . options . workspaceRoot , modifiedFile ) ) ,
@@ -120,28 +160,38 @@ export class VitestExecutor implements TestExecutor {
120160 }
121161
122162 if ( specsToRerun . length > 0 ) {
163+ this . log ( 1 , `Re-running ${ specsToRerun . length } test specifications.` ) ;
164+ this . log ( 2 , 'Specs to rerun:' , specsToRerun ) ;
123165 testResults = await vitest . rerunTestSpecifications ( specsToRerun ) ;
166+ } else {
167+ this . log ( 1 , 'No test specifications to rerun.' ) ;
124168 }
125169 }
126170
127171 // Check if all the tests pass to calculate the result
128172 const testModules = testResults ?. testModules ?? this . vitest . state . getTestModules ( ) ;
129173
130174 let success = testModules . every ( ( testModule ) => testModule . ok ( ) ) ;
175+ let finalResultReason = 'All tests passed.' ;
176+
131177 // Vitest does not return a failure result when coverage thresholds are not met.
132178 // Instead, it sets the process exit code to 1.
133179 // We check this exit code to determine if the test run should be considered a failure.
134180 if ( success && process . exitCode === 1 ) {
135181 success = false ;
182+ finalResultReason = 'Test run failed due to unmet coverage thresholds.' ;
136183 // Reset the exit code to prevent it from carrying over to subsequent runs/builds
137184 process . exitCode = 0 ;
138185 }
139186
187+ this . log ( 1 , `Test run finished with success: ${ success } . Reason: ${ finalResultReason } ` ) ;
140188 yield { success } ;
141189 }
142190
143191 async [ Symbol . asyncDispose ] ( ) : Promise < void > {
192+ this . log ( 1 , 'Disposing VitestExecutor: Closing Vitest instance.' ) ;
144193 await this . vitest ?. close ( ) ;
194+ this . log ( 1 , 'Vitest instance closed.' ) ;
145195 }
146196
147197 private prepareSetupFiles ( ) : string [ ] {
@@ -154,10 +204,13 @@ export class VitestExecutor implements TestExecutor {
154204 testSetupFiles . unshift ( 'polyfills.js' ) ;
155205 }
156206
207+ this . log ( 1 , 'Prepared setup files:' , testSetupFiles ) ;
208+
157209 return testSetupFiles ;
158210 }
159211
160212 private async initializeVitest ( ) : Promise < Vitest > {
213+ this . log ( 1 , 'Initializing Vitest.' ) ;
161214 const {
162215 coverage,
163216 reporters,
@@ -180,6 +233,7 @@ export class VitestExecutor implements TestExecutor {
180233 vitestNodeModule = await import ( 'vitest/node' ) ;
181234 } catch ( error : unknown ) {
182235 assertIsError ( error ) ;
236+ this . log ( 1 , `Failed to import 'vitest/node'. Error code: ${ error . code } ` ) ;
183237 if ( error . code !== 'ERR_MODULE_NOT_FOUND' ) {
184238 throw error ;
185239 }
@@ -199,6 +253,7 @@ export class VitestExecutor implements TestExecutor {
199253 browserViewport ,
200254 ) ;
201255 if ( browserOptions . errors ?. length ) {
256+ this . log ( 1 , 'Browser configuration errors found.' , { errors : browserOptions . errors } ) ;
202257 throw new Error ( browserOptions . errors . join ( '\n' ) ) ;
203258 }
204259
@@ -207,7 +262,9 @@ export class VitestExecutor implements TestExecutor {
207262 this . logger . info ( message ) ;
208263 }
209264 }
265+ this . log ( 1 , 'Browser configuration complete.' , { config : browserOptions . browser } ) ;
210266
267+ this . log ( 1 , `Verifying build results. File count: ${ this . buildResultFiles . size } .` ) ;
211268 assert (
212269 this . buildResultFiles . size > 0 ,
213270 'buildResult must be available before initializing vitest' ,
@@ -235,6 +292,8 @@ export class VitestExecutor implements TestExecutor {
235292 ? await findVitestBaseConfig ( [ projectRoot , workspaceRoot ] )
236293 : runnerConfig ;
237294
295+ this . log ( 1 , 'External Vitest configuration path:' , { externalConfigPath } ) ;
296+
238297 let project = projectName ;
239298 if ( debug && browserOptions . browser ?. instances ) {
240299 if ( browserOptions . browser . instances . length > 1 ) {
@@ -246,6 +305,7 @@ export class VitestExecutor implements TestExecutor {
246305 // When running browser tests, Vitest appends the browser name to the project identifier.
247306 // The project name must match this augmented name to ensure the correct project is targeted.
248307 project = `${ projectName } (${ browserOptions . browser . instances [ 0 ] . browser } )` ;
308+ this . log ( 1 , 'Adjusted project name for debugging with browser:' , { project } ) ;
249309 }
250310
251311 // Filter internal entries and setup files from the include list
@@ -256,43 +316,46 @@ export class VitestExecutor implements TestExecutor {
256316 ! internalEntries . some ( ( internal ) => entry . startsWith ( internal ) ) && ! setupFileSet . has ( entry )
257317 ) ;
258318 } ) ;
319+ this . log ( 2 , 'Included test files (after filtering):' , include ) ;
259320
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- ] ,
321+ const vitestConfig = {
322+ config : externalConfigPath ,
323+ root : workspaceRoot ,
324+ project,
325+ outputFile,
326+ cache : cacheOptions . enabled ? undefined : ( false as const ) ,
327+ testNamePattern : this . options . filter ,
328+ watch,
329+ ...( typeof ui === 'boolean' ? { ui } : { } ) ,
330+ ...debugOptions ,
331+ } ;
332+ const vitestServerConfig = {
333+ // Note `.vitest` is auto appended to the path.
334+ cacheDir : cacheOptions . path ,
335+ server : {
336+ // Disable the actual file watcher. The boolean watch option above should still
337+ // be enabled as it controls other internal behavior related to rerunning tests.
338+ watch : null ,
295339 } ,
296- ) ;
340+ plugins : [
341+ await createVitestConfigPlugin ( {
342+ browser : browserOptions . browser ,
343+ coverage,
344+ projectName,
345+ projectSourceRoot,
346+ optimizeDepsInclude : this . externalMetadata . implicitBrowser ,
347+ reporters,
348+ setupFiles : testSetupFiles ,
349+ projectPlugins,
350+ include,
351+ } ) ,
352+ ] ,
353+ } ;
354+
355+ this . log ( 1 , 'Calling startVitest with final configuration.' ) ;
356+ this . log ( 2 , 'Vitest config:' , vitestConfig ) ;
357+ this . log ( 2 , 'Vitest server config:' , vitestServerConfig ) ;
358+
359+ return startVitest ( 'test' , undefined , vitestConfig , vitestServerConfig ) ;
297360 }
298361}
0 commit comments