How to Fix Font Rendering Differences in Happo Screenshots
Trace Happo font mismatches to loading, blocked requests, browser behavior, or glyph fallback. Follow a practical debugging sequence before tuning diff tolerance.
Start with the affected Happo snap-request logs, then verify that the font request succeeds from the capture worker and that the intended font is ready when the screenshot is taken. Compare the same page state across browser targets. If the font loaded and the difference is limited to small rasterization noise, adjust comparison tolerance; if the font fell back, a request failed, glyph coverage differs, or line wrapping changed, fix that cause first.
This order matters because a fallback font can change text width and layout. Hiding that difference with a permissive pixel threshold makes the report quieter without making the screenshot correct.
1. Find the failing capture in Happo logs
Open the report’s logs and find the affected component and snap-request. Happo’s report logs can help surface a font that took too long, a missing asset, or asynchronous work that completed after capture. Search for the component name, then inspect the surrounding messages and identify the browser target that produced each line. Happo’s report logs guide
- Locate a component whose typography is visibly wrong in the report.
- Check whether the same component differs in every target or only one.
- Look for font request failures, timeouts, blocked-host messages, and capture timing warnings.
- Record the target, request URL, and whether the issue reproduces on a fresh run.
A passing test run does not prove that the intended font file loaded. Inspect the asset and capture evidence rather than inferring font readiness from the overall run status.
2. Check whether the font request reaches the worker
A failed font stylesheet or font-file request usually leaves the page rendering with a fallback. Check the complete request chain: the stylesheet URL, the font URL referenced by its CSS, redirects, response status, and whether the final host can be reached by the Happo worker.
Happo’s guidance on allowedHostnames describes restricting outgoing requests and notes that blocked font requests can produce fallback rendering. The behavior and default depend on the npm package version and configuration in use, so check the relevant configuration for your installed version. Inspect worker logs for allowed and blocked hosts, and allow the actual font-serving host when the restriction is enabled. Happo’s hostname access guidance
- Font hosted by your app: confirm the worker can access the asset host and that the URL works outside your logged-in browser session.
- Font hosted by a provider or CDN: allow the final host after redirects, not only the stylesheet host.
- Private or authenticated font: ensure the capture has the required access. A font that loads only in a local browser with a cached session can fail in a clean worker.
- Cross-origin font: inspect the browser’s console and response headers for CORS failures.
- Restricted outbound access: check the Happo configuration and worker logs for blocked requests; use the exact option and syntax documented for your installed version.
Do not copy an allowedHostnames snippet from a different Happo package version without checking the current integration documentation. The available source material does not establish one universal configuration for every integration.
3. Make capture wait for fonts
Happo documents waiting for fonts and asynchronous assets in its capture workflow. If a mismatch remains, inspect the integration’s capture state and logs to determine whether the intended font finished loading before the snapshot. Happo documentation
For a page you control, the browser Font Loading API provides a direct readiness check. Run this in the page context after the application has rendered the component and before capture:
await document.fonts.ready;
const status = document.fonts.check('16px "Example Sans"');
console.log({ fontSetStatus: document.fonts.status, exampleSansAvailable: status });
Replace Example Sans with the family actually used by the affected text. document.fonts.ready resolves when the document’s font loading and layout operations have settled; it does not guarantee that a particular remote font request succeeded. Check the specific family with document.fonts.check() and inspect the request logs as well.
If your integration supports a custom wait condition, wait for the application’s rendered state and font readiness using that integration’s documented mechanism. Avoid guessing a fixed delay: a delay can mask a slow request on one run while still racing on another. A delay is useful only as a diagnostic experiment, not as proof that the correct font loaded.
4. Compare browser behavior and capture state
Compare like with like: same route, data, viewport, device scale, application state, font request, and capture point. Then inspect whether the mismatch is present after the font is ready in each target.
Font loading can look different between browsers. Google Fonts documents Chrome and Safari displaying blank space for web-font text while the font loads, while Firefox can display the default font and redraw with the web font after it arrives. A screenshot captured during those transient states can therefore differ even when the final page converges. Google Fonts: technical considerations
| What you see | Likely explanation | Next check |
|---|---|---|
| Fallback face in one target | Font request failed, was blocked, or was not ready at capture time | Worker logs, request URL and status, font readiness |
| Blank text in one capture | Capture may have landed during font loading | Capture timing and browser-specific loading state |
| Only emoji or a few symbols differ | Glyph coverage or fallback-font differences | Which font supplies those code points in each target |
| Same font and layout, tiny edge differences | Rasterization or anti-aliasing noise | Whether a small color-delta tolerance is appropriate |
| Text wraps differently or elements move | Different font metrics, weight, size, or loaded state | Font file, weight mapping, CSS, and readiness |
Happo documents captures across browser targets including Chrome, Firefox, Safari, Edge, and iOS Safari. Use the targets configured for your project and confirm which target each snap-request represents; do not assume every environment has the same font-loading state. Happo integration documentation
5. Check glyph coverage and font selection
If ordinary letters match but emoji, accented characters, icons, or particular symbols do not, investigate glyph coverage before treating the result as generic browser noise. The requested family may not contain those characters, in which case the browser selects fallback fonts. Those fallback choices can vary by platform and target.
- Identify the exact characters that differ.
- Check whether the intended font contains those glyphs, including the language or Unicode subset in use.
- Inspect the CSS family list and the actual font files requested for each weight and style.
- Check whether an icon font or emoji font is supplying the affected glyphs.
- Compare the same character on each configured browser target after font readiness.
Happo has shown emoji varying across browser targets in a historical cross-browser example. Treat that as a clue for character-specific mismatches, not as an explanation for all typography differences. Happo’s cross-browser examples
6. Tune tolerance only after fixing loading problems
Happo’s color-delta tolerance can ignore small differences such as anti-aliasing and image-compression noise. Use it only after confirming that the intended font loaded and the remaining variation is acceptable. Do not raise tolerance to conceal fallback text, missing fonts, changed line wrapping, shifted elements, or a broken asset. Happo comparison documentation
Make a small change and review the resulting diff. If the mismatch changes the geometry or meaning of the page, tolerance is not the fix. Keep tolerance consistent across comparable snapshots so the threshold does not make important typography regressions invisible.
7. Keep font captures reliable and fast
- Load only the fonts and weights the page needs. Extra font files add requests and can extend the time before a stable capture. Google Fonts recommends requesting only the styles used by the page. Google Fonts CSS API guidance
- Prefer stable, reachable font assets. A remote stylesheet and its font files introduce external network dependencies. If policy permits, serving versioned font files with the application can make the asset path easier to control.
- Use a deterministic capture state. Fix data, viewport, locale, and interaction state so font changes are not obscured by unrelated differences.
- Wait on readiness rather than adding large sleeps. A readiness condition avoids paying a fixed delay on every capture while still accounting for font loading.
- Keep a sensible fallback stack. Fallbacks improve resilience for users, but their metrics can differ. They should not be mistaken for the intended font in a visual baseline.
For cost and reliability, avoid repeatedly rerunning a full matrix when one target and one component can isolate the failure. Start with the affected snap-request, then rerun the narrowest useful case after correcting access or readiness. The available Happo documentation cited here does not establish a universal per-run price or a numeric performance guarantee, so check your account and integration details for those specifics.
Common errors and fixes
| Symptom or log clue | Cause to investigate | Fix |
|---|---|---|
| Font URL returns 404 | Wrong asset path, deployment omission, or stale CSS URL | Correct the deployed URL and verify the exact file requested by the worker. |
| Font request is blocked | Worker host restriction or unreachable external host | Review worker logs and the active package configuration; allow the actual font host when appropriate. |
| Font is correct locally but fallback appears in Happo | Local cache, credentials, or network access differs from the worker | Inspect the worker request and make the asset reachable under the capture’s access model. |
| Intermittent fallback or blank text | Capture races font loading or an asynchronous render | Wait for the relevant render and font readiness using documented integration controls; inspect logs. |
| Only bold or italic text differs | Missing weight/style file or incorrect @font-face mapping |
Verify the requested weight and style exist and map to the intended file. |
| Only symbols or emoji differ | Missing glyph or platform fallback choice | Check coverage and fallback selection for those code points. |
| Diff remains as fine pixel noise | Anti-aliasing or rasterization variation | After confirming font and layout, apply a carefully reviewed small tolerance. |
Or skip the browser setup
If you need a clean screenshot of a URL without maintaining browser capture setup, ScreenshotNeo is a website screenshot API and MCP server. It returns a screenshot or PDF from one GET request. It is useful for page captures and documentation workflows; it does not replace Happo’s visual regression comparison or diagnose a font mismatch inside your Happo worker.
See the ScreenshotNeo API documentation. This cURL example saves a WebP screenshot of the target page:
curl -G "https://api.screenshotneo.com/v1/shot" \
-d access_key=YOUR_API_KEY \
--data-urlencode url=https://stripe.com \
-o shot.webp
Python:
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
timeout=90,
)
r.raise_for_status()
with open("shot.webp", "wb") as f:
f.write(r.content)
Node.js:
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(`Screenshot request failed: ${res.status}`);
const bytes = new Uint8Array(await res.arrayBuffer());
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', bytes));
- Cookie banners, popups, and chat widgets are removed before the shot; each cleanup step can be turned off.
- Bot checks, blank pages, and failed loads are never billed; response headers report the page verdict and billing status.
- An MCP server lets AI agents use screenshot, page-info, and PDF tools.
- 1,000 screenshots a month are free with no card; paid plans start at $5 for 3,000.
Sign up for ScreenshotNeo’s free 1,000 screenshots a month, with no card required.
FAQ
Why did my Happo screenshot use a fallback font?
Most often, the font request failed or was blocked, or the capture happened before the intended font became ready. Check the snap-request logs, the worker’s access to the font host, and font readiness.
Why do fonts look different in Chrome and Firefox screenshots?
They can expose different intermediate loading states, and their rendering environments can differ. Compare after the intended font is loaded in each target; then check glyph fallback and small rasterization variation.
Should I disable web fonts in visual tests?
Only if the product intentionally does not depend on those fonts. Otherwise, ensure the intended assets load and the capture waits for them so the test represents the page users should see.
Will ScreenshotNeo fix a Happo font mismatch?
No. ScreenshotNeo captures a URL and removes supported consent banners, popups, and chat widgets. It does not change Happo’s worker configuration or replace Happo’s visual regression workflow.


