Your first visual review with Difora

Capture in your CI, compare in the EU, and approve the changes you intend.

Install the CLI (Node 24.22.1+): add difora from npm (or read the GitHub README if npm does not load) to your screenshot project: npm install --save-dev difora. Commit package.json and package-lock.json, then run npm ci in CI before the capture and upload commands below.

Already signed in? Open Getting started in the app for the short guide and CI provider setup.

1. Create your account and project

Create your free account, confirm your email and sign in. No personal invitation or credit card is required. Your organization owner reviews the complete service agreement and DPA under Agreements and explicitly accepts them for the organization before uploading screenshots. Create a project for the suite you want to review. Free includes one project and 5,000 submitted snapshots per month; unchanged screenshots count too. Paid plans require a separate order.

Can one project serve several repositories? We recommend one project per repository. Snapshot names share a project namespace, so repositories need distinct prefixes such as site-a/home and site-b/home. Prefixes avoid name collisions, but each build must still contain the complete suite (or combine it with shards): names missing from the compared baseline are reported as removals. A project has one repository integration.

2. Save your project token

Copy the CI token when it is shown. Store it as a secret named DIFORA_TOKEN in your CI provider. You can issue or revoke tokens in project Settings → CI tokens. Keep tokens out of your source code and logs.

3. Capture a stable suite

Add screenshots/, test-results/ and playwright-report/ to .gitignore.

Configure tests to save screenshots to a fresh screenshots directory. Use the manual Playwright or Cypress recipe, or the Playwright/Storybook helpers included in the CLI release package. A plain Playwright test does not save a screenshot unless you explicitly capture one.

Use deterministic test data and wait for the page to be ready. Keep names, fonts, viewport and browser consistent. For example, home@1280.png and home@640.png represent separate snapshots.

Start with the complete Playwright config and static-site recipe. Save this test as tests/visual/home.spec.ts:

import { test } from '@playwright/test';
import { diforaScreenshot } from 'difora/playwright';

test('home', async ({ page }) => {
  await page.goto('/');
  await page.evaluate(() => document.fonts.ready);
  await diforaScreenshot(page, 'home', { viewportSuffix: true });
});

withDifora(test) adds a screenshot fixture to your existing Playwright test. Storybook's test-runner uses createPostVisit from difora/storybook; Cypress has a capture recipe. See the complete capture setup for each runner.

4. Add capture and upload to CI

Save this as .github/workflows/visual.yml. It uses the capture config above and your site's build script; omit that build step for a no-build site. The Playwright config starts the server. See the CI reference for optional commit statuses. Install other browsers only if your projects use them:

name: Visual review
on: { push: { branches: [main] }, pull_request: {} }
jobs:
  visual:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5
        with: { fetch-depth: 0 } # merge-base comparisons need full history
      - uses: actions/setup-node@v5
        with: { node-version: 24 }
      - run: npm ci
      - run: npx playwright install --with-deps chromium
      - run: npm run build
      - run: npx playwright test # produces ./screenshots/*.png
      - run: npx difora upload ./screenshots
        env:
          DIFORA_TOKEN: ${{ secrets.DIFORA_TOKEN }}

Difora detects the branch and commit. The first build normally exits 1 because new snapshots need review; the printed URL opens the build. Other providers and optional commit statuses are covered in the CI guide.

Run pull requests and pushes to main only so a PR branch is not uploaded twice; each new build counts toward snapshot usage.

5. Approve the first baseline

Baseline environment: Approve baselines only from builds captured in CI, or in the exact same environment CI uses. Mac and Windows captures can render fonts differently from Linux CI, causing visual differences on every page even when the app has not changed.

Outside CI, the CLI detects branch and commit from your local Git checkout. To test the pipeline locally, use an explicit scratch branch and leave that build unapproved:

DIFORA_TOKEN='<token>' npx difora upload ./screenshots --branch local-check

Replace <token> with your project token in the shell. Keep the token only in a shell variable or CI secret, never in a file.

Open the build URL, inspect each screenshot, and approve the intended result. Set the project's baseline branch (usually main) in General settings and start with an approved run on that branch. New branch builds use those baselines until branch-specific approvals override them.

Run the same suite again: unchanged screenshots should pass. Change one visible detail, run it again and inspect the comparison before approving or rejecting it. A screenshot deliberately removed from the suite appears as a removal to review.

6. Connect statuses and notifications

General settings can configure repository statuses. GitHub needs the Difora GitHub App; GitLab and Bitbucket use a repository token. Alternatively, the CLI's --post-status option uses CI credentials. Integration setup depends on the provider.

The server integration also keeps a PR/MR summary comment updated. GitHub App installations need Checks and Pull requests read/write; existing installers accept the new permission once. The CLI detects the PR number, or you can pass --pr 12. Screenshot thumbnails are off by default. Owners may opt in, but anyone with a thumbnail URL can view it and repository image proxies may cache it outside the EU. Provider setup and thumbnail privacy.

Project owners can add email, Slack or signed-webhook channels under Settings → Notifications. Start with build.unreviewed and build.error, send a test, and check delivery history. Organization members can view channels and history.

Optional: scale to parallel suites

Each CI shard captures a different part of the suite. Upload with --shard 1/2 in one job and --shard 2/2 in the other, using the same token and pipeline identity. The last shard starts one comparison for the combined set. Snapshot names must be unique across shards. A retry can copy the other shards from the previous attempt. Use a new --parallel-id if the partition, commit or rules change. Matrix examples, timeouts and retry rules.

Optional: ignore intentional noise

Add difora.config.json to your screenshot directory or use --config. Repository rules take priority over saved UI rules; the most specific pattern wins and omitted thresholds inherit project values.

{
  "version": 1,
  "rules": [{
    "match": "home*",
    "ignore": [{ "x": 10, "y": 10, "width": 100, "height": 40 }]
  }]
}

Or open a snapshot, choose Ignore regions, draw a rectangle and Save as rule. Re-run the build to apply it. Coordinates use original screenshot pixels; masked pixels are excluded from the comparison ratio. Dimension changes still count. Keep the same rules on every shard and retry. Patterns, precedence, limits and JSON schema.

Understand merge-base comparisons

Keep full Git history and fetch your target branch. Difora uses the exact retained, passed or approved baseline-branch build at the merge-base commit, then applies branch-local approvals. The review header shows the anchor or the fallback to current branch baselines. Missing history produces a CLI warning; --no-merge-base opts out. History setup and anchor rules. Settings → Branches lets owners clean stale baselines; automatic cleanup defaults to 60 inactive days and protects active builds and open reviews.

Understand the outcome

--exit-zero-on-changes allows a pilot to upload without failing CI for pending review. --no-wait returns after upload and does not wait for the comparison.

Screenshot processing and storage use EU-owned infrastructure. Your organization controls which members can review projects and which notification or repository integrations receive build metadata.