ScreenshotNeo

BlogHow-to

How to Fix Percy Visual Diffs Caused by Font Loading

Find out whether a Percy font diff starts in your test browser, asset discovery, or rendering, then fix the cause without hiding real regressions.

By the ScreenshotNeo team4 October 20268 min read

A Percy font diff usually means the intended font was unavailable at one stage of the capture pipeline. Check whether the page uses the right font immediately before the snapshot, inspect Percy’s asset and network errors, make the font host accessible, and wait for the application’s real ready state. If the font is requested only after scrolling or interaction, trigger that behavior before capture. Increase Percy’s asset-discovery idle timeout only when network evidence shows the font request arrives late.

1. Find which stage changes the font

Percy’s process has more than one stage: the SDK captures the DOM in the test browser, then Percy discovers assets and renders screenshots in its own infrastructure. A page can therefore look correct before the snapshot and use a fallback font in Percy if the font request is late, blocked, or not discovered. Percy SDK and screenshot capture workflow

  1. Open the page in the test browser immediately before the Percy snapshot.
  2. Check the affected text’s computed font and inspect the browser network panel for the actual font file request and response.
  3. Compare that state with the Percy result. If the test browser is already wrong, investigate application loading or test readiness. If only Percy is wrong, inspect the Percy build’s asset and network errors.
  4. Record the exact font URL, request status, host, and whether the request happens before or after the snapshot. This narrows the fix to readiness, access, discovery timing, or lazy loading.

A CSS @font-face rule proves only that a font is referenced; it does not prove that Percy can retrieve the file. Verify the request itself.

2. Check font delivery and access

In the Percy build, look for failed CSS or font requests and the host they came from. BrowserStack’s troubleshooting guidance calls out failed font or CSS resources, hosts that may need allowlisting, slow elements, and lazy-loaded assets. Troubleshoot common issues

  • Request fails in the test browser: fix the application’s font URL, server response, CORS configuration, or authentication so the browser can load the intended font.
  • Request works in the browser but fails in Percy: check Percy’s reported network error and add the failed asset host to the allowed hostnames when required by your project configuration.
  • Font needs authentication: investigate the authentication or asset access configuration for Percy’s discovery stage. Do not treat an authorization failure as a timing problem.
  • Request is redirected or returns a different file: inspect the final URL and response in the build diagnostics; make the intended asset available on the path Percy retrieves.

Do not assume that because the page’s CSS loads, a cross-origin font file is available too. Diagnose the exact font request and response.

3. Wait for the application’s actual ready state

Take the snapshot only after the app reaches the state the test is meant to capture. Prefer an application-specific signal, such as a page-ready selector, over an arbitrary sleep. Percy’s CLI troubleshooting documentation describes waitForSelector and waitForTimeout configuration; use a timeout when measured behavior calls for one, not as a default guess. The documented snapshot options do not provide a universal dedicated “wait for fonts” snapshot option. Percy snapshot command options

In scripted browser tests, you can also check that required fonts are ready in the test browser before calling Percy. This is a useful precondition, but it does not prove that Percy’s later asset discovery retrieved the font. Always confirm the rendered result in the Percy build.

// Playwright-style example: adapt the selector and snapshot call to your test setup.
await page.goto('https://example.com');
await page.locator('[data-testid="page-ready"]').waitFor();
await page.evaluate(async () => {
  await document.fonts.ready;
  if (!document.fonts.check('16px "Brand Sans"')) {
    throw new Error('Brand Sans is not available in the test browser');
  }
});
await percySnapshot(page, 'Ready page');

document.fonts.ready and document.fonts.check() check browser-side font readiness and availability for the specified font shorthand. Replace the example family and selector with your application’s real values. A successful browser-side check does not replace checking Percy’s asset discovery.

4. Tune Percy asset discovery only when the request is late

Percy’s workflow documentation says asset discovery waits by default for 100 ms with no new network requests. If the build evidence shows the font request starts late or arrives outside that window, increase the relevant network-idle-timeout in Percy configuration or CLI use, then compare the next build. Asset discovery timing and workflow

Use the smallest increase that covers the observed delay. A longer idle window cannot repair a 403, inaccessible host, incorrect URL, or a font that is never requested. Large blanket waits slow capture and can hide an underlying delivery issue.

5. Trigger lazy-loaded fonts at the right stage

If the font or its stylesheet is requested only after scrolling, opening a menu, or another interaction, cause that behavior before the snapshot. For CLI snapshots, Percy documents a beforeSnapshot technique that scrolls the asset-discovery browser so lazy resources enter the viewport and are requested. Capturing lazy-loaded elements in Percy snapshots

