@@ -40,16 +40,15 @@ function renderReplayTestSummary(
4040 for ( const entry of data . failures ) {
4141 renderFailedTestResult ( entry ) ;
4242 }
43- for ( const entry of flaky ) {
44- renderFlakyTestResult ( entry ) ;
45- }
4643 }
4744
4845 const durationMs = typeof data . durationMs === 'number' ? data . durationMs : undefined ;
4946 const flakySuffix = flaky . length > 0 ? `, ${ flaky . length } flaky` : '' ;
47+ const durationSuffix = durationMs !== undefined ? ` in ${ formatDurationSeconds ( durationMs ) } ` : '' ;
5048 process . stdout . write (
51- `Test summary: ${ data . passed } passed, ${ data . failed } failed${ flakySuffix } ${ durationMs !== undefined ? ` in ${ durationMs } ms` : '' } \n` ,
49+ `Test summary: ${ data . passed } passed, ${ data . failed } failed${ flakySuffix } ${ durationSuffix } \n` ,
5250 ) ;
51+ renderFlakyTestSummary ( flaky ) ;
5352 return getReplayTestExitCode ( data ) ;
5453}
5554
@@ -59,11 +58,10 @@ function renderVerboseTestResult(result: ReplaySuiteTestResult): void {
5958 return ;
6059 }
6160
62- const prefix = replayResultPrefix ( result ) ;
63- const attemptSuffix =
64- 'attempts' in result && result . attempts > 1 ? ` after ${ result . attempts } attempts` : '' ;
65- const durationSuffix = result . durationMs > 0 ? ` (${ result . durationMs } ms)` : '' ;
66- process . stdout . write ( `${ prefix } ${ result . file } ${ attemptSuffix } ${ durationSuffix } \n` ) ;
61+ const durationSuffix = formatReplayTestDurationSuffix ( result ) ;
62+ process . stdout . write (
63+ `${ replayResultPrefix ( result ) } ${ replayTestDisplayName ( result ) } ${ durationSuffix } \n` ,
64+ ) ;
6765 if ( result . status === 'skipped' ) {
6866 process . stdout . write ( ` ${ result . message ?? 'skipped' } \n` ) ;
6967 }
@@ -73,16 +71,16 @@ function renderFailedTestResult(
7371 result : Extract < ReplaySuiteTestResult , { status : 'failed' } > ,
7472) : void {
7573 const attemptSuffix = result . attempts > 1 ? ` after ${ result . attempts } attempts` : '' ;
76- const durationSuffix = result . durationMs > 0 ? ` ( ${ result . durationMs } ms)` : '' ;
77- process . stdout . write ( `FAIL ${ result . file } ${ attemptSuffix } ${ durationSuffix } \n` ) ;
74+ const durationSuffix = formatReplayTestDurationSuffix ( result ) ;
75+ process . stdout . write ( `FAIL ${ replayTestDisplayName ( result ) } ${ attemptSuffix } ${ durationSuffix } \n` ) ;
7876 process . stdout . write ( ` ${ result . error ?. message ?? 'Unknown test failure' } \n` ) ;
7977 for ( const line of replayFailureConsoleLines ( result ) ) {
8078 process . stdout . write ( ` ${ line } \n` ) ;
8179 }
8280}
8381
8482function replayResultPrefix ( result : ReplaySuiteTestResult ) : string {
85- if ( result . status === 'passed' ) return result . attempts > 1 ? 'FLAKY' : 'PASS' ;
83+ if ( result . status === 'passed' ) return 'PASS' ;
8684 if ( result . status === 'skipped' ) return 'SKIP' ;
8785 return 'INFO' ;
8886}
@@ -98,17 +96,64 @@ function replayFailureConsoleLines(
9896 ] . filter ( Boolean ) ;
9997}
10098
101- function renderFlakyTestResult ( result : Extract < ReplaySuiteTestResult , { status : 'passed' } > ) : void {
102- const durationSuffix = result . durationMs > 0 ? ` (${ result . durationMs } ms)` : '' ;
103- process . stdout . write ( `FLAKY ${ result . file } after ${ result . attempts } attempts${ durationSuffix } \n` ) ;
104- }
105-
10699function isFlakyReplayTestResult (
107100 result : ReplaySuiteTestResult ,
108101) : result is Extract < ReplaySuiteTestResult , { status : 'passed' } > {
109102 return result . status === 'passed' && result . attempts > 1 ;
110103}
111104
105+ function renderFlakyTestSummary (
106+ results : Array < Extract < ReplaySuiteTestResult , { status : 'passed' } > > ,
107+ ) : void {
108+ if ( results . length === 0 ) return ;
109+ process . stdout . write ( 'Flaky tests:\n' ) ;
110+ for ( const result of results ) {
111+ process . stdout . write (
112+ ` PASS ${ replayTestDisplayName ( result ) } after ${ result . attempts } attempts${ formatFlakyReplayDurationSuffix ( result ) } \n` ,
113+ ) ;
114+ for ( const failure of result . attemptFailures ?? [ ] ) {
115+ const attemptDuration =
116+ typeof failure . durationMs === 'number'
117+ ? ` (${ formatDurationSeconds ( failure . durationMs ) } )`
118+ : '' ;
119+ process . stdout . write (
120+ ` attempt ${ failure . attempt } failed${ attemptDuration } : ${ failure . message } \n` ,
121+ ) ;
122+ }
123+ }
124+ }
125+
126+ function replayTestDisplayName ( result : ReplaySuiteTestResult ) : string {
127+ return path . basename ( result . file ) ;
128+ }
129+
130+ function formatReplayTestDurationSuffix ( result : ReplaySuiteTestResult ) : string {
131+ if ( result . status === 'passed' && result . attempts > 1 ) {
132+ return formatFlakyReplayDurationSuffix ( result ) ;
133+ }
134+ if ( result . status === 'failed' && result . attempts > 1 && result . durationMs > 0 ) {
135+ return ` (total ${ formatDurationSeconds ( result . durationMs ) } )` ;
136+ }
137+
138+ const durationMs =
139+ result . status === 'passed' && typeof result . finalAttemptDurationMs === 'number'
140+ ? result . finalAttemptDurationMs
141+ : result . durationMs ;
142+ return durationMs > 0 ? ` (${ formatDurationSeconds ( durationMs ) } )` : '' ;
143+ }
144+
145+ function formatFlakyReplayDurationSuffix (
146+ result : Extract < ReplaySuiteTestResult , { status : 'passed' } > ,
147+ ) : string {
148+ const timings = [
149+ typeof result . finalAttemptDurationMs === 'number'
150+ ? `passed attempt ${ formatDurationSeconds ( result . finalAttemptDurationMs ) } `
151+ : '' ,
152+ result . durationMs > 0 ? `total ${ formatDurationSeconds ( result . durationMs ) } ` : '' ,
153+ ] . filter ( Boolean ) ;
154+ return timings . length > 0 ? ` (${ timings . join ( ', ' ) } )` : '' ;
155+ }
156+
112157function getReplayTestExitCode ( data : ReplaySuiteResult ) : number {
113158 return data . failed > 0 ? 1 : 0 ;
114159}
@@ -144,7 +189,7 @@ function buildReplayJunitXml(suite: ReplaySuiteResult): string {
144189}
145190
146191function renderJUnitTestCase ( test : ReplaySuiteTestResult ) : string [ ] {
147- const name = xmlEscape ( path . basename ( test . file ) ) ;
192+ const name = xmlEscape ( replayTestDisplayName ( test ) ) ;
148193 const className = xmlEscape (
149194 path . dirname ( test . file ) === '.' ? test . file : path . dirname ( test . file ) ,
150195 ) ;
@@ -239,6 +284,13 @@ function formatJUnitSeconds(durationMs: number): string {
239284 return ( Math . max ( 0 , durationMs ) / 1000 ) . toFixed ( 3 ) ;
240285}
241286
287+ function formatDurationSeconds ( durationMs : number ) : string {
288+ const seconds = Math . max ( 0 , durationMs ) / 1000 ;
289+ if ( seconds >= 10 ) return `${ seconds . toFixed ( 1 ) } s` ;
290+ if ( seconds >= 1 ) return `${ seconds . toFixed ( 2 ) } s` ;
291+ return `${ seconds . toFixed ( 3 ) . replace ( / 0 + $ / , '' ) . replace ( / \. $ / , '' ) } s` ;
292+ }
293+
242294function xmlEscape ( value : string ) : string {
243295 return value
244296 . replaceAll ( '&' , '&' )
0 commit comments