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-reporterBasic 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">falseOr 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-reportParallel 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.htmlCypress 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.htmlMochawesome'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.