@@ -33,6 +33,7 @@ export interface MaestroFlowInfo {
3333 status : MaestroFlowStatus ;
3434 success ?: number ;
3535 test_case_id ?: number ;
36+ error_messages ?: string [ ] ;
3637}
3738
3839export interface MaestroRunInfo {
@@ -48,6 +49,7 @@ export interface MaestroRunInfo {
4849 options ?: Record < string , unknown > ;
4950 assets ?: MaestroRunAssets ;
5051 flows ?: MaestroFlowInfo [ ] ;
52+ error_messages ?: string [ ] ;
5153}
5254
5355export interface MaestroRunDetails extends MaestroRunInfo {
@@ -869,7 +871,6 @@ export default class Maestro {
869871 // Check for version update notification
870872 const latestVersion = response . headers ?. [ 'x-testingbotctl-version' ] ;
871873 utils . checkForUpdate ( latestVersion ) ;
872-
873874 return response . data ;
874875 } catch ( error ) {
875876 throw new TestingBotError ( `Failed to get Maestro test status` , {
@@ -924,11 +925,14 @@ export default class Maestro {
924925 }
925926
926927 if ( allFlows . length > 0 ) {
928+ // Check if any flow has failed (for showing error column)
929+ const hasFailures = this . hasAnyFlowFailed ( allFlows ) ;
930+
927931 if ( ! flowsTableDisplayed ) {
928932 // First time showing flows - display header and initial state
929933 console . log ( ) ; // Empty line before flows table
930- this . displayFlowsTableHeader ( ) ;
931- displayedLineCount = this . displayFlowsWithLimit ( allFlows , previousFlowStatus ) ;
934+ this . displayFlowsTableHeader ( hasFailures ) ;
935+ displayedLineCount = this . displayFlowsWithLimit ( allFlows , previousFlowStatus , hasFailures ) ;
932936 flowsTableDisplayed = true ;
933937 } else {
934938 // Update flows in place
@@ -941,6 +945,26 @@ export default class Maestro {
941945 }
942946
943947 if ( status . completed ) {
948+ // Display final flows table with error messages if there are failures
949+ if ( ! this . options . quiet && flowsTableDisplayed ) {
950+ const allFlows : MaestroFlowInfo [ ] = [ ] ;
951+ for ( const run of status . runs ) {
952+ if ( run . flows && run . flows . length > 0 ) {
953+ allFlows . push ( ...run . flows ) ;
954+ }
955+ }
956+
957+ const hasFailures = this . hasAnyFlowFailed ( allFlows ) ;
958+ if ( hasFailures ) {
959+ // Clear previous in-place display and redraw with error messages
960+ console . log ( ) ; // Empty line before final table
961+ this . displayFlowsTableHeader ( true ) ;
962+ for ( const flow of allFlows ) {
963+ this . displayFlowRow ( flow , false , true ) ;
964+ }
965+ }
966+ }
967+
944968 // Print final summary
945969 if ( ! this . options . quiet ) {
946970 console . log ( ) ; // Empty line before summary
@@ -963,12 +987,7 @@ export default class Maestro {
963987 }
964988 } else {
965989 const failedRuns = status . runs . filter ( ( run ) => run . success !== 1 ) ;
966- logger . error ( `${ failedRuns . length } test run(s) failed:` ) ;
967- for ( const run of failedRuns ) {
968- logger . error (
969- ` - Run ${ run . id } (${ run . capabilities . deviceName } ): ${ run . report } ` ,
970- ) ;
971- }
990+ logger . error ( `${ failedRuns . length } test run(s) failed` ) ;
972991 }
973992
974993 if ( this . options . report && this . options . reportOutputDir ) {
@@ -1082,6 +1101,15 @@ export default class Maestro {
10821101 }
10831102 }
10841103
1104+ private hasAnyFlowFailed ( flows : MaestroFlowInfo [ ] ) : boolean {
1105+ return flows . some (
1106+ ( flow ) =>
1107+ ( flow . status === 'DONE' && flow . success !== 1 ) ||
1108+ flow . status === 'FAILED' ||
1109+ ( flow . error_messages && flow . error_messages . length > 0 ) ,
1110+ ) ;
1111+ }
1112+
10851113 private calculateFlowDuration ( flow : MaestroFlowInfo ) : string {
10861114 if ( ! flow . requested_at ) {
10871115 return '-' ;
@@ -1160,15 +1188,15 @@ export default class Maestro {
11601188 private displayFlowsWithLimit (
11611189 flows : MaestroFlowInfo [ ] ,
11621190 previousFlowStatus : Map < number , MaestroFlowStatus > ,
1191+ hasFailures : boolean = false ,
11631192 ) : number {
11641193 const maxFlows = this . getMaxDisplayableFlows ( ) ;
11651194 const displayFlows = flows . slice ( 0 , maxFlows ) ;
11661195 let linesWritten = 0 ;
11671196
11681197 for ( const flow of displayFlows ) {
1169- this . displayFlowRow ( flow , false ) ;
1198+ linesWritten += this . displayFlowRow ( flow , false , hasFailures ) ;
11701199 previousFlowStatus . set ( flow . id , flow . status ) ;
1171- linesWritten ++ ;
11721200 }
11731201
11741202 // Show summary for remaining flows
@@ -1181,37 +1209,70 @@ export default class Maestro {
11811209 return linesWritten ;
11821210 }
11831211
1184- private displayFlowsTableHeader ( ) : void {
1185- const header = ` ${ 'Duration' . padEnd ( 10 ) } ${ 'Status' . padEnd ( 8 ) } Test` ;
1186- const separator = ` ${ '─' . repeat ( 10 ) } ${ '─' . repeat ( 8 ) } ${ '─' . repeat ( 40 ) } ` ;
1212+ private displayFlowsTableHeader ( hasFailures : boolean = false ) : void {
1213+ let header = ` ${ 'Duration' . padEnd ( 10 ) } ${ 'Status' . padEnd ( 8 ) } Test` ;
1214+ let separator = ` ${ '─' . repeat ( 10 ) } ${ '─' . repeat ( 8 ) } ${ '─' . repeat ( 30 ) } ` ;
1215+
1216+ if ( hasFailures ) {
1217+ header += ' Fail reason' ;
1218+ separator += ` ${ '─' . repeat ( 80 ) } ` ;
1219+ }
1220+
11871221 console . log ( colors . dim ( header ) ) ;
11881222 console . log ( colors . dim ( separator ) ) ;
11891223 }
11901224
1191- private displayFlowRow ( flow : MaestroFlowInfo , isUpdate : boolean = false ) : void {
1225+ private displayFlowRow (
1226+ flow : MaestroFlowInfo ,
1227+ isUpdate : boolean = false ,
1228+ hasFailures : boolean = false ,
1229+ ) : number {
11921230 const duration = this . calculateFlowDuration ( flow ) . padEnd ( 10 ) ;
11931231 const statusDisplay = this . getFlowStatusDisplay ( flow ) ;
11941232 // Pad based on display text length, add extra for color codes
11951233 const statusPadded = statusDisplay . colored + ' ' . repeat ( Math . max ( 0 , 8 - statusDisplay . text . length ) ) ;
1196- const name = flow . name ;
1234+ const name = flow . name . padEnd ( 30 ) ;
11971235
1198- const row = ` ${ duration } ${ statusPadded } ${ name } ` ;
1236+ let linesWritten = 0 ;
1237+ const isFailed = flow . status === 'DONE' && flow . success !== 1 ;
1238+ const errorMessages = flow . error_messages || [ ] ;
1239+
1240+ // Build the main row
1241+ let row = ` ${ duration } ${ statusPadded } ${ name } ` ;
1242+
1243+ // Add first error message on the same line if failed and has errors
1244+ if ( hasFailures && isFailed && errorMessages . length > 0 ) {
1245+ row += ` ${ colors . red ( errorMessages [ 0 ] ) } ` ;
1246+ }
11991247
12001248 if ( isUpdate ) {
1201- // Move cursor up and clear line before writing
12021249 process . stdout . write ( `\r${ row } ` ) ;
12031250 } else {
12041251 console . log ( row ) ;
12051252 }
1253+ linesWritten ++ ;
1254+
1255+ // Display remaining error messages on continuation lines
1256+ if ( ! isUpdate && hasFailures && isFailed && errorMessages . length > 1 ) {
1257+ // Indent to align with the Fail reason column: Duration(11) + Status(9) + Test(31) = 51 chars
1258+ const indent = ' ' . repeat ( 51 ) ;
1259+ for ( let i = 1 ; i < errorMessages . length ; i ++ ) {
1260+ console . log ( `${ indent } ${ colors . red ( errorMessages [ i ] ) } ` ) ;
1261+ linesWritten ++ ;
1262+ }
1263+ }
1264+
1265+ return linesWritten ;
12061266 }
12071267
12081268 private displayFlowsTable (
12091269 flows : MaestroFlowInfo [ ] ,
12101270 previousFlowStatus : Map < number , MaestroFlowStatus > ,
12111271 showHeader : boolean ,
1272+ hasFailures : boolean = false ,
12121273 ) : number {
12131274 if ( showHeader ) {
1214- this . displayFlowsTableHeader ( ) ;
1275+ this . displayFlowsTableHeader ( hasFailures ) ;
12151276 }
12161277
12171278 let linesWritten = 0 ;
@@ -1221,8 +1282,7 @@ export default class Maestro {
12211282 const isNewFlow = prevStatus === undefined ;
12221283
12231284 if ( isNewFlow ) {
1224- this . displayFlowRow ( flow , false ) ;
1225- linesWritten ++ ;
1285+ linesWritten += this . displayFlowRow ( flow , false , hasFailures ) ;
12261286 }
12271287
12281288 previousFlowStatus . set ( flow . id , flow . status ) ;
0 commit comments