SnapDrift — free visual regression
testing for GitHub Actions

Catch unintended UI changes on every pull request. SnapDrift captures each route, diffs it against a baseline, and posts the result as a PR comment — in your own CI, with no account required.

Free & open sourceMIT licensedGitHub Actions native

What SnapDrift is

SnapDrift is a free, MIT-licensed visual regression testing runner for GitHub Actions. It captures full-page frames of your application, compares them against a known baseline, and reports drift directly on the pull request. It is the open-source client behind Snap's hosted Visual CI, and it runs the same comparison engine as the free in-browser visual diff tool.

You keep ownership of checkout, build, startup, readiness, and teardown. SnapDrift takes over once your app is reachable — so it fits the CI you already have instead of asking you to reorganize it.

How it works

Every pull request runs the same loop, driven by two workflow jobs and a single config file:

1
Baseline on main
A merge publishes the accepted screenshots as a baseline artifact
2
Capture each route
SnapDrift starts Playwright and screenshots every configured route
3
Diff against the baseline
Pixel comparison, with unequal sizes still reviewable
4
Report on the PR
An upserted comment lists what drifted, and the run enforces your mode

Quickstart

Two files get you from zero to a reviewed visual run. First, add .github/snapdrift.json:

json
{
  "baselineArtifactName": "my-app-snapdrift-baseline",
  "workingDirectory": ".",
  "baseUrl": "http://127.0.0.1:8080",
  "resultsFile": "qa-artifacts/snapdrift/baseline/current/results.json",
  "manifestFile": "qa-artifacts/snapdrift/baseline/current/manifest.json",
  "screenshotsRoot": "qa-artifacts/snapdrift/baseline/current",
  "routes": [
    { "id": "home-desktop", "path": "/", "viewport": "desktop" },
    { "id": "home-mobile",  "path": "/", "viewport": "mobile"  }
  ],
  "diff": { "threshold": 0.01, "mode": "report-only" }
}

Then wire up the two jobs — one publishes the baseline from main, the other runs on every pull request. Both are pinned to the immutable SnapDrift v0.13.0 release commit:

yaml
name: Visual Regression
on:
  push:
    branches: [main]
  pull_request:

# pr-diff posts its report as a PR comment, so the token needs write access
# to issues and pull requests.
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
      # ... check out, build and start your app ...
      - name: SnapDrift Baseline
        uses: ranacseruet/snapdrift/actions/baseline@3967590687d420479ba5ec351e1394de174c1838 # v0.13.0
        with:
          repo-config-path: .github/snapdrift.json

  pr-diff:
    if: github.event_name == 'pull_request'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      # ... check out, build and start your app ...
      - name: SnapDrift Report
        uses: ranacseruet/snapdrift/actions/pr-diff@3967590687d420479ba5ec351e1394de174c1838 # v0.13.0
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          repo-config-path: .github/snapdrift.json

The full walkthrough — permissions, route scoping, and the hosted provider — is in the Visual CI guide.

Enforcement modes

Set diff.mode to control what a drifting run does. Start permissive and tighten once the signal is stable:

ModeBehaviorWhen to use
report-onlyNever stops the run; posts results for visibilityStart here while baselines settle
fail-on-changesFails when any capture exceeds the thresholdDay-to-day once the signal is stable
fail-on-incompleteFails on missing captures or comparison errorsCatch broken or partial captures
strictFails on any drift signal or incomplete comparisonLock the UI down before a release

What it handles

Baselines on your default branch

A baseline job runs when you merge to main and stores the accepted screenshots. Every pull request then diffs against the latest successful baseline, so drift is measured against what actually shipped.

Pull-request reporting

SnapDrift posts an upserted PR comment with the routes that changed, the diff result, and (with the hosted provider) a link into the review dashboard. No scrolling through raw CI logs to find what moved.

Route scoping from changed files

Map source paths to routes so a pull request only captures the pages it touches. Smaller runs, faster feedback, and fewer unrelated diffs on every comment.

Four enforcement modes

Start at report-only while baselines settle, then move to fail-on-changes, fail-on-incomplete, or strict as the signal becomes trustworthy.

Pluggable backends

Local filesystem by default, or provider: "snap" for hosted Snap baselines. Switch with one config change — the routes and diff settings carry over.

Export baselines, no lock-in

snapdrift migrate-baselines --to local --from snap downloads the project's hosted baselines into the local layout for reproducible debugging or to leave the hosted provider. Adopting the hosted provider is a deliberate step — seed it from the baseline action — not a re-upload of a local bundle.

Local CLI

SnapDrift also ships a CLI for running captures, diffs, and migrations locally against a running app — useful for validating a change before you push:

bash
# Establish a local-provider baseline
snapdrift baseline

# Capture screenshots without publishing anything
snapdrift capture

# Compare against the baseline after making UI changes
snapdrift diff --open

# Export hosted baselines back to the local layout
snapdrift migrate-baselines --to local --from snap

Current constraints

  • Ubuntu runners for the actions; the local CLI runs on any OS Node 22+ supports.
  • Full-page capture only.
  • Viewport presets desktop (1440×900) and mobile (390×844), or a custom { width, height }.
  • One global diff.threshold.
  • Unequal dimensions compare on a top-left-aligned union canvas, so a size change still produces a reviewable diff instead of failing outright.

Free forever, or scale without the runner

SnapDrift is free because it runs on your infrastructure. When running Playwright inside CI starts to cost more than it is worth, Snap's hosted provider is a drop-in upgrade: change provider to "snap" and Snap renders your preview deploy on its Lambda fleet, keeps baselines in S3 that don't expire, and adds a review dashboard — the same routes, the same diff config, no workflow rewrite.

Try the engine for free first. The in-browser visual diff tool uses the same comparison engine as SnapDrift — drop in two images and see exactly what a run reports. Need raw captures instead of diffs? The free screenshot API captures any public URL with no signup.

Frequently asked questions

Is SnapDrift really free?

Yes. SnapDrift is MIT-licensed and free to run in your own GitHub Actions. There is no account, no API key, and no per-screenshot charge for local mode. You bring the runner; SnapDrift handles capture, diff, and reporting.

Does SnapDrift need a Snap account?

No. Local mode works with just the two workflow jobs and a .github/snapdrift.json file. A Snap account is only needed if you opt into provider: "snap" for hosted baselines and Lambda-rendered captures.

How is this different from Snap's hosted Visual CI?

SnapDrift is the open-source client you run yourself: it installs Playwright on your runner, starts your app, and keeps baselines as GitHub artifacts. Hosted Visual CI is the managed upgrade — Snap renders your preview deploy on its Lambda fleet, stores baselines in S3 that don't expire, and adds a review dashboard. Same workflow, less infrastructure to own.

When should I move from SnapDrift to the hosted provider?

When baseline artifacts expiring (30 days by default) becomes a problem, when installing Playwright and starting your app on every run costs too many runner minutes, or when you want reviewers to triage and approve diffs in a dashboard instead of PR comments.

Does SnapDrift work outside GitHub Actions?

The local CLI (snapdrift capture, diff, and migrate-baselines) runs anywhere Node 22+ is supported, so you can validate UI changes during development. The action-based baseline and PR-diff pipelines are built for GitHub Actions, and the hosted provider path is designed for multiple CI systems.

Add visual testing to your next pull request

SnapDrift is free and open source. Start with local mode today, and upgrade to hosted Visual CI only when you want Snap to run the renders for you.