Mochawesome: Beautiful Test Reports for Mocha and Cypress

Mochawesome: Beautiful Test Reports for Mocha and Cypress

Plain Mocha test output gets the job done in local development, but in CI environments and for team review, you need richer reporting. Mochawesome generates beautiful, interactive HTML reports with test details, timing, code snippets, and failure screenshots — all in a self-contained file you can share or archive.

What Is Mochawesome?

Mochawesome is a custom reporter for the Mocha test framework that produces standalone HTML/CSS reports. It also works with Cypress, which uses Mocha under the hood.

Key features:

  • Self-contained HTML files (no server needed to view)
  • Interactive filtering (pass/fail/pending)
  • Test code snippets in reports
  • Embedded screenshots (with Cypress integration)
  • JSON output for custom processing
  • Merge utility for parallel test runs

Installation

# For plain Mocha
npm install --save-dev mochawesome

<span class="hljs-comment"># For Cypress
npm install --save-dev cypress-mochawesome-reporter

Basic Mocha Setup

# Run with mochawesome reporter
npx mocha tests/ --reporter mochawesome

<span class="hljs-comment"># Custom output directory
npx mocha tests/ --reporter mochawesome \
  --reporter-options reportDir=reports,reportFilename=test-results

<span class="hljs-comment"># Include all tests even skipped ones
npx mocha tests/ --reporter mochawesome \
  --reporter-options reportDir=reports,reportFilename=results,quiet=<span class="hljs-literal">false

Or configure in .mocharc.json:

{
  "reporter": "mochawesome",
  "reporter-option": [
    "reportDir=reports",
    "reportFilename=results",
    "html=true",
    "json=true",
    "overwrite=false",
    "timestamp=true"
  ]
}

Reporter Options

Option Default Description
reportDir mochawesome-report Output directory
reportFilename mochawesome Filename without extension
html true Generate HTML report
json true Generate JSON report
overwrite true Overwrite existing files
quiet false Suppress logging
timestamp false Add timestamp to filename
charts true Include donut charts
code true Include test code
showHooks failed Show hooks: always/failed/never
saveJson false Save JSON alongside HTML

Cypress Integration

Cypress uses Mocha internally, so Mochawesome integrates naturally:

npm install --save-dev cypress-mochawesome-reporter
// cypress.config.js
const { defineConfig } = require('cypress');

module.exports = defineConfig({
  reporter: 'cypress-mochawesome-reporter',
  reporterOptions: {
    reportDir: 'cypress/reports',
    charts: true,
    reportPageTitle: 'My App Tests',
    embeddedScreenshots: true,
    inlineAssets: true,
    saveAllAttempts: false,
  },
  e2e: {
    setupNodeEvents(on, config) {
      require('cypress-mochawesome-reporter/plugin')(on);
      return config;
    },
  },
});

Automatic Screenshot on Failure

With embeddedScreenshots: true, Cypress screenshots are automatically embedded in the report:

// cypress/support/e2e.js
import 'cypress-mochawesome-reporter/register';

// Screenshots are auto-captured on failure and embedded in report
# Run Cypress tests
npx cypress run

<span class="hljs-comment"># Report generated at cypress/reports/

Merging Reports from Parallel Runs

When you run tests in parallel across multiple processes or machines, each produces its own JSON file. mochawesome-merge combines them:

npm install --save-dev mochawesome-merge marge
# Run tests in parallel (each writes its own JSON)
npx mocha tests/part1/ --reporter mochawesome \
  --reporter-options reportDir=reports,reportFilename=results-1,html=<span class="hljs-literal">false
npx mocha tests/part2/ --reporter mochawesome \
  --reporter-options reportDir=reports,reportFilename=results-2,html=<span class="hljs-literal">false

