Match your CI capture environment
Understand environment warnings and make local captures comparable with CI.
What the warning means
Build review shows Environment differs when a successfully compared screenshot and its baseline record different renderer settings. Expand it to see the affected count and fields, then choose Show affected snapshots. This filter combines with your search, variants and status filters. Open Capture details to compare the values.
Difora checks browser name and full version, operating system, device scale factor, locale, time zone and environment ID/revision. OS version is checked when recorded. A known difference counts even if other fields are missing. Missing evidence without a known difference is unknown; it never proves a match. New, removed and failed captures are excluded. Identical PNGs can still have different recorded environments.
These are client-reported values, not independent verification. Matching values do not guarantee identical fonts or browser state. Warnings do not change pixel results, thresholds, review decisions or exit codes. Client 0.11.0 also prints a warning after a completed upload; JSON output stays on stdout and warnings go to stderr. Older clients and builds remain usable.
Pin a capture stack
Use the same image digest, CPU architecture, npm lockfile, browser package and fonts locally and in CI. This example selects Playwright 1.63.0 on Ubuntu 24.04, Linux AMD64. The official image includes browsers and system libraries; install the matching npm package in your project and commit its lockfile.
npm install --save-dev --save-exact @playwright/test@1.63.0 difora@0.11.0
# Commit package.json and package-lock.json.
If your registry has not received the client release yet, install the verified package from the public client releases. The capture helpers are also available in 0.10.1.
Use this immutable AMD64 image reference wherever the examples say
CAPTURE_IMAGE:
CAPTURE_IMAGE='mcr.microsoft.com/playwright@sha256:bc6ab0d6d44ff4826e4cb8c1e6d801e185bfc42bb0753f8e2a30efc70db054c7'
Do not install unpinned fonts or browser updates during capture. For custom fonts, build a derived image with versioned font files and pin its resulting digest. Record a new environment revision whenever this stack changes. Pin application dependencies, test data and time-sensitive inputs separately; a container does not stabilize those inputs.
Record the renderer
// playwright.config.ts
import { defineConfig } from '@playwright/test';
export default defineConfig({
use: {
browserName: 'chromium', viewport: { width: 1280, height: 900 },
deviceScaleFactor: 1, locale: 'en-US', timezoneId: 'UTC',
colorScheme: 'light', reducedMotion: 'reduce',
},
});
// In your test, after loading the page and stable test data:
import { diforaScreenshot } from 'difora/playwright';
await page.evaluate(() => document.fonts.ready);
await diforaScreenshot(page, 'checkout', {
dir: './screenshots', animations: 'disabled', caret: 'hide',
captureMetadata: {
os: { name: 'Linux', version: '24.04' },
environment: {
id: 'playwright-linux-amd64',
revision: 'sha256:bc6ab0d6d44ff4826e4cb8c1e6d801e185bfc42bb0753f8e2a30efc70db054c7',
},
},
});
The helper observes browser and page settings. Declare the renderer OS, not the upload machine's OS. With a remote browser, use the remote browser's image identity. Use the same declared ID and revision locally and in CI; a job number is not an environment revision. Keep explicit theme and variant names stable too.
Run locally
Run from your trusted project checkout. An isolated dependency volume prevents host packages from being reused inside Linux. On Apple Silicon, AMD64 runs through emulation; check the resulting images before assuming equality with native AMD64.
docker run --rm --init --ipc=host --platform linux/amd64 \
-e CI=true -e TZ=UTC -e LANG=C.UTF-8 \
-v "$PWD:/work" -v /work/node_modules -w /work \
"$CAPTURE_IMAGE" \
bash -lc 'npm ci && npx playwright test'
# Upload only after checking your captures:
npx difora upload ./screenshots
GitHub Actions
Use a Linux X64 runner. Keep your existing application startup and upload steps; this replaces the capture environment.
jobs:
capture:
runs-on: ubuntu-24.04
container:
image: mcr.microsoft.com/playwright@sha256:bc6ab0d6d44ff4826e4cb8c1e6d801e185bfc42bb0753f8e2a30efc70db054c7
options: --init --ipc=host
env:
CI: 'true'
TZ: UTC
LANG: C.UTF-8
steps:
- uses: actions/checkout@v5
- run: npm ci
- run: npx playwright test
- run: npx difora upload ./screenshots
env:
DIFORA_TOKEN: ${{ secrets.DIFORA_TOKEN }}
GitLab CI
Select your Docker executor runner with Linux AMD64. Configure sufficient shared memory on that runner. Store the project token as a masked CI variable.
capture:
image: mcr.microsoft.com/playwright@sha256:bc6ab0d6d44ff4826e4cb8c1e6d801e185bfc42bb0753f8e2a30efc70db054c7
variables:
CI: 'true'
TZ: UTC
LANG: C.UTF-8
script:
- npm ci
- npx playwright test
- npx difora upload ./screenshots
Verify your result
Capture the same source, fixtures and routes locally and in CI, then compare PNG hashes and inspect any differing pixels. Record the image digest, architecture, full browser version and font versions alongside that evidence. Keep host-native captures separate from your Linux baseline. Upgrade one pinned stack deliberately, inspect the resulting changes and approve the intended baseline.
For repeated captures that vary within one environment, use Stability history. For helper and sidecar options, see capture metadata. The official Docker guide and visual comparison guide explain browser image compatibility and rendering differences.