CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-testem

Test-framework agnostic JavaScript testing runner that supports TDD workflows and CI integration across multiple browsers and environments.

Pending
Overview
Eval results
Files

reporters.mddocs/

Reporters

Testem provides multiple output formats for test results including TAP, XUnit, dot notation, TeamCity integration, and an interactive development UI.

Capabilities

Built-in Reporters

Testem includes several built-in reporters for different output formats and integrations.

const reporters = {
  tap: 'TapReporter',        // Test Anything Protocol output
  xunit: 'XUnitReporter',    // XUnit XML format for CI integration
  dot: 'DotReporter',        // Dot progress indicator
  teamcity: 'TeamCityReporter', // TeamCity service messages
  dev: 'DevReporter'         // Interactive development UI (default for dev mode)
};

Usage Examples:

# Command line reporter selection
testem ci -R tap           # TAP output
testem ci -R xunit         # XUnit XML
testem ci -R dot           # Dot notation
testem ci -R teamcity      # TeamCity format
// Configuration file
{
  "reporter": "tap",
  "report_file": "test-results.txt"
}

TAP Reporter

Test Anything Protocol (TAP) output format, widely supported by CI systems and test tooling.

/**
 * TAP (Test Anything Protocol) reporter
 * Outputs TAP-compliant test results
 */
class TapReporter {
  constructor(silent?: boolean, out?: NodeJS.WritableStream);
}

// TAP configuration options
interface TapOptions {
  tap_failed_tests_only?: boolean;      // Only output failing tests
  tap_quiet_logs?: boolean;             // Only show logs for failed tests
  tap_strict_spec_compliance?: boolean; // Strict TAP spec compliance
  tap_log_processor?: (log: any) => string; // Custom log processing function
}

Example TAP Output:

ok 1 Chrome 91.0 - should add numbers correctly
ok 2 Chrome 91.0 - should handle edge cases
not ok 3 Firefox 89.0 - should validate input
  ---
  message: "Expected 'invalid' to throw error"
  severity: fail
  data:
    at: "test/validation.js:15:5"
  ...

1..3
# tests 3
# pass  2
# fail  1

# not ok

Usage Examples:

// Basic TAP configuration
{
  "reporter": "tap"
}

// Advanced TAP options
{
  "reporter": "tap",
  "tap_failed_tests_only": true,
  "tap_quiet_logs": true,
  "tap_strict_spec_compliance": true
}

// Custom log processor (testem.js only)
module.exports = {
  reporter: 'tap',
  tap_log_processor: function(log) {
    return `[${new Date().toISOString()}] ${log}`;
  }
};

XUnit Reporter

XML format compatible with JUnit and other CI systems that expect XUnit-style test results.

/**
 * XUnit XML reporter for CI integration
 * Generates JUnit-compatible XML output
 */
class XUnitReporter {
  constructor(silent?: boolean, out?: NodeJS.WritableStream);
}

Example XUnit Output:

<?xml version="1.0" encoding="UTF-8" ?>
<testsuite name="Testem Tests" tests="3" failures="1" timestamp="2023-06-15T10:30:45Z" time="5.2">
  <testcase classname="Chrome 91.0" name="should add numbers correctly" time="0.1"/>
  <testcase classname="Chrome 91.0" name="should handle edge cases" time="0.2"/>
  <testcase classname="Firefox 89.0" name="should validate input" time="0.3">
    <failure name="should validate input" message="Expected 'invalid' to throw error">
      <![CDATA[
      AssertionError: Expected 'invalid' to throw error
          at test/validation.js:15:5
      ]]>
    </failure>
  </testcase>
</testsuite>

Usage Examples:

// XUnit with output file
{
  "reporter": "xunit",
  "report_file": "test-results.xml"
}

// CI integration
{
  "reporter": "xunit",
  "launch_in_ci": ["Chrome", "Firefox"]
}

Dot Reporter

Minimalist dot-based progress indicator showing test execution status.

/**
 * Dot progress reporter
 * Shows dots for passing tests, F for failures
 */
class DotReporter {
  constructor(silent?: boolean, out?: NodeJS.WritableStream);
}

Example Dot Output:

..F...F..

Failures:

  1) Chrome 91.0 - should validate input
     Expected 'invalid' to throw error
       at test/validation.js:15:5

  2) Firefox 89.0 - should handle async operations  
     Timeout after 5000ms
       at test/async.js:23:10

9 tests, 2 failures

Usage Examples:

// Dot reporter for quick feedback
{
  "reporter": "dot"
}

TeamCity Reporter

TeamCity service message format for seamless integration with JetBrains TeamCity CI/CD platform.

/**
 * TeamCity service messages reporter
 * Outputs TeamCity-compatible service messages
 */
class TeamCityReporter {
  constructor(silent?: boolean, out?: NodeJS.WritableStream);
}

Example TeamCity Output:

##teamcity[testSuiteStarted name='testem.suite']
##teamcity[testStarted name='Chrome 91.0 - should add numbers correctly']
##teamcity[testFinished name='Chrome 91.0 - should add numbers correctly' duration='100']
##teamcity[testStarted name='Firefox 89.0 - should validate input']
##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']
##teamcity[testFinished name='Firefox 89.0 - should validate input' duration='300']
##teamcity[testSuiteFinished name='testem.suite' duration='5200']

