Skip to content
ship.page/blog
CI

Your Playwright report shouldn't be a zip

Bart PelleChief Shipper

· 2 min read

Your CI just produced a gorgeous, interactive Playwright report — traces, screenshots, the works. Then it zipped it, stuffed it into the artifacts tab, and sentenced it to death by nobody-ever-downloads-this. There's a better ending to this story, and it takes two lines of YAML.

The artifact dead-end

GitHub artifacts are where HTML reports go to be forgotten. The flow is hostile by design: find the run, scroll to artifacts, download a zip, extract it, double-click index.html — and then discover half the assets 404 because browsers (rightfully) don't love file:// pages with relative paths and service-worker ambitions.

So nobody looks. Reviewers approve red-adjacent PRs because opening the report costs two minutes of faff. The report existed; the information didn't.

The fix isn't a better zip. It's deleting the zip from the loop entirely: serve the folder like the website it already is.

Point the official action at the report directory, right after your test step:

.github/workflows/tests.yml

- uses: bitgate/ship-page-action@v1  if: always()  with:    path: playwright-report    name: pr-${{ github.event.number }}    api-key: ${{ secrets.SHIP_API_KEY }}

The directory goes up as-is — every drop mounts at the root of its own subdomain, so absolute paths, traces and screenshots all resolve exactly like they did on localhost. No base-path rewriting, no build step, no bucket to manage.

Two details carry the weight here. if: always() means a failing run still publishes its report, which is precisely the run you want to inspect. And name makes the URL stable: every push to the PR replaces the content at the same address, so the link a reviewer bookmarked on Monday still shows the latest results on Thursday.

Set comment: true and on pull requests the action posts the URL as a comment, updating that same comment on every re-run — the link is where the conversation already is, not buried in a checks sub-page.

What it costs

Anonymous deploys work with zero config and expire after 7 days — fine for one-off runs. name and api-key are the paid bits: mint an sp_ key in the dashboard, drop it in your repo secrets, done. No servers, no bucket lifecycle rules, no auth pages for viewers.

Not just Playwright

Anything that emits HTML gets the same treatment:

  • Coveragevitest run --coverage --coverage.reporter=html, ship the coverage/ folder. Red lines, clickable.
  • Storybookstorybook build -o storybook-static, ship it. Designers review components in a browser tab, not in a local checkout.
  • Lighthouse — audit the deployed preview, ship the single-file HTML report. Scores on the PR, not in the logs.

Full copy-paste recipes for all four live in Test & build reports in CI. If your pipeline isn't GitHub Actions, the whole thing is one curlCI with plain curl has you covered.

Your reports already are websites. Stop zipping them.

Written by

Bart Pelle

Chief Shipper

Bart founded Bitgate and crowned himself Chief Shipper — a fancy title for “throws HTML at an API all day and occasionally writes about it.” If a build's on fire, he's the one who lit it. Usually on purpose.

Share

XHacker News