GitHub Action that renders + uploads Storybook screenshots and uploads Playwright recordings to DiffDeck for hosted visual review and screenshot diffs.
It is a thin wrapper around the @diffdeckai/cli
npm package: it fills branch/commit metadata from the GitHub context, runs the CLI
via npx, and surfaces the resulting DiffDeck URL as an action output. All upload
logic lives in the CLI — this action implements none of it.
The command input selects what to do (default auto):
command |
CLI command | What it does |
|---|---|---|
auto |
(detected) | Runs every applicable mode: a recording when video is set, and a screenshot pass when the Storybook dir exists. |
screenshot |
screenshot-storybook |
Renders every story in CI (six variants) + render-check, then uploads the screenshots + build. A story that fails to render fails the job. |
storybook |
upload-storybook |
Uploads the built Storybook only; the server renders the screenshots. |
recording |
upload-recording |
Uploads a single Playwright recording (video + test metadata). |
Screenshot mode needs Playwright in your project — add it as a devDependency (
npm i -D playwright). The action runsplaywright install chromiumfor you (unlessinstall-browsers: false) and caches the download between runs. It does not run--with-depsby default (GitHub-hosted runners already have the system libraries, and the apt install is slow); setinstall-deps: trueif your runner needs them.
Add your DiffDeck project token (shown when you add a repository in DiffDeck) as
a repository secret named DIFFDECK_TOKEN.
name: DiffDeck Storybook
on: [push, pull_request]
jobs:
diffdeck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # so commit metadata is available
- uses: actions/setup-node@v4
with:
node-version: 22
- run: npm ci
- run: npm run build-storybook -- --output-dir storybook-static
- uses: diffdeck/diffdeck-action@v1
with:
# command: auto # the default — a Storybook dir present ⇒ screenshot mode
token: ${{ secrets.DIFFDECK_TOKEN }}
dir: storybook-staticIn screenshot mode the action renders only the stories affected by files
changed since the baseline build, and the server carries the rest forward — so a
typical PR renders a handful of stories instead of the whole library. It kicks in
automatically when two things are present:
-
fetch-depth: 0onactions/checkout(already in the example above) — so the CLI cangit diffthe baseline commit againstHEAD. On a shallow clone the CLI now tries to fetch just the baseline commit on demand, so incremental often works even without this;fetch-depth: 0is the reliable guarantee. -
--webpack-stats-jsonwhen building Storybook — sopreview-stats.json(the module graph) lands in the build dir:- run: npm run build-storybook -- --output-dir storybook-static --webpack-stats-json
It fails open to a full render whenever it can't scope safely (no baseline yet,
no stats file, a shallow clone, or a global file like a lockfile / .storybook
config changed). Set full: true for a periodic full render to bound any drift.
- uses: diffdeck/diffdeck-action@v1
with:
command: storybook
token: ${{ secrets.DIFFDECK_TOKEN }}
dir: storybook-static - uses: diffdeck/diffdeck-action@v1
with:
command: recording
token: ${{ secrets.DIFFDECK_TOKEN }}
video: test-results/home.webm
test: "Home page renders"
file: "tests/home.spec.ts"
status: passedFor a whole Playwright run, the
@diffdeckai/playwright-reporteris the better fit — it uploads every recording with metadata automatically. Use this action'srecordingmode for one-off single-video uploads.
- uses: diffdeck/diffdeck-action@v1
id: diffdeck
with:
token: ${{ secrets.DIFFDECK_TOKEN }}
dir: storybook-static
- run: echo "Review at ${{ steps.diffdeck.outputs.url }}"| Input | Required | Default | Description |
|---|---|---|---|
command |
no | auto |
auto, screenshot, storybook, or recording. |
token |
yes | — | DiffDeck project token. Use a repository secret (e.g. ${{ secrets.DIFFDECK_TOKEN }}). |
dir |
no | storybook-static |
Built Storybook static directory (for screenshot/storybook). |
video |
no | — | Recorded Playwright video file (for recording). |
host |
no | (CLI default — https://diffdeck.ai) |
DiffDeck base URL. Set only for self-hosted / non-default deployments. |
branch |
no | github.head_ref || github.ref_name |
Branch the upload is for. |
commit |
no | github.sha |
Commit SHA the upload is for. |
message |
no | (commit subject line) | Commit message for Storybook builds. |
default-branch |
no | github.event.repository.default_branch |
Repository default branch. Persisted server-side so PR/feature-branch builds resolve their baseline against it. |
pr-number |
no | github.event.pull_request.number |
Pull request number the build is for. Persisted server-side so the build deep-links straight to the exact PR (else a branch-filtered PR search). |
install-browsers |
no | true |
In screenshot mode, run playwright install chromium first. |
install-deps |
no | false |
Also install system libs (--with-deps). Slow; usually unneeded on GitHub-hosted runners. |
cache-browsers |
no | true |
Cache ~/.cache/ms-playwright between runs (screenshot mode). |
concurrency |
no | (CLI default — ~3× CPU, capped 4–16) | Parallel render workers. |
locale |
no | (CLI default — en-US) |
Browser locale for rendering. |
timezone |
no | (CLI default — UTC) |
Browser timezone for rendering. |
settle |
no | (CLI default — 500) |
Milliseconds to wait after each story renders before screenshotting. |
full |
no | false |
Force a full render (disable incremental/TurboSnap scoping). |
stats |
no | (<dir>/preview-stats.json) |
Webpack stats JSON for incremental scoping. |
test |
no | — | Recording: test title. |
file |
no | — | Recording: test file path. |
test-id |
no | — | Recording: stable test identifier. |
status |
no | — | Recording: test status (passed, failed, …). |
duration |
no | — | Recording: test duration in milliseconds. |
retries |
no | — | Recording: number of retries. |
metadata |
no | — | Recording: extra metadata as a JSON string. |
cli-version |
no | latest |
Version (npm dist-tag or semver) of @diffdeckai/cli to run. |
| Output | Description |
|---|---|
url |
URL of the uploaded build / recording on DiffDeck (parsed from CLI). |
The action runs (roughly):
# screenshot (the auto default when a Storybook dir is present)
DIFFDECK_TOKEN=<token> \
npx @diffdeckai/cli@<cli-version> screenshot-storybook --dir <dir> --branch <branch> --commit <commit> [--message <msg>] [--host <host>]
# storybook (server renders)
DIFFDECK_TOKEN=<token> \
npx @diffdeckai/cli@<cli-version> upload-storybook --dir <dir> --branch <branch> --commit <commit> [--message <msg>] [--host <host>]
# recording
DIFFDECK_TOKEN=<token> \
npx @diffdeckai/cli@<cli-version> upload-recording --video <video> --branch <branch> --commit <commit> [metadata…] [--host <host>]The token is passed to the CLI via the DIFFDECK_TOKEN environment variable (never
on the command line); the CLI sends it to DiffDeck as the X-UI-Review-Token
request header. The action captures the CLI's output and exposes the review URL it
prints as the url output.