Visual testing in CI: making screenshots consistent
Most visual testing pain comes from environment differences between local and CI. Learn how to make your CI pipeline produce reliable, reviewable screenshots.
Why CI produces different screenshots
Your tests pass locally but fail in CI. The screenshots look almost identical—but "almost" fails a pixel comparison. This isn't flakiness. It's environmental drift.
Operating system rendering
macOS, Linux, and Windows render fonts differently. Anti-aliasing algorithms vary even between Linux distributions.
GPU and acceleration
Hardware rendering produces different results than software rendering. CI runners often lack GPU access entirely.
Browser versions
Even minor browser updates can change rendering. Your local Chrome may differ from CI's pinned version.
Screen scaling and DPI
Retina displays, HiDPI settings, and viewport scaling affect pixel output. CI environments have their own display configurations.
These differences are real. The pixels genuinely differ between environments. The question is whether those differences matter—and how to eliminate the ones that don't.
Make the environment deterministic
Consistency is the goal. Your CI environment should produce identical screenshots every time, matching exactly what baselines were captured against.
Use containerised browsers
Docker containers provide identical OS, fonts, and browser versions across all environments.
Pin browser versions
Lock to specific browser releases. Auto-updating browsers will eventually break your baselines.
Install consistent fonts
Either use web fonts that load before capture, or install identical system fonts in your CI image.
Disable GPU rendering
Force software rendering for consistent output. It's slower but eliminates hardware variance.
The effort here pays dividends. A stable environment means failures are real issues, not environmental noise. For more, see reducing visual testing flakiness.
Keep pipelines fast
Visual tests are slower than unit tests. Plan your pipeline accordingly:
- Test selection: Run full visual suite on main branch PRs; smoke tests on feature branches
- Parallelisation: Distribute tests across multiple workers to reduce wall-clock time
- Separate stages: Consider running visual tests in a parallel pipeline that doesn't block unit test feedback
- Nightly full runs: For large suites, run comprehensive visual tests overnight rather than on every commit
The goal is fast feedback for most changes, thorough validation before merge.
How to think about baselines in CI
Baselines should be captured in the same environment they'll be tested against. If CI is your test environment, CI should also be your baseline capture environment.
This means baseline updates happen through CI, not local machines. It's more friction, but it eliminates the "works on my machine" problem for visual tests.
For structured approaches to baseline management, see baseline management best practices.
When to block merges vs report only
Not every visual test needs to block deployment. Consider a tiered approach:
- Block: Design system components, checkout flows, anything where visual bugs directly impact users or revenue
- Report: Experimental features, rapidly iterating pages, areas where design is still being explored
- Nightly audit: Full suite runs that surface drift without blocking daily development
Build confidence gradually. Start in reporting mode, prove stability, then escalate to blocking for critical paths.
Quick checklist
- Use containerised or pinned browser environments
- Install consistent fonts in CI images
- Disable GPU rendering for software consistency
- Configure fixed viewport and display settings
- Decide on blocking vs reporting mode
- Set up baseline update approval workflow
- Document environment requirements for the team
Related guides
Frequently Asked Questions
Why do screenshots differ in CI?
Do I need Docker for consistent screenshots?
Should visual tests run on every PR?
How do we stop flaky failures from blocking deploys?
How do teams manage baseline updates safely?
How do we handle font rendering differences?
Should we run visual tests on feature branches?
How do we make visual tests faster in CI?
Join the waitlist for CI-friendly visual testing
Get early access