Visual regression in your CI pipeline

Catch visual changes before they reach production. Start free with snapdrift, then upgrade to Snap for hosted baselines, pinned rendering, and a triage dashboard.

Free & open sourceGitHub Actions readyNo app in CI on Snap

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:

1
Open a PR
Your workflow triggers on pull_request
2
Capture
snapdrift screenshots each configured route
3
Diff vs baseline
Pixel diff against the stored baseline
4
PR comment
Results posted back to the pull request

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:

json
{
  "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.

yaml
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.json

snapdrift 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:

ModeBehaviorWhen to use
report-onlyAlways passes; posts results for visibilityStart here while baselines settle
fail-on-changesFails the check when any route exceeds the thresholdDay-to-day once the signal is stable
fail-on-incompleteFails on missing captures, dimension shifts, or errorsCatch broken or partial captures
strictFails on any drift signal whatsoeverLock 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

Ready to add visual testing to your CI?

snapdrift is free and open source. Snap starts at $9/month for hosted baselines.