1- import { Envelope } from '@cucumber/messages'
1+ import {
2+ Envelope ,
3+ Location ,
4+ Pickle ,
5+ TestCaseFinished ,
6+ TestCaseStarted ,
7+ TestRunFinished ,
8+ TestRunStarted ,
9+ TestStepResult ,
10+ TestStepResultStatus ,
11+ } from '@cucumber/messages'
212import { Query } from '@cucumber/query'
313
14+ import {
15+ ensure ,
16+ ERROR_INDENT_LENGTH ,
17+ formatCounts ,
18+ formatDuration ,
19+ formatNonPassingTitle ,
20+ formatPickleLocation ,
21+ formatTestStepResultError ,
22+ GHERKIN_INDENT_LENGTH ,
23+ indent ,
24+ } from './helpers.js'
425import type { Options } from './types.js'
526
627export class SummaryPrinter {
@@ -19,7 +40,189 @@ export class SummaryPrinter {
1940 this . query . update ( message )
2041
2142 if ( message . testRunFinished ) {
22- this . println ( 'Summary!' )
43+ this . printSummary ( )
44+ }
45+ }
46+
47+ private printSummary ( ) {
48+ this . printNonPassingScenarios ( )
49+ this . printStats ( )
50+ this . printSnippets ( )
51+ }
52+
53+ private printStats ( ) {
54+ this . println ( )
55+ this . printGlobalHookCounts ( )
56+ this . printScenarioCounts ( )
57+ this . printStepCounts ( )
58+ this . printDuration ( )
59+ }
60+
61+ private printNonPassingScenarios ( ) {
62+ const theOrder : TestStepResultStatus [ ] = [
63+ TestStepResultStatus . UNKNOWN ,
64+ TestStepResultStatus . PENDING ,
65+ TestStepResultStatus . UNDEFINED ,
66+ TestStepResultStatus . AMBIGUOUS ,
67+ TestStepResultStatus . FAILED ,
68+ ]
69+
70+ const picklesByStatus = new Map <
71+ TestStepResultStatus ,
72+ Array < {
73+ pickle : Pickle
74+ location : Location | undefined
75+ testCaseStarted : TestCaseStarted
76+ testCaseFinished : TestCaseFinished
77+ testStepResult : TestStepResult
78+ } >
79+ > ( )
80+
81+ for ( const testCaseFinished of this . query . findAllTestCaseFinished ( ) ) {
82+ const testCaseStarted = ensure (
83+ this . query . findTestCaseStartedBy ( testCaseFinished ) ,
84+ 'TestCaseStarted must exist for TestCaseFinished'
85+ )
86+ const pickle = ensure (
87+ this . query . findPickleBy ( testCaseFinished ) ,
88+ 'Pickle must exist for TestCaseFinished'
89+ )
90+ const location = this . query . findLocationOf ( pickle )
91+ const testStepResult = this . query . findMostSevereTestStepResultBy ( testCaseFinished )
92+ if ( testStepResult ) {
93+ if ( ! picklesByStatus . has ( testStepResult . status ) ) {
94+ picklesByStatus . set ( testStepResult . status , [ ] )
95+ }
96+ picklesByStatus . get ( testStepResult . status ) ! . push ( {
97+ pickle,
98+ location,
99+ testCaseStarted,
100+ testCaseFinished,
101+ testStepResult,
102+ } )
103+ }
104+ }
105+
106+ for ( const status of theOrder ) {
107+ const picklesForThisStatus = picklesByStatus . get ( status ) ?? [ ]
108+ if ( picklesForThisStatus . length > 0 ) {
109+ this . println ( )
110+ this . println ( formatNonPassingTitle ( status , this . options . theme , this . stream ) )
111+ picklesForThisStatus . forEach (
112+ ( { pickle, location, testCaseStarted, testStepResult } , index ) => {
113+ const formattedLocation = formatPickleLocation (
114+ pickle ,
115+ location ,
116+ this . options . theme ,
117+ this . stream
118+ )
119+ const formattedAttempt =
120+ testCaseStarted . attempt > 0 ? `, after ${ testCaseStarted . attempt + 1 } attempts` : ''
121+ this . println (
122+ indent (
123+ `${ index + 1 } ) ${ pickle . name } ${ formattedAttempt } ${ formattedLocation } ` ,
124+ GHERKIN_INDENT_LENGTH
125+ )
126+ )
127+ if ( status === TestStepResultStatus . FAILED ) {
128+ const content = formatTestStepResultError (
129+ testStepResult ,
130+ this . options . theme ,
131+ this . stream
132+ )
133+ if ( content ) {
134+ this . println ( indent ( content , GHERKIN_INDENT_LENGTH + ERROR_INDENT_LENGTH + 1 ) )
135+ this . println ( )
136+ }
137+ }
138+ }
139+ )
140+ }
141+ }
142+ }
143+
144+ private printGlobalHookCounts ( ) {
145+ const testRunHookFinished = this . query . findAllTestRunHookFinished ( )
146+ if ( testRunHookFinished . length === 0 ) {
147+ return
148+ }
149+
150+ const globalHookCountsByStatus = testRunHookFinished
151+ . map ( ( testRunHookFinished ) => testRunHookFinished . result . status )
152+ . reduce (
153+ ( prev , status ) => {
154+ return {
155+ ...prev ,
156+ [ status ] : ( prev [ status ] ?? 0 ) + 1 ,
157+ }
158+ } ,
159+ { } as Partial < Record < TestStepResultStatus , number > >
160+ )
161+ this . println ( formatCounts ( 'hooks' , globalHookCountsByStatus , this . options . theme , this . stream ) )
162+ }
163+
164+ private printScenarioCounts ( ) {
165+ const scenarioCountsByStatus = this . query
166+ . findAllTestCaseFinished ( )
167+ . map ( ( testCaseFinished ) => this . query . findMostSevereTestStepResultBy ( testCaseFinished ) )
168+ . map ( ( testStepResult ) => testStepResult ?. status ?? TestStepResultStatus . PASSED )
169+ . reduce (
170+ ( prev , status ) => {
171+ return {
172+ ...prev ,
173+ [ status ] : ( prev [ status ] ?? 0 ) + 1 ,
174+ }
175+ } ,
176+ { } as Partial < Record < TestStepResultStatus , number > >
177+ )
178+ this . println ( formatCounts ( 'scenarios' , scenarioCountsByStatus , this . options . theme , this . stream ) )
179+ }
180+
181+ private printStepCounts ( ) {
182+ const stepCountsByStatus = this . query
183+ . findAllTestCaseFinished ( )
184+ . flatMap ( ( testCaseFinished ) => this . query . findTestStepsFinishedBy ( testCaseFinished ) )
185+ . map ( ( testStepFinished ) => testStepFinished . testStepResult . status )
186+ . reduce (
187+ ( prev , status ) => {
188+ return {
189+ ...prev ,
190+ [ status ] : ( prev [ status ] ?? 0 ) + 1 ,
191+ }
192+ } ,
193+ { } as Partial < Record < TestStepResultStatus , number > >
194+ )
195+ this . println ( formatCounts ( 'steps' , stepCountsByStatus , this . options . theme , this . stream ) )
196+ }
197+
198+ private printDuration ( ) {
199+ const testRunStarted = this . query . findTestRunStarted ( ) as TestRunStarted
200+ const testRunFinished = this . query . findTestRunFinished ( ) as TestRunFinished
201+
202+ this . println ( formatDuration ( testRunStarted . timestamp , testRunFinished . timestamp ) )
203+ }
204+
205+ private printSnippets ( ) {
206+ const snippets = this . query
207+ . findAllTestCaseFinished ( )
208+ . map ( ( testCaseFinished ) => this . query . findPickleBy ( testCaseFinished ) )
209+ . filter ( ( pickle ) => ! ! pickle )
210+ . sort ( ( a , b ) => {
211+ // TODO compare by location too
212+ return a ?. uri . localeCompare ( b ?. uri || '' ) || 0
213+ } )
214+ . flatMap ( ( pickle ) => this . query . findSuggestionsBy ( pickle ) )
215+ . flatMap ( ( suggestion ) => suggestion . snippets )
216+ . map ( ( snippet ) => snippet . code )
217+ const uniqueSnippets = new Set ( snippets )
218+ if ( uniqueSnippets . size > 0 ) {
219+ this . println ( )
220+ this . println ( 'You can implement missing steps with the snippets below:' )
221+ this . println ( )
222+ for ( const snippet of uniqueSnippets ) {
223+ this . println ( snippet )
224+ this . println ( )
225+ }
23226 }
24227 }
25228}
0 commit comments