0
# Reporters
1
2
Testem provides multiple output formats for test results including TAP, XUnit, dot notation, TeamCity integration, and an interactive development UI.
3
4
## Capabilities
5
6
### Built-in Reporters
7
8
Testem includes several built-in reporters for different output formats and integrations.
9
10
```javascript { .api }
11
const reporters = {
12
tap: 'TapReporter', // Test Anything Protocol output
13
xunit: 'XUnitReporter', // XUnit XML format for CI integration
14
dot: 'DotReporter', // Dot progress indicator
15
teamcity: 'TeamCityReporter', // TeamCity service messages
16
dev: 'DevReporter' // Interactive development UI (default for dev mode)
17
};
18
```
19
20
**Usage Examples:**
21
22
```bash
23
# Command line reporter selection
24
testem ci -R tap # TAP output
25
testem ci -R xunit # XUnit XML
26
testem ci -R dot # Dot notation
27
testem ci -R teamcity # TeamCity format
28
```
29
30
```javascript
31
// Configuration file
32
{
33
"reporter": "tap",
34
"report_file": "test-results.txt"
35
}
36
```
37
38
### TAP Reporter
39
40
Test Anything Protocol (TAP) output format, widely supported by CI systems and test tooling.
41
42
```javascript { .api }
43
/**
44
* TAP (Test Anything Protocol) reporter
45
* Outputs TAP-compliant test results
46
*/
47
class TapReporter {
48
constructor(silent?: boolean, out?: NodeJS.WritableStream);
49
}
50
51
// TAP configuration options
52
interface TapOptions {
53
tap_failed_tests_only?: boolean; // Only output failing tests
54
tap_quiet_logs?: boolean; // Only show logs for failed tests
55
tap_strict_spec_compliance?: boolean; // Strict TAP spec compliance
56
tap_log_processor?: (log: any) => string; // Custom log processing function
57
}
58
```
59
60
**Example TAP Output:**
61
62
```
63
ok 1 Chrome 91.0 - should add numbers correctly
64
ok 2 Chrome 91.0 - should handle edge cases
65
not ok 3 Firefox 89.0 - should validate input
66
---
67
message: "Expected 'invalid' to throw error"
68
severity: fail
69
data:
70
at: "test/validation.js:15:5"
71
...
72
73
1..3
74
# tests 3
75
# pass 2
76
# fail 1
77
78
# not ok
79
```
80
81
**Usage Examples:**
82
83
```javascript
84
// Basic TAP configuration
85
{
86
"reporter": "tap"
87
}
88
89
// Advanced TAP options
90
{
91
"reporter": "tap",
92
"tap_failed_tests_only": true,
93
"tap_quiet_logs": true,
94
"tap_strict_spec_compliance": true
95
}
96
97
// Custom log processor (testem.js only)
98
module.exports = {
99
reporter: 'tap',
100
tap_log_processor: function(log) {
101
return `[${new Date().toISOString()}] ${log}`;
102
}
103
};
104
```
105
106
### XUnit Reporter
107
108
XML format compatible with JUnit and other CI systems that expect XUnit-style test results.
109
110
```javascript { .api }
111
/**
112
* XUnit XML reporter for CI integration
113
* Generates JUnit-compatible XML output
114
*/
115
class XUnitReporter {
116
constructor(silent?: boolean, out?: NodeJS.WritableStream);
117
}
118
```
119
120
**Example XUnit Output:**
121
122
```xml
123
<?xml version="1.0" encoding="UTF-8" ?>
124
<testsuite name="Testem Tests" tests="3" failures="1" timestamp="2023-06-15T10:30:45Z" time="5.2">
125
<testcase classname="Chrome 91.0" name="should add numbers correctly" time="0.1"/>
126
<testcase classname="Chrome 91.0" name="should handle edge cases" time="0.2"/>
127
<testcase classname="Firefox 89.0" name="should validate input" time="0.3">
128
<failure name="should validate input" message="Expected 'invalid' to throw error">
129
<![CDATA[
130
AssertionError: Expected 'invalid' to throw error
131
at test/validation.js:15:5
132
]]>
133
</failure>
134
</testcase>
135
</testsuite>
136
```
137
138
**Usage Examples:**
139
140
```javascript
141
// XUnit with output file
142
{
143
"reporter": "xunit",
144
"report_file": "test-results.xml"
145
}
146
147
// CI integration
148
{
149
"reporter": "xunit",
150
"launch_in_ci": ["Chrome", "Firefox"]
151
}
152
```
153
154
### Dot Reporter
155
156
Minimalist dot-based progress indicator showing test execution status.
157
158
```javascript { .api }
159
/**
160
* Dot progress reporter
161
* Shows dots for passing tests, F for failures
162
*/
163
class DotReporter {
164
constructor(silent?: boolean, out?: NodeJS.WritableStream);
165
}
166
```
167
168
**Example Dot Output:**
169
170
```
171
..F...F..
172
173
Failures:
174
175
1) Chrome 91.0 - should validate input
176
Expected 'invalid' to throw error
177
at test/validation.js:15:5
178
179
2) Firefox 89.0 - should handle async operations
180
Timeout after 5000ms
181
at test/async.js:23:10
182
183
9 tests, 2 failures
184
```
185
186
**Usage Examples:**
187
188
```javascript
189
// Dot reporter for quick feedback
190
{
191
"reporter": "dot"
192
}
193
```
194
195
### TeamCity Reporter
196
197
TeamCity service message format for seamless integration with JetBrains TeamCity CI/CD platform.
198
199
```javascript { .api }
200
/**
201
* TeamCity service messages reporter
202
* Outputs TeamCity-compatible service messages
203
*/
204
class TeamCityReporter {
205
constructor(silent?: boolean, out?: NodeJS.WritableStream);
206
}
207
```
208
209
**Example TeamCity Output:**
210
211
```
212
##teamcity[testSuiteStarted name='testem.suite']
213
##teamcity[testStarted name='Chrome 91.0 - should add numbers correctly']
214
##teamcity[testFinished name='Chrome 91.0 - should add numbers correctly' duration='100']
215
##teamcity[testStarted name='Firefox 89.0 - should validate input']
216
##teamcity[testFailed name='Firefox 89.0 - should validate input' message='Expected |'invalid|' to throw error' details='AssertionError: Expected |'invalid|' to throw error|n at test/validation.js:15:5']
217
##teamcity[testFinished name='Firefox 89.0 - should validate input' duration='300']
218
##teamcity[testSuiteFinished name='testem.suite' duration='5200']
219
```
220
221
**Usage Examples:**
222
223
```javascript
224
// TeamCity integration
225
{
226
"reporter": "teamcity"
227
}
228
```
229
230
### Dev Reporter
231
232
Interactive text-based UI for development mode with real-time test feedback and keyboard controls.
233
234
```javascript { .api }
235
/**
236
* Interactive development reporter
237
* Provides TUI with test results and browser management
238
*/
239
class DevReporter {
240
constructor(silent?: boolean, out?: NodeJS.WritableStream);
241
}
242
```
243
244
**Features:**
245
246
- Real-time test results display
247
- Browser tab navigation
248
- File watching notifications
249
- Keyboard-controlled interface
250
- Split-panel view for test output
251
- Color-coded test status
252
253
**Keyboard Controls:**
254
255
- `ENTER` - Run tests
256
- `q` - Quit
257
- `←`/`→` - Navigate browser tabs
258
- `TAB` - Switch panels
259
- `↑`/`↓` - Scroll
260
- `SPACE` - Page down
261
- `b` - Page up
262
263
**Usage Examples:**
264
265
```javascript
266
// Dev reporter (default for development mode)
267
{
268
"reporter": "dev" // Usually automatic in dev mode
269
}
270
```
271
272
## Custom Reporters
273
274
### Creating Custom Reporters
275
276
Create custom reporters by implementing the reporter interface.
277
278
```javascript { .api }
279
/**
280
* Base reporter interface
281
*/
282
interface Reporter {
283
/**
284
* Called when test suite starts
285
* @param numLaunchers - Number of test launchers
286
*/
287
onStart(numLaunchers: number): void;
288
289
/**
290
* Called when a test starts
291
* @param launcher - Launcher information
292
* @param test - Test information
293
*/
294
onTestStart(launcher: LauncherInfo, test: TestInfo): void;
295
296
/**
297
* Called when a test completes
298
* @param launcher - Launcher information
299
* @param test - Test result
300
*/
301
onTestResult(launcher: LauncherInfo, test: TestResult): void;
302
303
/**
304
* Called when all tests complete
305
* @param results - Final test results
306
*/
307
onAllTestResults(results: TestResults): void;
308
}
309
310
interface LauncherInfo {
311
name: string; // Launcher name (e.g., "Chrome 91.0")
312
type: string; // Launcher type ("browser" or "process")
313
}
314
315
interface TestInfo {
316
name: string; // Test name
317
id: number; // Test ID
318
}
319
320
interface TestResult extends TestInfo {
321
passed: boolean; // Test passed
322
failed: boolean; // Test failed
323
error?: Error; // Test error
324
logs: string[]; // Test logs
325
runDuration: number; // Test duration in ms
326
}
327
```
328
329
**Custom Reporter Example:**
330
331
```javascript
332
// custom-reporter.js
333
class CustomReporter {
334
constructor(silent, out) {
335
this.out = out || process.stdout;
336
this.silent = silent;
337
this.results = [];
338
}
339
340
onStart(numLaunchers) {
341
if (!this.silent) {
342
this.out.write(`Starting tests on ${numLaunchers} launchers...\n`);
343
}
344
}
345
346
onTestResult(launcher, test) {
347
this.results.push({ launcher, test });
348
349
if (!this.silent) {
350
const status = test.passed ? '✓' : '✗';
351
const duration = `(${test.runDuration}ms)`;
352
this.out.write(`${status} ${launcher.name} - ${test.name} ${duration}\n`);
353
}
354
}
355
356
onAllTestResults(results) {
357
const passed = this.results.filter(r => r.test.passed).length;
358
const total = this.results.length;
359
360
this.out.write(`\nResults: ${passed}/${total} tests passed\n`);
361
362
// Output JSON summary
363
const summary = {
364
total,
365
passed,
366
failed: total - passed,
367
results: this.results
368
};
369
370
this.out.write(`\n${JSON.stringify(summary, null, 2)}\n`);
371
}
372
}
373
374
module.exports = CustomReporter;
375
```
376
377
### Registering Custom Reporters
378
379
```javascript
380
// testem.js
381
const CustomReporter = require('./custom-reporter');
382
383
module.exports = {
384
src_files: ['src/**/*.js', 'test/**/*.js'],
385
reporter_options: {
386
'custom': CustomReporter
387
}
388
};
389
```
390
391
## Reporter Configuration
392
393
### Output File Configuration
394
395
Direct reporter output to files for CI integration and archival.
396
397
```javascript { .api }
398
interface ReporterConfig {
399
reporter: string; // Reporter name
400
report_file?: string; // Output file path
401
reporter_options?: object; // Reporter-specific options
402
}
403
```
404
405
**Usage Examples:**
406
407
```javascript
408
// Output to file
409
{
410
"reporter": "tap",
411
"report_file": "results/test-output.tap"
412
}
413
414
// XUnit with timestamped filename
415
{
416
"reporter": "xunit",
417
"report_file": "results/junit-{{timestamp}}.xml"
418
}
419
420
// Multiple output files (testem.js only)
421
module.exports = {
422
reporter: 'tap',
423
after_tests: function(config, data, callback) {
424
// Custom post-processing
425
const fs = require('fs');
426
const results = JSON.stringify(data.results, null, 2);
427
fs.writeFileSync('results/detailed-results.json', results);
428
callback();
429
}
430
};
431
```
432
433
### CI Integration Examples
434
435
### Jenkins Integration
436
437
```xml
438
<!-- Jenkins pipeline -->
439
<project>
440
<builders>
441
<hudson.tasks.Shell>
442
<command>testem ci -R xunit</command>
443
</hudson.tasks.Shell>
444
</builders>
445
<publishers>
446
<hudson.tasks.junit.JUnitResultArchiver>
447
<testResults>test-results.xml</testResults>
448
</hudson.tasks.junit.JUnitResultArchiver>
449
</publishers>
450
</project>
451
```
452
453
### GitHub Actions Integration
454
455
```yaml
456
# .github/workflows/test.yml
457
name: Tests
458
on: [push, pull_request]
459
jobs:
460
test:
461
runs-on: ubuntu-latest
462
steps:
463
- uses: actions/checkout@v2
464
- uses: actions/setup-node@v2
465
- run: npm install
466
- run: testem ci -R tap
467
- uses: dorny/test-reporter@v1
468
if: always()
469
with:
470
name: Test Results
471
path: test-results.tap
472
reporter: java-junit
473
```