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
- Passed / approved: CLI exit 0.
- Needs review, rejected or comparison error: exit 1; open the review link.
-
Upload or usage failure: exit 2; check the message
and
npx difora doctor. - Wait timed out: exit 3; processing continues on the server.
--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.