<span class="hljs-comment"># Merge all JSON files
npx mochawesome-merge reports/*.json > reports/merged.json

<span class="hljs-comment"># Generate final HTML from merged JSON
npx marge reports/merged.json --reportDir reports --reportFilename final-report

Parallel CI with Merge

# GitHub Actions parallel matrix
jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        shard: [1, 2, 3, 4]
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
      - run: npm ci
      
      - name: Run test shard ${{ matrix.shard }}
        run: |
          npx mocha tests/ \
            --reporter mochawesome \
            --reporter-options "reportDir=reports,reportFilename=shard-${{ matrix.shard }},html=false,json=true"
      
      - name: Upload JSON report
        uses: actions/upload-artifact@v4
        with:
          name: test-reports-${{ matrix.shard }}
          path: reports/shard-${{ matrix.shard }}.json

  report:
    needs: test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
      - run: npm ci
      
      - name: Download all reports
        uses: actions/download-artifact@v4
        with:
          pattern: test-reports-*
          merge-multiple: true
          path: reports/
      
      - name: Merge and generate HTML report
        run: |
          npx mochawesome-merge reports/*.json > reports/merged.json
          npx marge reports/merged.json \
            --reportDir reports \
            --reportFilename final-report \
            --reportTitle "Test Results - ${{ github.run_id }}"
      
      - name: Upload final report
        uses: actions/upload-artifact@v4
        with:
          name: test-report-final
          path: reports/final-report.html

Cypress Parallel with Mochawesome

For Cypress parallel runs:

jobs:
  test:
    strategy:
      matrix:
        container: [1, 2, 3]
    steps:
      - name: Run Cypress tests
        run: npx cypress run --parallel --record --ci-build-id ${{ github.run_id }}
        # Each container writes to cypress/reports/

      - name: Upload reports
        uses: actions/upload-artifact@v4
        with:
          name: cypress-report-${{ matrix.container }}
          path: cypress/reports/*.json

  merge-report:
    needs: test
    runs-on: ubuntu-latest
    steps:
      - run: npm ci
      
      - uses: actions/download-artifact@v4
        with:
          pattern: cypress-report-*
          merge-multiple: true
          path: cypress/reports/

      - name: Merge reports
        run: |
          npx mochawesome-merge cypress/reports/*.json > merged.json
          npx marge merged.json \
            --reportDir html-reports \
            --embeddedScreenshots true \
            --inlineAssets true
      
      - uses: actions/upload-artifact@v4
        with:
          name: cypress-report
          path: html-reports/

Customizing Reports

Custom Report Title and CSS

// .mocharc.js
module.exports = {
  reporter: 'mochawesome',
  reporterOptions: {
    reportDir: 'reports',
    reportFilename: 'test-results',
    reportTitle: 'My App Test Suite',
    reportPageTitle: 'CI Test Report',
    // Custom CSS
    cdn: false,  // Embed assets locally
  }
};

Adding Custom Context

Mochawesome supports adding custom data to test results:

const addContext = require('mochawesome/addContext');

describe('User API', function() {
  it('creates a user', async function() {
    const response = await createUser({ name: 'Alice' });
    
    // Add context to this test in the report
    addContext(this, {
      title: 'API Response',
      value: JSON.stringify(response.body, null, 2)
    });
    
    addContext(this, {
      title: 'Request URL',
      value: `${response.request.method} ${response.request.url}`
    });
    
    expect(response.status).to.equal(201);
  });
});

Context appears in the report as expandable sections for each test.

With Cypress

// cypress/e2e/checkout.cy.js
describe('Checkout', () => {
  it('completes purchase', () => {
    cy.addTestContext('Test environment: staging');
    
    cy.visit('/cart');
    cy.get('[data-cy=checkout-btn]').click();
    
    // On failure, screenshot is auto-embedded
    cy.get('[data-cy=order-confirmation]').should('be.visible');
  });
});

Processing JSON Output

The JSON report enables custom processing:

// analyze-results.js
const fs = require('fs');

const report = JSON.parse(fs.readFileSync('reports/mochawesome.json', 'utf8'));

const { stats } = report;
console.log(`Tests: ${stats.tests}`);
console.log(`Passes: ${stats.passes}`);
console.log(`Failures: ${stats.failures}`);
console.log(`Pending: ${stats.pending}`);
console.log(`Duration: ${stats.duration}ms`);

// Find slowest tests
const allTests = [];
function collectTests(suite) {
  suite.tests.forEach(t => allTests.push(t));
  suite.suites.forEach(s => collectTests(s));
}
collectTests(report.results[0]);

const slowest = allTests
  .sort((a, b) => b.duration - a.duration)
  .slice(0, 5);

console.log('\nSlowest tests:');
slowest.forEach(t => console.log(`  ${t.duration}ms: ${t.fullTitle}`));

// Count failures by file
const failures = allTests.filter(t => t.fail);
console.log(`\nFailed tests (${failures.length}):`);
failures.forEach(t => {
  console.log(`  ✗ ${t.fullTitle}`);
  console.log(`    ${t.err.message}`);
});

Posting Results to Slack

// post-to-slack.js
const fs = require('fs');
const https = require('https');

const report = JSON.parse(fs.readFileSync('reports/mochawesome.json', 'utf8'));
const { stats } = report;

const emoji = stats.failures > 0 ? ':x:' : ':white_check_mark:';
const color = stats.failures > 0 ? '#ff0000' : '#36a64f';

const payload = {
  attachments: [{
    color,
    title: `${emoji} Test Results — ${stats.failures > 0 ? 'FAILED' : 'PASSED'}`,
    fields: [
      { title: 'Passed', value: stats.passes, short: true },
      { title: 'Failed', value: stats.failures, short: true },
      { title: 'Pending', value: stats.pending, short: true },
      { title: 'Duration', value: `${(stats.duration / 1000).toFixed(1)}s`, short: true },
    ],
    footer: `Build ${process.env.GITHUB_RUN_ID || 'local'}`,
  }]
};

const webhookUrl = process.env.SLACK_WEBHOOK_URL;
// POST to Slack...

Comparison with Other JS Reporters

Feature Mochawesome Jest HTML Reporter Allure
Framework Mocha, Cypress Jest Many
HTML output
Screenshots ✓ (Cypress)
Merge parallel
Interactive UI
Historical trends
Self-contained HTML
Setup complexity Low Low High

Mochawesome wins for simplicity and Cypress integration. Allure provides more features (history, categories, environments) at the cost of needing a server or static site to view.

Publishing Reports

Self-contained HTML means easy publishing:

# GitHub Pages deployment
- name: Deploy report to GitHub Pages
  uses: peaceiris/actions-gh-pages@v3
  if: always()
  with:
    github_token: ${{ secrets.GITHUB_TOKEN }}
    publish_dir: reports/
    destination_dir: test-reports/${{ github.run_id }}

# Access at:
# https://myorg.github.io/myrepo/test-reports/<run-id>/final-report.html

Mochawesome's combination of minimal setup, good Cypress integration, and self-contained HTML output makes it the default choice for JavaScript test reporting in most teams. Add the merge utility and you cover parallel CI runs without any additional infrastructure.

Read more