Usage Examples:

// TeamCity integration
{
  "reporter": "teamcity"
}

Dev Reporter

Interactive text-based UI for development mode with real-time test feedback and keyboard controls.

/**
 * Interactive development reporter
 * Provides TUI with test results and browser management
 */
class DevReporter {
  constructor(silent?: boolean, out?: NodeJS.WritableStream);
}

Features:

  • Real-time test results display
  • Browser tab navigation
  • File watching notifications
  • Keyboard-controlled interface
  • Split-panel view for test output
  • Color-coded test status

Keyboard Controls:

  • ENTER - Run tests
  • q - Quit
  • / - Navigate browser tabs
  • TAB - Switch panels
  • / - Scroll
  • SPACE - Page down
  • b - Page up

Usage Examples:

// Dev reporter (default for development mode)
{
  "reporter": "dev"  // Usually automatic in dev mode
}

Custom Reporters

Creating Custom Reporters

Create custom reporters by implementing the reporter interface.

/**
 * Base reporter interface
 */
interface Reporter {
  /**
   * Called when test suite starts
   * @param numLaunchers - Number of test launchers
   */
  onStart(numLaunchers: number): void;
  
  /**
   * Called when a test starts
   * @param launcher - Launcher information
   * @param test - Test information
   */
  onTestStart(launcher: LauncherInfo, test: TestInfo): void;
  
  /**
   * Called when a test completes
   * @param launcher - Launcher information  
   * @param test - Test result
   */
  onTestResult(launcher: LauncherInfo, test: TestResult): void;
  
  /**
   * Called when all tests complete
   * @param results - Final test results
   */
  onAllTestResults(results: TestResults): void;
}

interface LauncherInfo {
  name: string;           // Launcher name (e.g., "Chrome 91.0")
  type: string;           // Launcher type ("browser" or "process")
}

interface TestInfo {
  name: string;           // Test name
  id: number;             // Test ID
}

interface TestResult extends TestInfo {
  passed: boolean;        // Test passed
  failed: boolean;        // Test failed  
  error?: Error;          // Test error
  logs: string[];         // Test logs
  runDuration: number;    // Test duration in ms
}

Custom Reporter Example:

// custom-reporter.js
class CustomReporter {
  constructor(silent, out) {
    this.out = out || process.stdout;
    this.silent = silent;
    this.results = [];
  }
  
  onStart(numLaunchers) {
    if (!this.silent) {
      this.out.write(`Starting tests on ${numLaunchers} launchers...\n`);
    }
  }
  
  onTestResult(launcher, test) {
    this.results.push({ launcher, test });
    
    if (!this.silent) {
      const status = test.passed ? '✓' : '✗';
      const duration = `(${test.runDuration}ms)`;
      this.out.write(`${status} ${launcher.name} - ${test.name} ${duration}\n`);
    }
  }
  
  onAllTestResults(results) {
    const passed = this.results.filter(r => r.test.passed).length;
    const total = this.results.length;
    
    this.out.write(`\nResults: ${passed}/${total} tests passed\n`);
    
    // Output JSON summary
    const summary = {
      total,
      passed,
      failed: total - passed,
      results: this.results
    };
    
    this.out.write(`\n${JSON.stringify(summary, null, 2)}\n`);
  }
}

module.exports = CustomReporter;

Registering Custom Reporters

// testem.js
const CustomReporter = require('./custom-reporter');

module.exports = {
  src_files: ['src/**/*.js', 'test/**/*.js'],
  reporter_options: {
    'custom': CustomReporter
  }
};

Reporter Configuration

Output File Configuration

Direct reporter output to files for CI integration and archival.

interface ReporterConfig {
  reporter: string;           // Reporter name
  report_file?: string;       // Output file path
  reporter_options?: object;  // Reporter-specific options
}

Usage Examples:

// Output to file
{
  "reporter": "tap",
  "report_file": "results/test-output.tap"
}

// XUnit with timestamped filename
{
  "reporter": "xunit", 
  "report_file": "results/junit-{{timestamp}}.xml"
}

// Multiple output files (testem.js only)
module.exports = {
  reporter: 'tap',
  after_tests: function(config, data, callback) {
    // Custom post-processing
    const fs = require('fs');
    const results = JSON.stringify(data.results, null, 2);
    fs.writeFileSync('results/detailed-results.json', results);
    callback();
  }
};

CI Integration Examples

Jenkins Integration

<!-- Jenkins pipeline -->
<project>
  <builders>
    <hudson.tasks.Shell>
      <command>testem ci -R xunit</command>
    </hudson.tasks.Shell>
  </builders>
  <publishers>
    <hudson.tasks.junit.JUnitResultArchiver>
      <testResults>test-results.xml</testResults>
    </hudson.tasks.junit.JUnitResultArchiver>
  </publishers>
</project>

GitHub Actions Integration

# .github/workflows/test.yml
name: Tests
on: [push, pull_request]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: actions/setup-node@v2
      - run: npm install
      - run: testem ci -R tap
      - uses: dorny/test-reporter@v1
        if: always()
        with:
          name: Test Results
          path: test-results.tap
          reporter: java-junit

Install with Tessl CLI

npx tessl i tessl/npm-testem

docs

cli.md

configuration.md

index.md

launchers.md

programmatic-api.md

reporters.md

tile.json