// Example beforeSnapshot hook for a CLI snapshot configuration.
// Adapt the configuration shape to the Percy CLI version in your project.
module.exports = {
  beforeSnapshot: async (page) => {
    await page.evaluate(async () => {
      window.scrollTo(0, document.body.scrollHeight);
      await new Promise((resolve) => setTimeout(resolve, 100));
      window.scrollTo(0, 0);
    });
  },
};

The short delay here is illustrative; choose a readiness condition or measured delay appropriate to the page. In browser-driven tests, perform the scroll or interaction in the test before asking Percy to snapshot. Confirm the font request appears in Percy’s build diagnostics afterward.

6. Use Percy-specific CSS only for an intentional rendering change

Percy-specific CSS applies in Percy’s rendering environment and can be configured per snapshot or globally. It can set an intentional test style, but it cannot make an inaccessible font file load. Do not replace the intended typeface with a fallback just to make diffs disappear if the product relies on the real font. Fix delivery and access first. Percy-specific CSS

7. Choose the fix by evidence

Evidence Likely stage Action Risk to watch
Fallback already appears before snapshot Application or test browser Fix font loading; wait for the app’s real ready signal A Percy-only workaround can hide the app defect
Font works in test browser; Percy logs a failed request Asset discovery access Allow the host or fix asset authentication and URL access A longer timeout will not fix a denied request
Network log shows font request starts late Asset discovery timing Increase the relevant network-idle timeout based on observed delay Excessive waits slow builds and obscure causes
Request appears only after scrolling or interaction Lazy loading Trigger the behavior in the test or use CLI beforeSnapshot Waiting without triggering does not request the asset
Font is deliberately different in Percy Percy renderer Use Percy-specific CSS to express the intended test state Forced substitute fonts mask real regressions

8. Troubleshooting common symptoms

Symptom Likely cause Fix
Percy shows a system or fallback font The intended font request failed, was blocked, or was not discovered Inspect the exact request in the build; fix host access, authorization, or URL.
Test screenshot looks right, Percy does not Percy performs asset discovery and rendering after DOM capture Review Percy network errors and discovery timing; browser readiness alone is insufficient.
Adding a fixed sleep changes results intermittently The delay does not represent a stable application-ready condition Wait for a page-specific selector or state; use timeout configuration only with timing evidence.
Increasing timeout has no effect The host is blocked, the request is unauthorized, or no trigger causes the request Resolve access or lazy-load behavior before changing the idle window again.
Only content below the fold has a font issue Lazy resources were never requested during discovery Scroll or trigger the content before snapshot; for CLI, configure the documented hook.
Percy CSS makes the diff disappear A rendering override may be forcing a substitute style Remove the override unless that altered appearance is intentionally what the test should verify.

9. Performance, reliability, and cost notes

Waiting for a specific readiness signal is usually more predictable than adding a broad delay to every snapshot. Increasing the network-idle timeout can add capture time, so tune it only when build evidence shows late requests. Fixing host access and triggering lazy resources address the cause and tend to make repeated builds more reliable than relying on a timing guess.

No font-specific Percy performance benchmark or cost figure is established by the cited documentation. Review your project’s own build duration and usage when changing timeouts. Avoid suppressing font diffs: a missing font can alter wrapping, element dimensions, and downstream layout, so the visual difference may represent a real delivery regression.

Or skip the browser setup

If your goal is to capture a URL as an image or PDF without maintaining a browser capture setup, ScreenshotNeo is a website screenshot API and MCP server. Its API accepts one GET request for a URL and returns a PNG, JPEG, WebP, or PDF. See the ScreenshotNeo API documentation.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`ScreenshotNeo returned ${res.status}`);
await Bun.write('shot.webp', res);
  • Cookie banners are accepted like a visitor and removed along with 60+ known consent platforms, newsletter popups, and chat widgets before the shot; each step can be turned off.
  • Bot checks, blank pages, timeouts, failed loads, and cache hits cost nothing; response headers report the page verdict and billing status.
  • An MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients.
  • 1,000 screenshots a month are free with no card; paid plans start at $5 for 3,000 screenshots.

Sign up for ScreenshotNeo and get 1,000 free screenshots a month, with no card required.

FAQ

Does Percy have a universal wait-for-fonts option?

The cited snapshot options do not document a universal dedicated option. Wait for your application’s readiness in the test, and verify font retrieval in Percy’s later asset-discovery stage.

Does document.fonts.ready guarantee Percy will render the correct font?

No. It checks readiness in the test browser. Percy separately discovers and fetches assets after DOM capture.

Should I use a Percy CSS override to stabilize typography?

Only when the altered typography is intentionally part of the Percy test state. It does not repair a font file Percy cannot retrieve.

What if the page uses a system font?

System fonts can differ between rendering environments. If consistent typography is required, use an accessible web font and verify its delivery in Percy; do not assume identical system font availability.