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.
Two lines to a link
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:
- Coverage —
vitest run --coverage --coverage.reporter=html, ship thecoverage/folder. Red lines, clickable. - Storybook —
storybook 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 curl — CI with plain curl has you covered.
Your reports already are websites. Stop zipping them.