From GitHub Actions to Authenticated URL: A Better Way to Share CI Artifacts
GitHub Actions stores build artifacts in a zip archive. To access them:
The problem with GitHub Actions artifact storage
GitHub Actions stores build artifacts in a zip archive. To access them:
- Go to the Actions run in GitHub
- Find the artifact section
- Click Download
- Extract the zip
- Open the HTML file locally
This works for engineers. It's unusable for everyone else.
Your PM can't access private repository Actions without a GitHub account. Your QA manager shouldn't need to navigate GitHub's CI interface to read a test report. Your VP shouldn't be extracting zip files to see a build summary.
And after 90 days, the artifact is gone. Any link shared in a Slack thread, Notion document, or Jira ticket goes dead.
The one-line change
Before:
- uses: actions/upload-artifact@v4
with:
name: test-report
path: ./playwright-report/After:
- name: Publish report
run: |
npm install -g @display-dev/cli
dsp publish ./playwright-report/ --name "test-run-${{ github.run_id }}"
env:
DISPLAYDEV_API_KEY: ${{ secrets.DISPLAYDEV_API_KEY }}The URL is permanent. It requires a company email to access. It renders in the browser without downloading.
Full workflow example
name: Test and Publish
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Lint
run: npm run lint
- name: Run tests
run: npm test -- --coverage
continue-on-error: true
- name: Build
run: npm run build
- name: Publish test coverage report
if: always()
run: |
npm install -g @display-dev/cli
COVERAGE_URL=$(dsp publish ./coverage/lcov-report/ \
--name "coverage-${{ github.sha }}" \
--title "Coverage: ${{ github.ref_name }}")
echo "COVERAGE_URL=$COVERAGE_URL" >> $GITHUB_ENV
env:
DISPLAYDEV_API_KEY: ${{ secrets.DISPLAYDEV_API_KEY }}
- name: Comment on PR
if: always() && github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
script: |
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: `📊 **Coverage report:** ${process.env.COVERAGE_URL}`
})Use cases this covers
Test reports: Playwright, Jest HTML, Allure, Cypress — any tool that outputs a static HTML report directory.
Build outputs: Preview builds for non-engineers. QA can review what's actually shipping without needing a local development environment.
Coverage reports: Istanbul/c8 HTML reports, accessible to engineers reviewing coverage without running tests locally.
Scheduled report generation: Nightly builds, weekly analytics exports, automated competitive monitoring reports — anything a scheduled workflow generates and stakeholders need to read.
Viewer experience
The PR comment appears with a URL. The QA lead clicks it. Google login — one click. They see the full interactive test report in their browser. No GitHub account. No zip file. No 90-day expiry.
The URL persists in Slack threads, Notion documents, and Jira tickets indefinitely.
Cost comparison
| GitHub Actions artifact storage | Display | |
|---|---|---|
| Viewer needs GitHub account | ✅ | ❌ |
| Viewer needs private repo access | ✅ | ❌ |
| Artifact expiry | 90 days | Never |
| Browser-renderable (no download) | ❌ | ✅ |
| Company SSO | ❌ | ✅ |
| Monthly cost | $0 (included) | $49 flat |
FAQ
Can I publish only on failure?
Yes. Use if: failure() in the step condition. The artifact is only published when the workflow fails.
How long are Display artifacts retained?
Indefinitely, until you delete them. No expiry.
What about other CI systems — GitLab, CircleCI, Jenkins?
The display CLI works anywhere Node.js runs. For GitLab CI: yaml publish: stage: report script: - npm install -g @display-dev/cli - dsp publish ./report/ --name "ci-$CI_PIPELINE_ID" variables: DISPLAYDEV_API_KEY: $DISPLAYDEV_API_KEY Same pattern for CircleCI and Jenkins — install the CLI, call dsp publish.
Free tier. No credit card. One-time password auth for viewers on free, Google + Microsoft SSO on Teams ($49/month flat).