1- using System . Text ;
1+ using System ;
2+ using System . Collections . Generic ;
3+ using System . Linq ;
4+ using System . Text ;
5+ using System . Web ;
26
37namespace TestStack . BDDfy . Reporters . MarkDown
48{
59 public class MarkDownReportBuilder : IReportBuilder
610 {
11+ private readonly List < Exception > _exceptions = new List < Exception > ( ) ;
12+
713 public string CreateReport ( FileReportModel model )
814 {
915 var report = new StringBuilder ( ) ;
1016
1117 foreach ( var story in model . Stories )
1218 {
19+ _exceptions . Clear ( ) ;
1320 if ( story . Metadata != null )
1421 {
1522 report . AppendLine ( string . Format ( "## {0}{1}" , story . Metadata . TitlePrefix , story . Metadata . Title ) ) ;
@@ -22,19 +29,154 @@ public string CreateReport(FileReportModel model)
2229 }
2330
2431 report . AppendLine ( ) ; // separator
25-
26- foreach ( var scenario in story . Scenarios )
32+
33+ foreach ( var scenarioGroup in story . Scenarios . GroupBy ( s => s . Id ) )
2734 {
28- report . AppendLine ( string . Format ( "### {0}" , scenario . Title ) ) ;
35+ if ( scenarioGroup . Count ( ) > 1 )
36+ {
37+ // all scenarios in an example based scenario share the same header and narrative
38+ var exampleScenario = story . Scenarios . First ( ) ;
39+ report . AppendLine ( string . Format ( "### {0}" , exampleScenario . Title ) ) ;
40+
41+ if ( exampleScenario . Steps . Any ( ) )
42+ {
43+ foreach ( var step in exampleScenario . Steps . Where ( s => s . ShouldReport ) )
44+ report . AppendLine ( " " + HttpUtility . HtmlEncode ( step . Title ) + " " ) ;
45+ }
2946
30- foreach ( var step in scenario . Steps )
31- report . AppendLine ( " " + step . Title + " " ) ;
47+ report . AppendLine ( ) ; // separator
48+ WriteExamples ( report , exampleScenario , scenarioGroup ) ;
49+ }
50+ else
51+ {
52+ foreach ( var scenario in scenarioGroup )
53+ {
54+ report . AppendLine ( string . Format ( "### {0}" , scenario . Title ) ) ;
3255
33- report . AppendLine ( ) ; // separator
56+ foreach ( var step in scenario . Steps )
57+ report . AppendLine ( " " + HttpUtility . HtmlEncode ( step . Title ) + " " ) ;
58+
59+ report . AppendLine ( ) ; // separator
60+ }
61+ }
3462 }
63+
64+ ReportExceptions ( report ) ;
3565 }
66+ _exceptions . Clear ( ) ;
3667
3768 return report . ToString ( ) ;
3869 }
70+
71+ private void WriteExamples ( StringBuilder report , Scenario exampleScenario , IEnumerable < Scenario > scenarioGroup )
72+ {
73+ report . AppendLine ( "### Examples: " ) ;
74+ report . AppendLine ( ) ;
75+ var scenarios = scenarioGroup . ToArray ( ) ;
76+ var allPassed = scenarios . All ( s => s . Result == Result . Passed ) ;
77+ var exampleColumns = exampleScenario . Example . Headers . Length ;
78+ var numberColumns = allPassed ? exampleColumns : exampleColumns + 2 ;
79+ var maxWidth = new int [ numberColumns ] ;
80+ var rows = new List < string [ ] > ( ) ;
81+
82+ Action < IEnumerable < string > , string , string > addRow = ( cells , result , error ) =>
83+ {
84+ var row = new string [ numberColumns ] ;
85+ var index = 0 ;
86+
87+ foreach ( var cellText in cells )
88+ row [ index ++ ] = cellText ;
89+
90+ if ( ! allPassed )
91+ {
92+ row [ numberColumns - 2 ] = result ;
93+ row [ numberColumns - 1 ] = error ;
94+ }
95+
96+ for ( var i = 0 ; i < numberColumns ; i ++ )
97+ {
98+ var rowValue = row [ i ] ;
99+ if ( rowValue != null && rowValue . Length > maxWidth [ i ] )
100+ maxWidth [ i ] = rowValue . Length ;
101+ }
102+
103+ rows . Add ( row ) ;
104+ } ;
105+
106+ addRow ( exampleScenario . Example . Headers , "Result" , "Errors" ) ;
107+ foreach ( var scenario in scenarios )
108+ {
109+ var failingStep = scenario . Steps . FirstOrDefault ( s => s . Result == Result . Failed ) ;
110+ var error = failingStep == null
111+ ? null
112+ : string . Format ( "Step: {0} failed with exception: {1}" , HttpUtility . HtmlEncode ( failingStep . Title ) , CreateExceptionMessage ( failingStep ) ) ;
113+
114+ addRow ( scenario . Example . Values . Select ( e => e . GetValueAsString ( ) ) , scenario . Result . ToString ( ) , error ) ;
115+ }
116+
117+ foreach ( var row in rows )
118+ WriteExampleRow ( report , row , maxWidth ) ;
119+ }
120+
121+ private void WriteExampleRow ( StringBuilder report , string [ ] row , int [ ] maxWidth )
122+ {
123+ report . Append ( " " ) ;
124+ for ( int index = 0 ; index < row . Length ; index ++ )
125+ {
126+ var col = row [ index ] ;
127+ report . AppendFormat ( "| {0} " , ( col ?? string . Empty ) . Trim ( ) . PadRight ( maxWidth [ index ] ) ) ;
128+ }
129+ report . AppendLine ( "|" ) ;
130+ }
131+
132+ private string CreateExceptionMessage ( Step step )
133+ {
134+ _exceptions . Add ( step . Exception ) ;
135+
136+ var exceptionReference = string . Format ( "[Details at {0} below]" , _exceptions . Count ) ;
137+ if ( ! string . IsNullOrEmpty ( step . Exception . Message ) )
138+ return string . Format ( "[{0}] {1}" , FlattenExceptionMessage ( step . Exception . Message ) , exceptionReference ) ;
139+
140+ return string . Format ( "{0}" , exceptionReference ) ;
141+ }
142+
143+ void ReportExceptions ( StringBuilder report )
144+ {
145+ if ( _exceptions . Count == 0 )
146+ return ;
147+
148+ report . AppendLine ( ) ;
149+ report . Append ( "#### Exceptions:" ) ;
150+
151+ for ( int index = 0 ; index < _exceptions . Count ; index ++ )
152+ {
153+ var exception = _exceptions [ index ] ;
154+ report . AppendLine ( ) ;
155+ report . AppendFormat ( " {0}. " , index + 1 ) ;
156+
157+ if ( ! string . IsNullOrEmpty ( exception . Message ) )
158+ {
159+ report . AppendLine ( FlattenExceptionMessage ( exception . Message ) ) ;
160+ }
161+ else
162+ report . AppendLine ( ) ;
163+
164+ var stackTrace = string . Join ( Environment . NewLine , exception . StackTrace . Split ( new [ ] { Environment . NewLine } , StringSplitOptions . None )
165+ . Select ( s => " " + s ) ) ;
166+ report . AppendLine ( stackTrace ) ;
167+ }
168+
169+ report . AppendLine ( ) ;
170+ }
171+
172+ static string FlattenExceptionMessage ( string message )
173+ {
174+ return message
175+ . Replace ( "\t " , " " ) // replace tab with one space
176+ . Replace ( Environment . NewLine , ", " ) // replace new line with one space
177+ . Trim ( ) // trim starting and trailing spaces
178+ . Replace ( " " , " " )
179+ . TrimEnd ( ',' ) ; // chop any , from the end
180+ }
39181 }
40182}
0 commit comments