Overview
snapdrift is a free, open-source visual-regression runner for GitHub Actions: a config file and two workflow jobs capture each route on a PR, diff it against a baseline, and post the result as a PR comment. Snap is the hosted upgrade โ the same workflow, but rendering moves off your CI runner, baselines live in durable storage, and you get a triage dashboard.
How it works
Every pull request runs the same four-step loop:
Start free โ snapdrift local mode
No Snap account needed. snapdrift starts Playwright inside your GitHub Actions runner, captures each configured route against your running app, and diffs the result against a stored baseline. Baselines live as workflow artifacts โ free for open-source and small teams.
Step 1 โ Add a config file
Create .github/snapdrift.json in your repo. baseUrl is the address of your app once it's started in the CI runner:
{
"baselineArtifactName": "my-app-snapdrift-baseline",
"baseUrl": "http://127.0.0.1:3000",
"routes": [
{ "id": "home-desktop", "path": "/", "viewport": "desktop" },
{ "id": "home-mobile", "path": "/", "viewport": "mobile" },
{ "id": "dashboard", "path": "/dashboard","viewport": "desktop" }
],
"diff": { "threshold": 0.01, "mode": "report-only" }
}Step 2 โ Add two workflow jobs
One job publishes a baseline when you merge to main. The other runs the diff on every pull request. You own app startup โ snapdrift takes over once the app is reachable.
name: Visual Regression
on:
push:
branches: [main]
pull_request:
permissions:
contents: read
actions: read
issues: write
pull-requests: write
jobs:
baseline:
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '22'
cache: npm
- run: npm ci && npm run build
- name: Start app
run: |
npm start &
for i in $(seq 1 45); do
curl -sf http://127.0.0.1:3000 && break || sleep 1
done
- name: SnapDrift Baseline
uses: ranacseruet/snapdrift/actions/baseline@v0.2.1
with:
repo-config-path: .github/snapdrift.json
pr-diff:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '22'
cache: npm
- run: npm ci && npm run build
- name: Start app
run: |
npm start &
for i in $(seq 1 45); do
curl -sf http://127.0.0.1:3000 && break || sleep 1
done
- name: SnapDrift Report
uses: ranacseruet/snapdrift/actions/pr-diff@v0.2.1
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
repo-config-path: .github/snapdrift.jsonsnapdrift installs Playwright, captures each route against your running app, diffs against the latest baseline artifact, and posts results to the PR.
Drift enforcement modes
Set diff.mode in your config to control what happens when drift is detected:
| Mode | Behavior | When to use |
|---|---|---|
report-only | Always passes; posts results for visibility | Start here while baselines settle |
fail-on-changes | Fails the check when any route exceeds the threshold | Day-to-day once the signal is stable |
fail-on-incomplete | Fails on missing captures, dimension shifts, or errors | Catch broken or partial captures |
strict | Fails on any drift signal whatsoever | Lock things down before a release |
Upgrade to Snap
When GH artifact retention (~90 days) becomes a problem, rendering inside the CI runner is too slow, or you want a triage dashboard โ Snap is the upgrade. snapdrift's provider: "snap" integration is in active development; once available, switching will require one config change and a Snap API key.
Snap is designed as a drop-in backend for snapdrift. When provider: snap is set, snapdrift hands off rendering to Snap's Lambda fleet โ your app no longer needs to run in the CI runner, baselines move to S3 with durable retention, and you get access to the triage dashboard. The same routes, the same diff modes, the same PR comment shape.
What you get with Snap
Render off your CI runner
Snap's Lambda renderer captures your preview deploy URL. No Playwright install, no app startup in CI, no runner minutes burned on rendering.
Durable baseline store
Baselines live in S3 with configurable retention โ 30 days, 1 year, or longer. No 90-day artifact cap. Multi-repo, audit-logged.
Pinned, deterministic rendering
Snap pins the Chromium version, font set, and timezone. A baseline from six months ago still reproduces exactly โ no drift from runner image updates.
Triage dashboard
Review diffs, approve new baselines, and assign reviews to teammates without leaving the browser. Available at /dashboard/visual.
In the meantime, you can already call Snap's screenshot API directly from CI via POST /v1/screenshots for single-shot captures. See the API reference.
snapdrift vs Snap
snapdrift
Free ยท OSS- โCapture + diff + PR comment
- โLocal CLI for dev-time checks
- โRoute scoping by changed files
- โFour enforcement modes
- โBaselines in GH artifacts (~90d)
- โApp must run in the CI runner
- โPlaywright installs on every run
- โBrowser version drifts with runner image
- โNo cross-CI (GitHub Actions only)
- โNo triage dashboard
Snap
Hosted- โCapture + diff + PR comment
- โLocal CLI for dev-time checks
- โRoute scoping by changed files
- โFour enforcement modes
- โDurable baselines in S3 (custom retention)
- โCaptures your preview deploy โ no app startup in CI
- โLambda render โ no Playwright in runner
- โPinned, versioned browser + fonts
- โCross-CI: GitLab, CircleCI, and more
- โTriage dashboard with approval workflow