Publish an HTML report from Bitbucket Pipelines

Bitbucket Pipelines is happy to build the report and then gives it nowhere to live. artifacts: keeps the files for 14 days and serves them as a download. The Test Reports tab renders results, but only from JUnit-format XML — a coverage page, a Lighthouse run, a benchmark dashboard or an eval scoreboard has no tab to appear in. And Bitbucket Cloud has no Pages-style static hosting to deploy to, so the usual fallback isn't there either.

What teams do instead: download the zip, or screenshot it into the PR.

One step, one URL

Store a scoped token (reports:write is enough) as a secured repository variable, create the Comma report once, and keep its id in a plain repository variable:

pipelines:
  default:
    - step:
        name: Test and publish report
        image: node:22
        script:
          - npm ci && npm run coverage
          - >-
            curl -fsS -X PATCH
            "https://commareports.com/api/v1/reports/$COMMA_REPORT_ID"
            -H "Authorization: Bearer $COMMA_API_TOKEN"
            -H "Content-Type: application/json"
            -d "$(jq -n --rawfile html coverage/index.html
                  --arg title "Coverage — ${BITBUCKET_COMMIT:0:7}"
                  '{title: $title, html: $html}')"
        after-script:
          - echo "Report: https://commareports.com/p/$COMMA_REPORT_ID"

Two details earn their keep:

  • PATCH, not POST. Each run appends a revision at the same URL — one bookmark per report, and any two runs can be diffed.
  • Publish on red too. Put the call in after-script (which runs even when the step fails) if the report is the thing you most want when the build breaks. after-script also has BITBUCKET_EXIT_CODE available, so the revision title can carry the outcome.

jq isn't in every image — apt-get install -y jq or pick an image that has it. In a minimal image, generating the JSON body with python3 -c is the shorter path.

What you get that a 14-day artifact can't give you

  • The link outlives the sprint. Revisions stay. There is no expiry clock ticking under the URL you pasted in the PR.
  • Comments anchored to the report. A reviewer highlights the failing row and pins a thread to it, and the thread survives the next twelve runs — see commenting on HTML.
  • Diffs between runs. "What changed since the last green build?" is a revision diff, not a memory exercise.
  • Readers without repository access. A Comma report can be team-visible, domain-gated, or shared with a named reviewer who has no Bitbucket seat — see the sharing model.
  • Announcements. A webhook on revision.created posts each run into Slack or Discord.

Limits worth knowing before you wire it up

  • HTML body: 5 MB. Screenshots, trace zips and JS bundles go in as assets — 25 MB per file, 250 MB per report.
  • Scripts run, sandboxed. Report HTML is stored verbatim and rendered in an iframe with sandbox="allow-scripts" and no allow-same-origin, so an interactive report keeps working. What needs care is a report that loads sibling files at view time (Allure, JMeter's dashboard) — upload them as assets alongside the HTML and relative references are rewritten. See Playwright or Cypress for the shape.
  • Rate limits are per token, 60/minute by default. One publish per build is nowhere near it.

Try it

Comma is free — unlimited reports, unlimited commenters, unlimited revision history. Add the step to one pipeline and send a link that still works next quarter.

Create your first report →

Related