Fix Screenshot API Captures with Missing Web Fonts
Learn how to diagnose missing or fallback fonts in screenshots, wait for used web fonts, and troubleshoot browser automation captures.
If a screenshot has blank text or the wrong typeface, wait for the page’s used fonts to finish loading before capturing it. In browser automation, evaluate document.fonts.ready after the page has reached the application state you intend to capture, then take the screenshot. Navigation completion, a generic delay, or network-idle state is not a substitute for checking the document’s font set.
document.fonts is the document’s FontFaceSet. Its ready promise fulfills when loading and layout operations for fonts used by the document have finished. It does not mean every font declared in CSS has loaded. See MDN’s document.fonts reference and the CSS Font Loading API guide.
1. Wait for fonts before capturing
Here is the basic Playwright pattern. Replace the selector with one that represents real application readiness on your page.
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
await page.waitForSelector('[data-page-ready]');
await page.evaluate(() => document.fonts.ready);
await page.screenshot({ path: 'capture.png', fullPage: true });
await browser.close();
})();
Install Playwright with npm install playwright and install its browser with npx playwright install chromium. The snippet is a usage pattern; choose the browser and readiness condition that match your application. The Playwright Page API documents navigation, waiting, and screenshot methods.
The sequence matters: first wait for navigation, then for the application state, then for used-font loading and related layout, then capture. If your app changes route content, text, or styles after the initial readiness check, wait for that change and evaluate document.fonts.ready again before taking the screenshot. This follows from the promise covering fonts used by the current document and its layout operations.
2. Check whether the intended font is actually available
If waiting does not fix the result, inspect the affected element and font faces from the page context. This diagnostic logs the computed family, the document font-set status, and known faces with their status.
const fontReport = await page.evaluate(() => {
const element = document.querySelector('h1');
const style = element ? getComputedStyle(element) : null;
return {
fontSetStatus: document.fonts.status,
computedFamily: style?.fontFamily ?? null,
computedWeight: style?.fontWeight ?? null,
computedStyle: style?.fontStyle ?? null,
faces: [...document.fonts].map(face => ({
family: face.family,
weight: face.weight,
style: face.style,
status: face.status
}))
};
});
console.log(fontReport);
Confirm that the affected element’s computed font-family, weight, and style match an @font-face rule you expect to use. A regular face may load successfully while a bold or italic face used by the captured text is missing or has failed. Also inspect the browser’s network panel or automation logs for the font request and check whether the capture environment can reach that font host.
Font loading can also be requested explicitly when diagnostics indicate the face has not been requested. The CSS Font Loading API provides FontFace.load() and FontFaceSet.load(); use a family, size, and sample text that correspond to the page’s actual CSS and content.
// Ask the document font set to load a face needed for this sample text.
await page.evaluate(async () => {
await document.fonts.load('400 16px "Example Sans"', 'Screenshot text');
await document.fonts.ready;
});
Explicit loading can help with diagnosis or a known face that has not yet been used. It cannot make an unreachable font URL, invalid font resource, or mismatched face rule succeed. See MDN’s FontFaceSet.load reference and the FontFace.load reference.
3. Understand blank text and fallback text
A screenshot taken during font loading may show invisible text or temporary fallback lettering. Google Fonts documents that browser defaults differ: Chrome may show blank space for text waiting on a web font, while Firefox may show default-font text and rerender after the font arrives. Treat this as documented behavior, not a guarantee for every browser version and configuration. See Google Fonts technical considerations.
CSS font-display values such as swap, optional, and fallback can allow a system font to appear while the custom face is pending. That can make text visible sooner, but it does not synchronize screenshot capture with the desired web font. If the exact custom typeface matters, still wait for the font set and verify the intended face.
@font-face {
font-family: "Example Sans";
src: url("/fonts/example-sans.woff2") format("woff2");
font-style: normal;
font-weight: 400;
font-display: swap;
}
For background on display behavior, see Chrome’s web font loading guidance.
4. Troubleshoot common failures
| Symptom | Likely checks | What to do |
|---|---|---|
| Text is blank | The capture may have happened while a font was pending; the browser may use blank text during part of its loading behavior. | Wait for application readiness, then await document.fonts.ready. Check whether the font request completes and whether the face status is loaded or errored. |
| Text is visible but looks like a fallback | The intended face may still be pending, failed, or not match the used weight/style. A font-display policy can show fallback text first. |
Check computed styles and face statuses, verify the resource request, and run the readiness check after the page applies its final styles. |
document.fonts.ready resolves but the typeface is still wrong |
The promise concerns fonts used in the document; it does not assert that every declared font loaded or that the intended CSS face matched. | Inspect the affected element’s computed family, weight, and style. Check the matching @font-face and its resource request. |
| The page changes after the font wait | A route transition, component render, or style update may introduce new text or a different used-font set. | Wait for that content or state first, then evaluate document.fonts.ready again immediately before capture. |
| The readiness wait hangs | A browser or runtime-specific problem is possible, but a pending promise alone does not identify the cause. | Log browser engine/version, automation version, operating system, and each face’s status. Reproduce on a minimal page and compare versions. Avoid an unbounded wait in production; if you add a timeout, report it as a failed readiness check and investigate rather than silently treating it as success. |
| Only CI or a hosted capture fails | The capture environment may differ in browser version, operating system, network access to the font host, or page state. | Record the environment and inspect font requests there. Compare with a local run using the same engine/version and verify external font-host access. |
A Playwright issue opened on September 29, 2026, reports a Linux WebKit reproduction using Playwright 1.63.0 and bundled WebKit 26.6 where waiting for document.fonts.ready remained pending and screenshot capture timed out; its author reported a 1.60.0 control completed, while intermediate versions had not been bisected. This is a scoped issue report with unresolved cause, not evidence that font readiness generally hangs. See Playwright issue #42986.
5. Improve capture reliability and control wait time
- Wait for the page state that matters. A generic network-idle wait does not establish that the intended font is ready. Use an application selector or state signal, followed by the font readiness check.
- Keep the capture environment stable. Record the browser engine and version, automation-library version, and operating system when diagnosing differences between local and CI or hosted runs.
- Make external dependencies reachable. If the font is hosted separately, confirm the capture environment can request it. For repeatable captures, serving fonts from a dependable origin your environment can access reduces one source of variation.
- Use bounded waits deliberately. A timeout can prevent a job from waiting forever, but it does not make an incomplete font load safe to capture. On timeout, surface a clear failure or apply an explicitly chosen fallback policy.
- Do not wait for unused declarations unnecessarily.
document.fonts.readycovers used fonts, whereas a page may declare faces it never uses. Explicitly load extra faces only when the capture needs them.
Waiting for font readiness adds the time the relevant font loads and layout takes; the actual delay depends on the page, browser, network, and cache. No fixed duration is guaranteed. Reuse browser sessions where appropriate for automation workloads, and avoid arbitrary long sleeps that slow every capture without confirming readiness.
6. Or skip the browser setup
If you want a screenshot without managing browser automation, ScreenshotNeo is a website screenshot API and MCP server. Its API returns PNG, JPEG, WebP, or PDF from one GET request. For font-sensitive pages, the capture still depends on the page and its rendering environment; use the documented wait options and check the response verdict when diagnosing a result. 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(`Screenshot request failed: ${res.status}`);
await Bun.write('shot.webp', res);
Cookie and consent banners, newsletter popups, and chat widgets are removed before the shot; each cleanup step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing status. Its MCP server gives AI agents tools for screenshots, page information, and PDF capture. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 screenshots. Every feature is on every plan.
Create a free ScreenshotNeo account for 1,000 screenshots a month with no card.
7. Frequently asked questions
Does document.fonts.ready wait for every font declared in the stylesheet?
No. It resolves after loading and layout operations for fonts used by the document finish. A declared but unused face may not be loaded.
Should I use a fixed delay instead?
A delay only waits for elapsed time; it cannot confirm that the relevant face loaded. Prefer application readiness followed by the font-set promise.
Can I make the screenshot use a fallback font on purpose?
Yes. Set an intentional fallback stack or capture with styles that use a system face. If the custom face is required, verify it has loaded instead of relying on font-display.
Can screenshot APIs fix a font URL or CSS mismatch?
No capture service can make an invalid face rule or inaccessible font resource correct. Diagnose the page’s computed style, face status, and font request first.


