0
# Reporting and HTML
1
2
Specs2 provides a flexible reporting system with multiple output formats including console, HTML, Markdown, and JUnit XML. The reporting system is designed to generate both human-readable documentation and machine-parseable results for continuous integration.
3
4
## Core Reporting
5
6
### Reporter
7
8
Base interface for all reporting functionality with lifecycle management.
9
10
```scala { .api }
11
trait Reporter {
12
def prepare(env: Env, printers: List[Printer]): List[SpecificationStructure] => Action[Unit]
13
def report(env: Env, printers: List[Printer]): SpecStructure => Action[Unit]
14
def finalize(env: Env, printers: List[Printer]): List[SpecificationStructure] => Action[Unit]
15
}
16
```
17
18
The Reporter coordinates the complete reporting lifecycle:
19
- **Prepare**: Initialize printers and set up reporting infrastructure
20
- **Report**: Process individual specification structures
21
- **Finalize**: Complete reporting, generate indexes, and cleanup resources
22
23
### Printer
24
25
Base interface for output formatting with fold-based processing.
26
27
```scala { .api }
28
trait Printer {
29
def prepare(env: Env, specifications: List[SpecificationStructure]): Action[Unit]
30
def finalize(env: Env, specifications: List[SpecificationStructure]): Action[Unit]
31
def fold(env: Env, spec: SpecStructure): Fold[Fragment]
32
}
33
```
34
35
The Printer uses a fold-based approach where:
36
- **Prepare**: Set up output files, directories, and resources
37
- **Fold**: Process fragments incrementally using functional fold operations
38
- **Finalize**: Complete output generation and cleanup
39
40
### Fold-Based Processing
41
42
```scala { .api }
43
trait Fold[T] {
44
def start: T
45
def fold(previous: T, fragment: Fragment): T
46
def end(final: T): T
47
}
48
```
49
50
This enables:
51
- **Incremental processing**: Process fragments one at a time
52
- **Memory efficiency**: Constant memory usage regardless of specification size
53
- **Streaming output**: Generate output as fragments are processed
54
55
## Console Reporting
56
57
### TextPrinter
58
59
Formats output for console display with colors and formatting.
60
61
```scala { .api }
62
trait TextPrinter extends Printer {
63
def printText(text: String): Action[Unit]
64
def printExample(example: Example): Action[Unit]
65
def printResult(result: Result): Action[Unit]
66
def printStatistics(stats: Statistics): Action[Unit]
67
}
68
```
69
70
**Console Output Example:**
71
```
72
Calculator specification
73
74
Calculator should
75
+ add two numbers correctly
76
+ subtract two numbers correctly
77
x handle division by zero
78
'10 / 0' doesn't throw a java.lang.ArithmeticException
79
80
Total for specification Calculator:
81
Finished in 23 ms
82
3 examples, 1 failure, 0 error
83
```
84
85
### Colors and Formatting
86
87
```scala { .api }
88
trait AnsiColors {
89
def green(text: String): String
90
def red(text: String): String
91
def blue(text: String): String
92
def yellow(text: String): String
93
def cyan(text: String): String
94
def white(text: String): String
95
def bold(text: String): String
96
}
97
```
98
99
**Usage:**
100
```scala
101
// Enable/disable colors
102
Arguments(colors = true) // Enable colors (default)
103
Arguments(colors = false) // Disable colors
104
105
// Command line
106
sbt "testOnly * -- colors" # Enable colors
107
sbt "testOnly * -- noColors" # Disable colors
108
```
109
110
## HTML Reporting
111
112
### HtmlTemplate
113
114
Generates complete HTML pages for specifications.
115
116
```scala { .api }
117
trait HtmlTemplate {
118
def page(spec: SpecificationStructure, fragments: Fragments): NodeSeq
119
def head(spec: SpecificationStructure): NodeSeq
120
def body(spec: SpecificationStructure, fragments: Fragments): NodeSeq
121
def css: String
122
def javascript: String
123
}
124
```
125
126
### SpecHtmlPage
127
128
Individual specification HTML page generation.
129
130
```scala { .api }
131
class SpecHtmlPage {
132
def generate(spec: SpecificationStructure): Action[Unit]
133
def generateToFile(spec: SpecurationStructure, file: File): Action[Unit]
134
def toXml(spec: SpecificationStructure): NodeSeq
135
}
136
```
137
138
**HTML Generation:**
139
```bash
140
# Generate HTML reports
141
sbt "testOnly * -- html"
142
143
# Specify output directory
144
sbt "testOnly * -- html outdir target/html-reports"
145
146
# Generate with custom CSS
147
sbt "testOnly * -- html css custom-styles.css"
148
```
149
150
### HTML Features
151
152
Generated HTML reports include:
153
154
- **Syntax highlighting** for code examples
155
- **Collapsible sections** for large specifications
156
- **Navigation menu** for multi-specification reports
157
- **Search functionality** for finding specific tests
158
- **Statistics summaries** with pass/fail counts
159
- **Execution timing** information
160
- **Failure details** with stack traces
161
162
### Htmlx
163
164
HTML utilities and transformations.
165
166
```scala { .api }
167
object Htmlx {
168
def render(nodes: NodeSeq): String
169
def toXhtml(html: String): NodeSeq
170
def format(nodes: NodeSeq): NodeSeq
171
def addCss(css: String): NodeSeq => NodeSeq
172
def addJavascript(js: String): NodeSeq => NodeSeq
173
}
174
```
175
176
## Advanced HTML Features
177
178
### TableOfContents
179
180
Navigation structure generation for multi-specification reports.
181
182
```scala { .api }
183
trait TableOfContents {
184
def create(specs: List[SpecificationStructure]): NodeSeq
185
def createEntry(spec: SpecificationStructure): NodeSeq
186
def createSection(title: String, specs: List[SpecificationStructure]): NodeSeq
187
}
188
```
189
190
### Indexing
191
192
Cross-reference and linking between specification pages.
193
194
```scala { .api }
195
trait Indexing {
196
def createIndex(specs: List[SpecificationStructure]): NodeSeq
197
def createLinks(spec: SpecificationStructure): Map[String, String]
198
def resolveReferences(html: NodeSeq): NodeSeq
199
}
200
```
201
202
**Multi-Specification HTML Report:**
203
```scala
204
// Generate linked HTML reports for multiple specifications
205
val specs = List(new UserSpec, new OrderSpec, new PaymentSpec)
206
HtmlReporter.generateSuite(specs, "target/html-reports")
207
```
208
209
## Markdown Reporting
210
211
### MarkdownPrinter
212
213
Generates Markdown documentation from specifications.
214
215
```scala { .api }
216
trait MarkdownPrinter extends Printer {
217
def printMarkdown(fragments: Fragments): Action[Unit]
218
def printTitle(title: String): Action[Unit]
219
def printSection(section: String): Action[Unit]
220
def printCodeBlock(code: String): Action[Unit]
221
def printTable(headers: List[String], rows: List[List[String]]): Action[Unit]
222
}
223
```
224
225
**Markdown Generation:**
226
```bash
227
# Generate Markdown reports
228
sbt "testOnly * -- markdown"
229
230
# Specify output file
231
sbt "testOnly * -- markdown outfile README.md"
232
```
233
234
**Generated Markdown Example:**
235
```markdown
236
# Calculator Specification
237
238
Calculator should
239
240
- ✓ add two numbers correctly
241
- ✓ subtract two numbers correctly
242
- ✗ handle division by zero
243
244
```
245
'10 / 0' doesn't throw a java.lang.ArithmeticException
246
```
247
248
## Statistics
249
250
- **Total**: 3 examples
251
- **Passed**: 2
252
- **Failed**: 1
253
- **Errors**: 0
254
- **Execution time**: 23ms
255
```
256
257
## JUnit XML Reporting
258
259
### JUnitXmlPrinter
260
261
Generates JUnit-compatible XML reports for CI integration.
262
263
```scala { .api }
264
trait JUnitXmlPrinter extends Printer {
265
def printXml(specs: List[SpecificationStructure]): Action[Unit]
266
def printTestSuite(spec: SpecificationStructure): NodeSeq
267
def printTestCase(example: Example): NodeSeq
268
def printFailure(failure: Result): NodeSeq
269
def printError(error: Result): NodeSeq
270
}
271
```
272
273
**JUnit XML Generation:**
274
```bash
275
# Generate JUnit XML reports
276
sbt "testOnly * -- junitxml"
277
278
# Specify output directory
279
sbt "testOnly * -- junitxml outdir target/test-reports"
280
```
281
282
**Generated XML Structure:**
283
```xml
284
<?xml version="1.0" encoding="UTF-8"?>
285
<testsuite name="CalculatorSpec" tests="3" failures="1" errors="0" time="0.023">
286
<testcase name="add two numbers correctly" classname="CalculatorSpec" time="0.005"/>
287
<testcase name="subtract two numbers correctly" classname="CalculatorSpec" time="0.008"/>
288
<testcase name="handle division by zero" classname="CalculatorSpec" time="0.010">
289
<failure message="Expected exception not thrown" type="assertion">
290
'10 / 0' doesn't throw a java.lang.ArithmeticException
291
</failure>
292
</testcase>
293
</testsuite>
294
```
295
296
## Custom Reporting
297
298
### SbtPrinter
299
300
SBT-specific output formatting for build tool integration.
301
302
```scala { .api }
303
trait SbtPrinter extends Printer {
304
def printSbtResult(result: Result, testName: String): Action[Unit]
305
def printSbtSummary(stats: Statistics): Action[Unit]
306
}
307
```
308
309
### NotifierPrinter
310
311
Integration with test framework notifiers for IDE support.
312
313
```scala { .api }
314
trait NotifierPrinter extends Printer {
315
def notifyStart(testName: String): Action[Unit]
316
def notifyEnd(testName: String, result: Result): Action[Unit]
317
def notifyError(testName: String, error: Throwable): Action[Unit]
318
}
319
```
320
321
### Custom Printers
322
323
Create custom output formats:
324
325
```scala { .api }
326
trait CustomPrinter extends Printer {
327
def format(fragments: Fragments): String
328
def writeToFile(content: String, file: File): Action[Unit]
329
}
330
```
331
332
**Example Custom Printer:**
333
```scala
334
class JsonPrinter extends CustomPrinter {
335
def print(fragments: Fragments): Action[Unit] = {
336
val json = fragments.fragments.map {
337
case Example(desc, result) =>
338
s"""{"description":"$desc","status":"${result.status}"}"""
339
case Text(text) =>
340
s"""{"type":"text","content":"$text"}"""
341
}.mkString("[", ",", "]")
342
343
writeToFile(json, new File("target/results.json"))
344
}
345
}
346
```
347
348
## Forms and Advanced Reporting
349
350
### Form
351
352
Structured data validation and reporting with tabular output.
353
354
```scala { .api }
355
case class Form(
356
title: String = "",
357
rows: List[Row] = Nil
358
) {
359
def tr(row: Row): Form
360
def th(cells: Cell*): Form
361
def td(cells: Cell*): Form
362
}
363
```
364
365
### Field
366
367
Individual form fields with validation.
368
369
```scala { .api }
370
case class Field[T](
371
label: String,
372
value: T,
373
expected: Option[T] = None
374
) {
375
def apply(actual: T): Field[T]
376
def must(matcher: Matcher[T]): Field[T]
377
}
378
```
379
380
**Form Usage Example:**
381
```scala
382
class FormReportSpec extends Specification { def is = s2"""
383
User validation form
384
$userValidationForm
385
"""
386
387
def userValidationForm = Form("User Validation").
388
tr(Field("Name", user.name).must(not(beEmpty))).
389
tr(Field("Email", user.email).must(beMatching(emailRegex))).
390
tr(Field("Age", user.age).must(beGreaterThan(0)))
391
}
392
```
393
394
### Cards
395
396
Card-based reporting layout for structured data presentation.
397
398
```scala { .api }
399
trait Cards {
400
def card(title: String): Card
401
def toTabs: Tabs
402
}
403
404
case class Card(
405
title: String,
406
body: Fragments
407
) {
408
def show: Fragment
409
}
410
```
411
412
### Tabs
413
414
Tabbed interface for organizing related content.
415
416
```scala { .api }
417
trait Tabs {
418
def tab(title: String, content: Fragments): Tab
419
def show: Fragment
420
}
421
422
case class Tab(
423
title: String,
424
content: Fragments,
425
active: Boolean = false
426
) {
427
def activate: Tab
428
}
429
```
430
431
## Statistics and Metrics
432
433
### Statistics
434
435
Execution statistics collection and reporting.
436
437
```scala { .api }
438
case class Statistics(
439
examples: Int = 0,
440
successes: Int = 0,
441
failures: Int = 0,
442
errors: Int = 0,
443
pending: Int = 0,
444
skipped: Int = 0,
445
trend: Option[Statistics] = None
446
) {
447
def total: Int = examples
448
def hasFailures: Boolean = failures > 0
449
def hasErrors: Boolean = errors > 0
450
def isSuccess: Boolean = !hasFailures && !hasErrors
451
}
452
```
453
454
### StatisticsRepository
455
456
Repository for persisting and retrieving execution statistics across test runs.
457
458
```scala { .api }
459
trait StatisticsRepository {
460
def store(spec: SpecificationStructure, stats: Statistics): Action[Unit]
461
def retrieve(spec: SpecificationStructure): Action[Option[Statistics]]
462
def clear(spec: SpecificationStructure): Action[Unit]
463
}
464
465
object StatisticsRepository {
466
def memory: StatisticsRepository
467
def file(path: String): StatisticsRepository
468
}
469
```
470
471
**Usage:**
472
- **Memory repository**: Statistics stored in memory for single test run
473
- **File repository**: Statistics persisted to disk for trend analysis
474
- **Trend analysis**: Compare current results with historical data
475
476
### Timing Information
477
478
Execution timing and performance metrics.
479
480
```scala { .api }
481
case class ExecutionTime(
482
duration: Duration,
483
timestamp: Long = System.currentTimeMillis
484
) {
485
def formatted: String
486
def inMillis: Long
487
def inSeconds: Double
488
}
489
```
490
491
**Enable Timing:**
492
```bash
493
# Show execution times in console
494
sbt "testOnly * -- showTimes"
495
496
# Include timing in HTML reports
497
sbt "testOnly * -- html showTimes"
498
```
499
500
## Report Configuration
501
502
### Output Directory Configuration
503
504
```bash
505
# Set output directory for all reports
506
sbt "testOnly * -- outdir target/custom-reports"
507
508
# HTML reports in specific directory
509
sbt "testOnly * -- html outdir target/html"
510
511
# Multiple formats with same base directory
512
sbt "testOnly * -- html markdown junitxml outdir target/reports"
513
```
514
515
### Report Templates
516
517
Customize HTML report appearance:
518
519
```scala
520
// Custom CSS
521
Arguments(html = true, css = "custom-styles.css")
522
523
// Custom JavaScript
524
Arguments(html = true, javascript = "custom-behavior.js")
525
526
// Custom template
527
Arguments(html = true, template = "custom-template.html")
528
```
529
530
## Integration Examples
531
532
### CI/CD Pipeline Integration
533
534
```yaml
535
# GitHub Actions example
536
- name: Run tests with reports
537
run: sbt "test -- junitxml html outdir target/test-reports"
538
539
- name: Publish test results
540
uses: dorny/test-reporter@v1
541
with:
542
name: Specs2 Tests
543
path: target/test-reports/*.xml
544
reporter: java-junit
545
```
546
547
### Documentation Generation
548
549
```scala
550
class DocumentationSpec extends Specification { def is =
551
args(html = true, markdown = true) ^ s2"""
552
553
# API Documentation
554
555
This specification serves as both tests and documentation.
556
557
## User Management API
558
559
The user management system provides the following capabilities:
560
561
### User Creation
562
563
create valid user $createValidUser
564
validate required fields $validateRequired
565
handle duplicate emails $handleDuplicates
566
567
### User Authentication
568
569
authenticate with valid credentials $validAuth
570
reject invalid credentials $invalidAuth
571
"""
572
```
573
574
## Best Practices
575
576
1. **Choose appropriate formats**: Console for development, HTML for documentation, JUnit XML for CI
577
2. **Organize output**: Use consistent directory structures for different report types
578
3. **Customize for audience**: Different styling and detail levels for different consumers
579
4. **Include timing**: Monitor performance trends with execution timing
580
5. **Structure for navigation**: Use sections and tables of contents for large test suites
581
6. **Integrate with tools**: Configure reports for CI/CD and monitoring systems
582
7. **Document with forms**: Use structured forms for complex validation scenarios
583
8. **Version control reports**: Consider including generated documentation in source control