The default Cypress terminal output is great for engineers running tests locally. It's not great for the product manager who wants to see whether last night's smoke ran green, the support engineer who wants to know which spec failed before merge, or the release manager who needs an artifact to attach to a release ticket. Mochawesome is the most popular Cypress reporter for that audience — a static HTML file with charts, filters, and embedded screenshots. This lesson walks through the install, the merge step, and how to integrate the report into a CI artifact pipeline so any stakeholder can read the results.
Why Cypress integrates with Mocha reporters
Cypress runs every spec under Mocha — the same JavaScript test framework Mocha uses for plain unit tests. Anything that hooks into Mocha's reporter API works with Cypress. The full ecosystem includes:
- mochawesome — static HTML report with charts, filtering, and embedded artefacts. Most popular.
- mocha-junit-reporter — JUnit XML, the format Jenkins, GitLab, and most CI platforms expect.
- allure-cypress — heavier-weight Allure framework reports with rich attachments.
- cypress-multi-reporters — wrapper that runs several reporters at once (terminal + HTML + JUnit, etc.).
- spec — the default terminal output that ships with Mocha.
Pick based on audience. CI dashboards eat JUnit XML; humans read HTML; engineers running locally read the terminal output. Most teams produce two or three formats from one run.
Installing Mochawesome
Three packages, one install:
npm install --save-dev \
mochawesome \
mochawesome-merge \
mochawesome-report-generator- mochawesome — the reporter itself. Plugs into Mocha and writes one JSON file per spec.
- mochawesome-merge — combines all the per-spec JSONs into one. Necessary because Cypress runs each spec in its own Mocha session.
- mochawesome-report-generator (CLI:
marge) — turns the merged JSON into the final HTML.
Configuring the reporter
Tell Cypress to use Mochawesome and where to write the JSONs:
// cypress.config.ts
import { defineConfig } from "cypress";
export default defineConfig({
e2e: {
reporter: "mochawesome",
reporterOptions: {
reportDir: "cypress/reports",
overwrite: false, // keep one JSON per spec
html: false, // don't write HTML per spec — we'll merge later
json: true,
},
},
});The settings that matter:
overwrite: false— without this, every spec's JSON overwrites the previous one and you end up with a report for the last spec only.html: false— skip per-spec HTML generation. We'll generate one consolidated report from the merged JSON.json: true— write the JSON files we'll merge.
Run npm run cy:run and cypress/reports/ fills with mochawesome.json, mochawesome_001.json, mochawesome_002.json, ... — one per spec.
Merging and generating the HTML
After the test run, two CLI calls turn the JSONs into a report:
# 1. Merge per-spec JSONs into one
npx mochawesome-merge cypress/reports/*.json > cypress/reports/merged.json
# 2. Generate HTML from the merged JSON
npx marge cypress/reports/merged.json --reportDir cypress/reports/html --inlineOpen cypress/reports/html/merged.html in a browser — you get a single HTML page with:
- A pass/fail summary chart at the top.
- Per-suite drill-down with timing, steps, and assertion errors.
- Filters by status (passed, failed, pending).
- Embedded screenshots for failed tests (next section).
- Searchable test names and a printable layout for stakeholder reports.
Wiring it into npm scripts
Run-and-report becomes one command:
{
"scripts": {
"cy:run": "cypress run",
"cy:report:merge": "mochawesome-merge cypress/reports/*.json > cypress/reports/merged.json",
"cy:report:html": "marge cypress/reports/merged.json --reportDir cypress/reports/html --inline",
"cy:report": "npm run cy:report:merge && npm run cy:report:html",
"test": "npm run cy:run && npm run cy:report"
}
}npm test now runs the full suite and generates the report in one go. CI workflows call the same scripts; the cypress/reports/html/ directory becomes the build artifact stakeholders download.
Add a cleanup step before each run so stale JSONs from a previous run don't pollute the merge:
"cy:report:clean": "rm -rf cypress/reports && mkdir cypress/reports",
"test": "npm run cy:report:clean && npm run cy:run && npm run cy:report"Attaching screenshots
Mochawesome can embed images in the report directly. Wire the screenshots from cypress/screenshots/ to their failing tests:
// cypress/support/e2e.ts
import addContext from "mochawesome/addContext";
Cypress.on("test:after:run", (test, runnable) => {
if (test.state === "failed") {
const parentTitle = runnable.parent?.title ?? "";
const screenshot =
`../screenshots/${Cypress.spec.name}/` +
`${parentTitle} -- ${test.title} (failed).png`;
addContext({ test }, screenshot);
}
});Now every failed test in the HTML report has a clickable screenshot embedded inline. Reviewers see the failure visually without leaving the report.
For deliberate evidence screenshots from cy.screenshot("step-3"), you can attach those manually inside the test:
import addContext from "mochawesome/addContext";
it("captures the checkout flow", function () {
cy.visit("/cart");
cy.screenshot("01-cart-page");
cy.then(() => addContext(this, "screenshots/checkout.cy.ts/01-cart-page.png"));
});The function() {} form (instead of an arrow) is required so this resolves to the Mocha context Mochawesome needs.
The reporting pipeline
In CI, this whole pipeline is four shell commands. The artifact is one HTML file (with --inline) that any stakeholder can open without dependencies — no server, no internet, no node-modules.
Multi-reporter setups
When you need both terminal output and HTML reports — or HTML for humans and JUnit for the CI dashboard — use cypress-multi-reporters:
npm install --save-dev cypress-multi-reporters// cypress.config.ts
e2e: {
reporter: "cypress-multi-reporters",
reporterOptions: {
reporterEnabled: "spec, mocha-junit-reporter, mochawesome",
mochaJunitReporterReporterOptions: {
mochaFile: "cypress/reports/junit/[hash].xml",
},
mochawesomeReporterOptions: {
reportDir: "cypress/reports",
overwrite: false,
html: false,
json: true,
},
},
}Now cypress run produces three things in one execution: terminal output for the engineer, JUnit XML for the CI dashboard, and Mochawesome JSON ready to be merged into HTML for stakeholders.
A complete production setup
Tying it all together, the package.json of a real CI-mature Cypress project:
{
"scripts": {
"cy:open": "cypress open",
"cy:run": "cypress run",
"cy:report:clean": "rm -rf cypress/reports && mkdir -p cypress/reports",
"cy:report:merge": "mochawesome-merge cypress/reports/*.json > cypress/reports/merged.json",
"cy:report:html": "marge cypress/reports/merged.json --reportDir cypress/reports/html --inline",
"cy:report": "npm run cy:report:merge && npm run cy:report:html",
"test": "npm run cy:report:clean && npm run cy:run; npm run cy:report"
}
}The ; (instead of &&) before npm run cy:report ensures the report runs even when tests fail — that's exactly when stakeholders most need it.
⚠️ Common mistakes
- Forgetting
overwrite: falsein reporter options. Each spec's JSON overwrites the previous one and the merged report contains only the last spec. The fix is one line inreporterOptions. - Pointing
--reportDirat a folder that already exists with old reports. Stalemerged.jsonfiles from yesterday's run can mix with today's JSONs and produce confusing reports. Add acy:report:cleanstep before every run. - Skipping
mochawesome-mergeand trying to pointmargeat multiple JSON files.margeonly consumes one JSON. The merge step is mandatory whenever you have more than one spec — which is always.
🎯 Practice task
Set up a complete reporting pipeline. 25-30 minutes.
- Install the three Mochawesome packages. Configure
cypress.config.tsexactly as in the lesson. - Run
npm run cy:runand confirmcypress/reports/fills with one JSON per spec. - Run the merge and marge commands manually. Open the resulting HTML — confirm pass/fail counts, suite breakdown, and timing all look right.
- Wire the four scripts (
cy:report:clean,cy:report:merge,cy:report:html,cy:report) intopackage.json. Runnpm testand confirm the full pipeline works end-to-end. - Add the screenshot-attachment hook from the lesson to
cypress/support/e2e.ts. Force a failure in one spec; re-run; open the HTML report and confirm the failure entry has an embedded screenshot. - Multi-reporter — install
cypress-multi-reportersand configure it to output both Mochawesome JSON and JUnit XML. Run the suite; confirm bothcypress/reports/*.jsonandcypress/reports/junit/*.xmlare populated. - Stretch: wire the report generation into a fake CI step — write a shell script
ci-test.shthat runs the suite, generates the report, then prints the absolute path tomerged.html(the artifact a CI uploader would publish). Test it locally before integrating into your real CI in chapter 8.
That ends chapter 7 — the cross-cutting concerns. You now have screenshots, videos, visual regression, accessibility audits, and human-readable reports. The next chapter takes the same suite and runs it under CI: GitHub Actions, Jenkins, parallelisation, and Cypress Cloud.