# Share a Cypress Test Report — One Link, Comments on the Failures

Canonical: https://commareports.com/share-cypress-report
Published: 2026-08-21

> Cypress has no built-in shareable report — mochawesome writes a file your team can't open. Publish it to Comma: one stable URL per suite, videos and screenshots attached, anchored comments on each failing spec, a revision per run.

# Share a Cypress test report

Cypress is excellent at telling _you_ what failed. The Test Runner
replays the DOM, the terminal prints a clean summary, the video is right
there in `cypress/videos/`.

None of that survives contact with a teammate. Cypress ships no
shareable report format, so the pipeline everyone converges on is
`cypress-mochawesome-reporter` writing an HTML file into
`cypress/reports/` — a file that then sits inside a CI artifact zip,
behind CI login, on a retention clock, with nowhere to discuss it. The
last mile is always the same: someone screenshots a red spec into Slack.

## The pattern

Store a [scoped token](/docs/api-tokens) — just `reports:write` — in your
CI secret store. Create the report once, keep its id, and `PATCH` it on
every run so there is **one URL per suite**, not one per run:

```bash
npx cypress run --reporter cypress-mochawesome-reporter

curl -fsS -X PATCH "https://commareports.com/api/v1/reports/$REPORT_ID" \
  -H "Authorization: Bearer $COMMA_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d "$(jq -n --rawfile html cypress/reports/html/index.html \
        --arg title "E2E — $GITHUB_SHA" '{title: $title, html: $html}')"
```

In GitHub Actions, with `if: always()` — the runs worth sharing are the
red ones:

```yaml
- name: Publish Cypress report to Comma
  if: always()
  env:
    COMMA_API_TOKEN: ${{ secrets.COMMA_API_TOKEN }}
    REPORT_ID: ${{ vars.COMMA_E2E_REPORT_ID }}
  run: |
    curl -fsS -X PATCH "https://commareports.com/api/v1/reports/$REPORT_ID" \
      -H "Authorization: Bearer $COMMA_API_TOKEN" \
      -H "Content-Type: application/json" \
      -d "$(jq -n --rawfile html cypress/reports/html/index.html \
            --arg title "E2E — ${GITHUB_SHA:0:7}" '{title: $title, html: $html}')"
```

The same two lines work in GitLab CI, CircleCI, Jenkins or Buildkite —
it's plain HTTPS. See [publishing from CI](/docs/ci) for the general
shape.

## Running in parallel

Parallel Cypress runs produce one JSON per machine. Merge before you
publish, or you'll publish a third of a test run:

```bash
npx mochawesome-merge cypress/reports/*.json > merged.json
npx marge merged.json --inline --reportDir cypress/reports/html
```

`--inline` (or `inlineAssets: true` in the reporter options) is the flag
that matters here: it produces a single self-contained HTML file instead
of an `index.html` plus an `assets/` directory.

## What renders, honestly

Comma stores report HTML verbatim and renders it inside an iframe with
`sandbox="allow-scripts"` and no `allow-same-origin`. The report's own
JavaScript runs — from an opaque origin, with no access to the app's
DOM, cookies or storage — so:

- **Interactive** — mochawesome's collapsible suites, the
  passed/failed/pending filters, expandable error stacks.
- **Faithful** — the summary counts, spec names, durations, assertion
  diffs, stack traces, and any screenshot inlined as a data URI.
- **Needs its files** — if you skipped `--inline`, upload the
  `assets/` directory alongside the HTML; relative `src`/`href`
  references are rewritten to the uploaded copies.
- **Capped at 5 MB** for the HTML body. A large suite with base64
  screenshots inlined can exceed it — attach the screenshots as
  [assets](/docs/api) instead of inlining them.

Videos are the other half of the evidence. Attach `cypress/videos/*.mp4`
as assets so the recording of the failing run lives next to the thread
about it.

## Comments land on the failing spec

This is the part CI artifacts have never had.

A reviewer opens the link, expands
`checkout.cy.ts › applies a discount code`, highlights the assertion
diff, and pins a thread to it: "the coupon field got a new
`data-cy` in #482 — selector needs updating." The thread stays anchored
to that failure as revisions accumulate, so when the same spec flakes in
three weeks the prior investigation is one click away instead of lost in
Slack scrollback. That's the [anchored comment
model](/comment-on-html).

The loop closes on the automation side too: an agent in Claude Code or
Cursor attached through [Comma's MCP server](/mcp) reads the threads
with `list_comments`, pushes a fix, and replies on the thread — same
scoped token as the CI publish.

## Housekeeping

- **One report per suite, one revision per run.** PATCH, don't POST.
  Title revisions by commit so the history reads like a log.
- **Announce red runs.** A [webhook](/docs/api) on `revision.created`
  posts the new revision to Slack or Discord — the Slack message goes
  back to being a notification, and the report is the artifact.
- **Rate limits are a non-issue.** 60 requests/minute per token; one
  publish per build is nowhere near it.
- **Keep it team-visible.** E2E results usually want `team` or
  `private`, not public-by-default — see the [sharing
  model](/docs/sharing).

## Try it

Comma is free — unlimited reports, unlimited commenters, unlimited
revision history. Take the next red Cypress run you would have zipped,
publish the report, and send the link instead.

**[Create your first report →](https://commareports.com/)**

### Related

- [Share a Playwright report](/share-playwright-report) — the same pattern, different runner
- [Share an interactive HTML report](/interactive-html-reports) — what runs inside the sandbox and what doesn't
- [Publish from CI](/docs/ci) — the general pipeline pattern
- [Comment on an HTML report](/comment-on-html) — how anchored threads work
