Run shotlist in CI
This guide adds a job that re-takes every screenshot on each pull request and fails when any of them no longer matches what is committed.
It assumes your screenshots are already published with --install and committed, along with shotlist.baseline.json. If they are not, work through Keeping a screenshot current first.
Step 1: make the job start the application
Nothing is listening on a fresh runner, so the configuration has to say how to start it:
site:
url: http://localhost:4321
serve:
command: npm run preview
ready: 4321
timeout: 60000 Give timeout more room than you need locally. A cold runner is slower than your machine, and a build that has not finished looks exactly like a server that will never answer.
Step 2: add the job
name: Screenshots
on: pull_request
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
- run: npm ci
- run: npm run build
- run: npx shotlist --lint --warnings
- run: npx shotlist --check --keep-going --diff
- uses: actions/upload-artifact@v4
if: failure()
with:
name: screenshot-diffs
path: screenshots/out/diff/ Four flags are doing the work:
-
--lintparses every file and reports all of them at once. It opens no browser, so it finishes in about a second and catches a misspelled key before the slow step starts. See Check your files without a browser. -
--checkre-takes every screenshot, compares it with the committed one, and exits non-zero if any differ. -
--keep-goingreports every problem in one run instead of stopping at the first, so a contributor sees the whole list. -
--diffwrites a three-panel image per changed screenshot, which the next step uploads so a reviewer can look at it without running anything.
Step 3: report the result as data
If another job needs to read the outcome, ask for JSON:
npx shotlist --check --json > report.json Everything written for a person moves to standard error, so the redirect leaves a usable file. The report carries a drift field describing any mismatch between this machine and the one that took the committed images, which lets a job tell a re-render apart from a regression without parsing prose.
Handle pull requests from forks
A pull request from a fork can edit shotlist.config.yaml. Run shotlist with --untrusted after an operator-controlled process has started the application:
npx shotlist --check --untrusted --allow http://localhost:4321 An untrusted Project cannot start site.serve or approve even its own site.url. The operator's --allow http://localhost:4321 grants that exact destination; the Run still refuses Project-provided destinations, paths outside the project, sessions and environment variables.
--untrusted confines shotlist rather than npm ci, a build script or the application being photographed. A fork can change all three, so run them on an isolated runner with no secrets and no access to private network services. Do not start a fork's npm run preview on a privileged runner and treat this flag as its sandbox. See What a configuration can do for the full policy.
Sign in without a person present
Sessions expire, so a scheduled job usually creates one at the start of the run. Name the secret's variable so the macro can read it:
SHOTLIST_ENV=ADMIN_PASSWORD npx shotlist --login admin --using sign-in SHOTLIST_ENV takes a list of names, and adds to whatever --allow-env allowed. See Capture a page behind a sign-in.
Expect differences from a different machine
A Linux runner rasterizes text differently from a Mac, and a different Chromium version moves pixels without the site moving at all. When --check runs on a machine that does not match shotlist.baseline.json, it says so before the results.
The reliable fix is to take the committed screenshots on the same platform the job uses. See Why screenshots drift.