Wait for the fonts that the page actually uses before capturing it. In Puppeteer or Playwright, navigate to the page, wait for the content being captured, await document.fonts.ready, then take the screenshot. This promise resolves after loading and layout work for used fonts finishes, but it does not prove that every declared face loaded or that the preferred family is the one being rendered.
How do I wait for web fonts before taking a screenshot?
The reliable sequence is navigation, an application-specific content check, font readiness, and capture. A network-idle event is a useful baseline, not a visual guarantee: pages can still insert text, switch classes, lazy-load regions, or begin using a different face afterward.
await page.goto(url, { waitUntil: 'networkidle' });
await page.locator('#capture-target').waitFor({ state: 'visible' });
await page.evaluate(() => document.fonts.ready);
await page.screenshot({ path: 'capture.png' });
The locator syntax above is Playwright-style. In Puppeteer, use a selector wait such as await page.waitForSelector('#capture-target', { visible: true }), then run the same document.fonts.ready evaluation and the Puppeteer screenshot method. Navigation option names and defaults vary by framework version, so check the current API documentation for Puppeteer screenshots and the Playwright Page API.
What document.fonts.ready actually guarantees
It tracks used fonts and layout
document.fonts exposes the document’s FontFaceSet. Its ready promise fulfills when loading and layout operations for all fonts used by the document are complete, as described in the MDN Document.fonts reference. This is why placing the await immediately before the screenshot prevents many fallback-font captures.
The Tool Desk
Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →#1 Best Overall
It does not load every declaration
A stylesheet can declare several families or weights that the page never uses. Browsers may leave those faces unloaded; MDN’s example specifically notes that some fonts can remain unloaded when they are unused. Font-display behavior can also allow fallback text to remain visible or make a face optional during the initial render. Therefore, readiness is about used faces, not every @font-face rule.
It does not prove font identity
The promise does not verify that a preferred font is installed, available at the URL you expect, or selected by the final cascade. If the exact typeface matters, inspect the computed style and validate the rendered result. The CSS Font Loading API documents explicit loading with document.fonts.load(); use it for a known family and weight before checking readiness.
A complete Puppeteer workflow
This example fixes the viewport, waits for the target, waits for used fonts, disables motion, and captures either the viewport or the full page. Install Puppeteer in your project and provide a URL through your own configuration.
Rank #2
- HTML CSS Design and Build Web Sites
- Comes with secure packaging
- It can be a gift option
import puppeteer from 'puppeteer';
const url = 'https://example.com';
const browser = await puppeteer.launch();
try {
const page = await browser.newPage();
await page.setViewport({ width: 1440, height: 900, deviceScaleFactor: 1 });
await page.goto(url, { waitUntil: 'networkidle0', timeout: 90000 });
await page.waitForSelector('#capture-target', { visible: true, timeout: 30000 });
// Ask the page to load the face that your design requires.
await page.evaluate(async () => {
await document.fonts.load('400 16px "Your Web Font"');
await document.fonts.ready;
document.documentElement.classList.add('screenshot-mode');
});
// Freeze common motion; add site-specific rules for carousels or video.
await page.addStyleTag({ content: `
*, *::before, *::after {
animation: none !important;
transition: none !important;
caret-color: transparent !important;
}
`});
await page.screenshot({ path: 'capture.png', fullPage: true });
} finally {
await browser.close();
}
Replace the family name and weight with a face your page really uses. If application code changes text or styles after this evaluation, wait again after that work. A page can become font-dependent later than initial navigation.
Free tools Windows power users keep installed
One-click scans. No signup required.
A complete Playwright workflow
import { chromium } from 'playwright';
const browser = await chromium.launch();
try {
const page = await browser.newPage({
viewport: { width: 1440, height: 900 },
deviceScaleFactor: 1
});
await page.goto('https://example.com', {
waitUntil: 'networkidle',
timeout: 90000
});
await page.locator('#capture-target').waitFor({ state: 'visible' });
await page.evaluate(async () => {
await document.fonts.load('400 16px "Your Web Font"');
await document.fonts.ready;
});
await page.screenshot({ path: 'capture.png', fullPage: true });
} finally {
await browser.close();
}
For visual regression, Playwright’s screenshot assertions wait for two consecutive captures to match and disable animations by default in the documented assertion options. That improves repeatability, but it does not replace the page-specific content and font checks.
Choose the capture scope and pixel scale
Viewport, full page, or element
- Viewport: captures what a user sees at the configured dimensions.
- Full page: captures the document’s scrollable content; confirm that lazy sections are loaded before capturing.
- Element: captures a component such as
#capture-target; use this for cards, invoices, or regression fixtures.
CSS pixels versus device pixels
Set both viewport dimensions and output scale deliberately. A device scale factor or Playwright’s equivalent changes the number of image pixels without changing the CSS layout. Keep these values fixed when comparing images across runs. The Page API documents CSS-pixel and device-pixel behavior.
Rank #3
Control dynamic content
Disable CSS animations and transitions, pause carousels, hide blinking carets, and provide stable data for timestamps or rotating ads. If a page continuously makes requests, a generic network-idle wait may never be reached; use a bounded delay plus a selector that represents the completed state.
Why does my screenshot use the fallback font?
The capture happened before the face was used
Move await page.evaluate(() => document.fonts.ready) after the target content is visible. If a modal, route, or client-side render introduces new text, call it again after that operation.
The requested face failed to load
Inspect browser console and network logs for failed font URLs, CORS errors, blocked requests, incorrect MIME types, or authentication requirements. A successful document load does not mean every font request succeeded.
Rank #4
- Brand: Wiley
- Set of 2 Volumes
- A handy two-book set that uniquely combines related technologies Highly visual format and accessible language makes these books highly effective learning tools Perfect for beginning web designers and front-end developers
The CSS selects another face
Check computed font-family, font-weight, and font-style on the actual text node. A missing weight can trigger synthetic bold or a different family. Explicitly load the intended combination with document.fonts.load(), then inspect the final style.
font-display changes what users see
Web-font policy can permit fallback text, swap later, or make a face optional. Decide whether the screenshot should represent the initial fallback state or the final branded state, and wait for the corresponding application signal rather than assuming one universal delay.
The browser environment differs
Container images, operating-system font substitution, missing language glyphs, and different browser versions can change shaping and line breaks. Pin the browser version, viewport, scale, locale, and timezone for repeatable captures. Compare rendered output, not only whether document.fonts.ready fulfilled.
Best Value
Reliability checklist
- Use a fixed viewport, device scale, locale, and timezone.
- Wait for the exact content or selector that belongs in the image.
- Await
document.fonts.readyafter content and style changes. - Explicitly call
document.fonts.load()when a particular family and weight are mandatory. - Check network and console errors for font requests.
- Load lazy images and below-the-fold regions before a full-page capture.
- Freeze animations and make time-dependent data deterministic.
- Use a bounded timeout and a fallback diagnostic screenshot when readiness fails.
Or skip the browser setup
ScreenshotNeo provides a website screenshot API and MCP server. It removes cookie and consent banners, newsletter popups, and chat widgets before capture; bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, with the response identifying the page verdict and billing status. Its MCP tools—take_screenshot, get_page_info, and capture_pdf—work with Claude, Cursor, and other MCP clients.
Use the API after your own font-readiness decisions are no longer worth maintaining:
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}`);
See the ScreenshotNeo API documentation for options such as full-page and selector captures, dark mode, device presets, retina scale, custom CSS and JavaScript, click and wait conditions, resource blocking, cookies and headers, timezone and geolocation, transparent backgrounds, resizing, caching, signed links, asynchronous webhooks, bulk capture, usage data, and PDF output. Every feature is available on every plan. The Free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.
Troubleshooting by symptom
| Symptom | Likely cause | Fix |
|---|---|---|
| Text wraps differently between runs | Viewport, scale, font weight, or browser differs | Pin those values and explicitly load the required face. |
| Capture hangs on network idle | Analytics, sockets, or polling keep requests active | Use a selector/state check and a bounded timeout instead. |
| Some sections show fallback text | Those sections became visible after the first readiness wait | Trigger the section, wait for it, then await document.fonts.ready again. |
| Full-page image misses content | Lazy loading depends on scrolling or intersection events | Scroll or trigger the application’s load routine before capture. |
| Only one environment fails | CORS, credentials, missing glyphs, or different system fonts | Compare request logs and computed styles inside that browser environment. |
FAQ
Is a fixed sleep enough?
No. A delay may be too short on a slow connection and unnecessarily long on a fast one. A content signal followed by font readiness is more meaningful.
Recommended Free Tools
Should I wait for every declared font?
Only if your design actually uses each face. Unused declarations may never load, and readiness intentionally concerns used fonts.
Does font readiness wait for images?
No. It covers used-font loading and related layout work. Wait separately for images, lazy regions, and application-specific state.
Which format should I save?
Choose PNG for lossless text and regression comparison, JPEG for smaller photographic output, or WebP when your pipeline supports it. Keep the format constant when comparing captures.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

