How to Wait for Fonts to Load Before Capturing Website Thumbnails
Wait for the page content, then use document.fonts.ready before capturing. Learn how to verify a specific font, handle timeouts, and keep thumbnails consistent.
In Playwright, wait for the content you need, then await document.fonts.ready in the page context before taking the screenshot. If a particular font face or glyph subset must be present, request it explicitly with document.fonts.load() and verify the result. A font-ready promise does not guarantee that a preferred font loaded successfully.
1. Wait for page content, then fonts
Navigation readiness and font readiness answer different questions. A navigation event tells you about document loading; it does not necessarily mean your single-page app has rendered the content for the thumbnail. Wait for the relevant content first, then ask the document’s font set to settle.
import { chromium } from 'playwright';
const url = 'https://example.com';
const browser = await chromium.launch();
const page = await browser.newPage({ viewport: { width: 1200, height: 800 } });
try {
await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 30_000 });
await page.locator('body').waitFor({ state: 'visible', timeout: 10_000 });
// If the app renders the thumbnail content asynchronously, wait for its actual selector:
// await page.locator('[data-thumbnail-ready]').waitFor({ state: 'visible', timeout: 15_000 });
await page.evaluate(async () => {
await document.fonts.ready;
});
await page.screenshot({ path: 'thumbnail.png' });
} finally {
await browser.close();
}
The Font Loading API’s document.fonts.ready promise fulfills when font loading and associated layout operations for the document are complete. It is a document-level readiness signal, not proof that a particular preferred face succeeded. See the MDN CSS Font Loading API and Playwright Page API.
2. Require a specific face or character set
Use document.fonts.load(fontShorthand, sampleText) when the thumbnail depends on a specific family, style, weight, or glyph subset. The sample text matters: a web font may be split into subsets, and the page’s currently visible text may not exercise the characters you care about.
const fontCheck = await page.evaluate(async () => {
const sample = 'Résumé — 東京';
const faces = await document.fonts.load('600 32px "Brand Sans"', sample);
return {
matchingFacesLoaded: faces.length,
checkPasses: document.fonts.check('600 32px "Brand Sans"', sample),
status: document.fonts.status,
};
});
if (fontCheck.matchingFacesLoaded === 0 || !fontCheck.checkPasses) {
throw new Error(`Required font did not become available: ${JSON.stringify(fontCheck)}`);
}
await page.screenshot({ path: 'thumbnail.png' });
Adapt the family, weight, style, and sample to the CSS and text in your page. A nonzero loaded-face count is useful evidence, but for critical visual output also inspect the rendered result or computed styles: fallback fonts can make text appear while the intended face is missing. The API and its explicit loading methods are documented by MDN.
3. Pick the right readiness condition
| Need | Use | What it establishes |
|---|---|---|
| Fonts currently used by the settled document | await document.fonts.ready |
Font loading and related layout operations have settled for the document. |
| A named face, style, weight, or text sample | await document.fonts.load(...), then check or inspect |
Requests matching font faces for the supplied descriptor and sample; verify that the required face is available. |
| Content created after initial navigation | Wait for an application-specific selector or state, then wait for fonts | Ensures the relevant content exists before font readiness is evaluated. |
| Dynamic page with ongoing background requests | Wait for the actual content condition and fonts | Avoids treating general network quiet as a substitute for page-specific readiness. |
Playwright supports load, domcontentloaded, networkidle, and commit navigation states. Its documentation discourages using networkidle as a general readiness strategy. Network activity is not the font API: prefer the condition that proves the content you need exists, followed by the font wait. See Playwright navigation options.
4. Make thumbnail captures repeatable
- Use the same browser engine and version, operating system, viewport, device scale factor, and headless settings as the reference capture.
- Wait for the exact page state represented by the thumbnail, not only for navigation.
- Use a fixed locale and consistent input data when page text or layout depends on them.
- For lazy content, scroll or otherwise trigger the content you intend to include, wait for it to appear, and then wait for fonts again.
- Keep navigation, content, font, and screenshot operations bounded with timeouts and report which stage failed.
Browser and host differences can change rendered pixels. Playwright’s guidance on visual comparisons describes keeping the rendering environment consistent. Playwright’s screenshot assertion helper can wait for two consecutive screenshots to produce the same result, which helps with screenshot-test stability; it does not verify that your intended font face loaded. See PageAssertions.
5. Common problems and fixes
| Symptom | Likely cause | Fix |
|---|---|---|
| Thumbnail uses a fallback font | The preferred font failed to load, its CSS was not applied, or the requested glyph was outside the loaded subset. | Call document.fonts.load() with the required descriptor and representative text; inspect network failures, font declarations, and the rendered result. |
document.fonts.ready resolves but the thumbnail is still wrong |
The app inserted or changed content after the wait, or the desired face was never successfully available. | Wait for the final content state first, then wait for fonts again. Explicitly request and verify critical faces. |
Wait for networkidle hangs |
Analytics, long polling, or other ongoing traffic prevents network quiet. | Use a specific content selector or app readiness condition, then the font API. |
| Screenshot operation times out | Navigation, font readiness, or screenshot work exceeded its timeout; browser-specific behavior may also be involved. | Log each stage, bound each wait, and reproduce with the same engine and version. Investigate a pending font promise instead of silently capturing an incomplete page. |
| Images or page sections are missing | Lazy content was not triggered, or the application had not rendered it when the font wait began. | Trigger the needed content, wait for its selector, then wait for fonts and capture. |
| Pixels differ across machines | Browser, OS, fonts installed, headless configuration, viewport, or device scale factor differs. | Pin and match the capture environment and settings. |
A recent report describes document.fonts.ready remaining pending in one Linux WebKit and Playwright configuration. It does not establish how common the issue is, its root cause, or a general workaround. If a font wait fails to settle, record the engine, version, operating system, and stage, and investigate that setup. See Playwright issue #42986.
6. Performance, reliability, and cost
Font readiness adds a wait for the fonts the page actually needs, so its duration depends on the page, font delivery, and cache state. An explicit load may request additional faces or glyph subsets. Set practical bounds around navigation, content readiness, font checks, and capture; if a required face does not arrive, fail clearly or apply a deliberate fallback policy instead of waiting without limit.
For repeated captures, reuse a browser process where appropriate and keep the rendering environment stable. Do not assume a warm cache or a quiet network means the desired font rendered. The cited documentation provides no general timing benchmark or quantified cost for this workflow, so measure against your pages and infrastructure.
7. Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server. Its API returns an image or PDF from one GET request; the product handles the browser capture workflow. The request below captures a page, but it does not expose a font-specific wait parameter, so use browser automation above when you must explicitly verify a particular typeface before capture. 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,
)
r.raise_for_status()
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); // For Node.js, use: import { writeFile } from 'node:fs/promises'; await writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));
ScreenshotNeo removes cookie and consent banners, newsletter popups, and chat widgets before the shot; bot checks, blank pages, failed loads, timeouts, and cache hits are not billed. Its MCP server gives AI agents tools to take screenshots, get page information, and capture PDFs. The free plan includes 1,000 screenshots each month with no card; paid plans start at $5 for 3,000. Sign up for 1,000 free screenshots a month, with no card required.
FAQ
Does document.fonts.ready wait for every font declared in CSS?
It is a readiness signal for font loading and layout operations needed by the document. To require a specific face or sample, request it explicitly with document.fonts.load() and verify availability.
Should I use networkidle instead?
No. Network quiet does not establish font readiness or prove that the target content is present. Wait for the relevant page state and use the font-loading API.
Can I capture reliably if the preferred font is unavailable?
Yes, if your thumbnail policy allows a fallback. Detect the missing required face and either fail the capture or proceed intentionally with the fallback, recording that choice.


