or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

core-specifications.mddsl-components.mdindex.mdintegration-features.mdmatcher-system.mdmutable-specifications.mdreporting.mdtest-execution.md

reporting.mddocs/

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