Font rendering differences in visual tests
Fonts are the silent killer of visual test stability. Learn why the same font looks different across environments and how to achieve consistent rendering.
What causes font differences
Font rendering is surprisingly complex. The same font file can produce visually different output depending on the operating system, browser, display, and configuration.
OS rasterizers differ
macOS, Windows, and Linux use different font rendering engines. The same font file produces visually different output on each platform.
Fallback font substitution
When a font isn't available, the browser substitutes a fallback. Different systems have different fallbacks, causing visible differences.
Subpixel antialiasing
Subpixel rendering varies by display type and OS settings. ClearType on Windows differs from macOS subpixel rendering.
Font hinting differences
TrueType hinting instructions are interpreted differently across renderers. This affects character spacing and shape at small sizes.
These differences are subtle individually but compound across a page. A screenshot from macOS compared to Linux CI will show differences on nearly every text element.
Fixes that actually work
The goal is to ensure your screenshots are captured in an identical environment every time—regardless of who runs them or where.
Pin environment with Docker
Run visual tests in a Docker container with fixed OS and font configuration. This is the most reliable approach for CI.
Install consistent fonts
Ensure the same fonts are installed in all environments. Don't rely on system fonts that vary by OS.
Use web fonts
Web fonts are bundled with your app, ensuring consistency. But you must wait for them to load before capturing screenshots.
Force font smoothing
CSS font-smoothing properties can normalise rendering. Use antialiased rendering to reduce subpixel variance.
Pin your environment with Docker
The most reliable approach is to run visual tests inside a Docker container. This gives you complete control over the OS, font packages, and rendering configuration.
FROM mcr.microsoft.com/playwright:v1.40.0-jammy
# Install fonts your app uses
RUN apt-get update && apt-get install -y \
fonts-liberation \
fonts-noto-color-emoji \
&& rm -rf /var/lib/apt/lists/*
# Optional: install custom fonts
COPY ./fonts /usr/local/share/fonts
RUN fc-cache -fvSee Docker setup for deterministic rendering for a complete configuration guide.
Use web fonts and wait for loading
Web fonts are bundled with your application, ensuring the same font files are used everywhere. But you must wait for them to load before capturing screenshots.
In Playwright, wait for fonts explicitly:
// Wait for all fonts to load
await page.evaluate(() => document.fonts.ready);
// Or check specific fonts
await page.evaluate(async () => {
await document.fonts.load('16px "Inter"');
});Font loading can be slow on first page load. If you're seeing fallback fonts in screenshots, increase your wait time or add an explicit font load check.
Force consistent font smoothing
CSS can help normalise font rendering across platforms. Force antialiased rendering to reduce subpixel variance:
/* Add to your test environment CSS */
* {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-rendering: geometricPrecision;
}This reduces but doesn't eliminate differences. Different operating systems still interpret these properties differently. It's most effective when combined with a pinned Docker environment.
Debugging font issues
When you see unexpected font diffs, here's how to identify the root cause:
- Check computed styles: Use DevTools to verify which font is actually rendering. The computed font-family shows the resolved font, including fallbacks.
- Compare font metrics: Capture the same text in both environments and overlay them. Font rendering differences manifest as slightly different character widths and heights.
- Verify font loading: Check
document.fonts.statusanddocument.fonts.check()for specific fonts. FOUT (flash of unstyled text) suggests incomplete font loading. - Isolate the variable: Run the same test in Docker locally and in CI. If they match, the issue is local environment. If they differ, check Docker image consistency.
Font determinism checklist
- Run visual tests in a consistent environment (Docker recommended)
- Install required fonts explicitly in your CI environment
- Use web fonts instead of system fonts where possible
- Wait for document.fonts.ready before capturing screenshots
- Set consistent font-smoothing CSS across all text
- Generate baselines from CI, not local development machines
- If using fallback fonts, verify they're identical across environments
- Test that emoji and icon fonts render consistently
Related guides
Frequently Asked Questions
Why do my screenshots differ between macOS and Linux CI?
Should I commit baselines from my Mac or from CI?
How do I ensure web fonts are loaded before screenshots?
Can I use system fonts and still have consistent tests?
What's the easiest fix for font differences?
Do font differences affect pixel-diff thresholds?
Eliminate font rendering headaches from visual testing
Get early access