How to Wait for Web Fonts Before Taking a Playwright Screenshot
Wait for the page content first, then await document.fonts.ready before capturing. Learn when to load a specific font explicitly and how to diagnose fallback fonts.
After the content you plan to capture is on the page, await document.fonts.ready in the browser context, then call page.screenshot(). This waits for fonts currently used by the document and the associated layout work to finish. If a particular family, weight, style, or glyph subset must be present, explicitly request it with document.fonts.load() and inspect font-face status if it does not load.
1. Wait for the page content, then for fonts
Font readiness depends on what the document actually uses. First wait for the application to render the content and typography that will appear in the image. Then wait for fonts and take the screenshot.
Playwright Test (JavaScript)
import { test, expect } from '@playwright/test';
test('capture after used fonts settle', async ({ page }) => {
await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
// Replace this locator with the content that determines your screenshot.
const heading = page.getByRole('heading', { name: 'Example Domain' });
await expect(heading).toBeVisible();
// Resolve document.fonts.ready in the page's browser context.
await page.evaluate(() => document.fonts.ready);
await page.screenshot({ path: 'page.png', fullPage: true });
});
Save this as a Playwright test file in a project with @playwright/test installed, then run it with your project’s Playwright test command. The URL, heading, and screenshot path are examples; use the page and content you actually need to capture. The Playwright Page API documents navigation and screenshots.
Standalone Playwright script
If you are using the Playwright library directly instead of the test runner, the same sequence works:
import { chromium } from 'playwright';
const browser = await chromium.launch();
try {
const page = await browser.newPage();
await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
await page.getByRole('heading', { name: 'Example Domain' }).waitFor();
await page.evaluate(() => document.fonts.ready);
await page.screenshot({ path: 'page.png', fullPage: true });
} finally {
await browser.close();
}
Run this as an ES module in a project with the playwright package installed and the chosen browser available.
2. Load a specific font or text subset explicitly
document.fonts.ready covers fonts used by the document. A font declared in CSS may remain unloaded if no rendered content uses it. If the exact face matters, wait until the relevant text exists, request the face with the CSS font shorthand and a representative sample string, and then await general readiness:
await page.getByText('The text that matters').waitFor();
await page.evaluate(async () => {
await document.fonts.load('400 16px "Example Sans"', 'The text that matters');
await document.fonts.ready;
});
await page.screenshot({ path: 'page.png' });
Replace Example Sans, weight, size, and sample text with the values used by the page. Include representative characters when a font has separate glyph subsets. This requests a face; it cannot make an unavailable or failing font resource succeed. The CSS Font Loading API documents explicit font loading and face states.
3. Understand what font readiness guarantees
document.fonts exposes the page’s FontFaceSet. Its ready promise resolves when the document has completed loading fonts it needs, layout operations are complete, and no further font loads are needed. It does not mean every font declared in stylesheets has been downloaded: unused faces may remain unloaded. See MDN’s references for Document.fonts and FontFaceSet.ready.
The promise is scoped to font loading and layout. It does not indicate that your application has finished updating. If the page changes its text, classes, or styles after the promise resolves, that can change which fonts are used. Wait for the final application state before awaiting font readiness. This sequencing follows from the API’s scope over currently required fonts and layout.
4. Navigation waits and font waits solve different problems
page.goto() can wait for navigation milestones such as commit, domcontentloaded, load, and networkidle. These milestones do not express the font-specific condition you need for a typography-sensitive capture. Playwright discourages using networkidle as a general testing readiness signal and recommends assertions tied to the page state instead. Wait for the content that matters, then await document.fonts.ready.
A fixed page.waitForTimeout() is a timing guess, not a font readiness check. It may be too short on a slow run and needlessly long on a fast one. Use a delay only when the application has a known timing behavior that has no better observable signal; do not make it the main font correctness mechanism.
5. Diagnose fallback typography
If the screenshot still shows fallback type, check whether the intended face was actually requested and applied. The CSS Font Loading API exposes face statuses including unloaded, loading, loaded, and failed.
const fontState = await page.evaluate(() => ({
status: document.fonts.status,
faces: [...document.fonts].map((face) => ({
family: face.family,
style: face.style,
weight: face.weight,
status: face.status,
})),
}));
console.log(fontState);
Run the inspection after the relevant content appears and after the font wait. Compare the family, style, and weight against the CSS rules the page is meant to use. A resolved readiness promise does not prove that a particular branded face loaded if that face was not needed or if the page uses a fallback.
6. Common errors and fixes
| Symptom | Likely cause | What to do |
|---|---|---|
| Screenshot uses a fallback font | The intended face was not used by the rendered text, failed to load, or does not match the selected family, weight, style, or glyph coverage. | Wait for the final text, check the applied CSS and font resource responses, explicitly call document.fonts.load() with the correct shorthand and sample text, and inspect face statuses. |
document.fonts.ready resolves but a declared font is still unloaded |
Declared faces need not load unless the page uses them. | Confirm the target text uses that face, or explicitly request it with document.fonts.load(). |
| The font wait completes, then the screenshot layout shifts | The application updated content or styles after the wait, causing different font use or layout. | Wait for the application state that controls the capture first; then wait for fonts and capture. |
| Font resource has a failed status | The requested font could not load, for example because the font resource or CSS configuration is unavailable or incorrect. | Check the face’s CSS declaration, URL, network response, and cross-origin setup as applicable. Correct the resource problem; waiting again cannot repair a failed request. |
| Font wait or screenshot times out on Linux WebKit | A specific Playwright issue report describes a timing-sensitive case with a font face in error while the set remained loading. |
Record Playwright, browser, OS, and font status; reduce the case to a reproducible page and check the report below. Do not assume the font rendered correctly just because a capture workaround returned an image. |
7. Browser-specific caveat: a reported WebKit hang
Playwright issue #42986, opened September 29, 2026, reports a Linux reproduction with Playwright 1.63.0 and bundled WebKit 26.6 (revision 2359): a face was in error while document.fonts.status remained loading, and the screenshot operation timed out during its font wait. The report describes a 1.60.0 control that completed and says individual face states were timing-sensitive.
This is one issue report, not evidence that all Linux WebKit captures hang. If you see this symptom, capture the exact versions and face states in your bug report and investigate the font failure itself. The issue report says bypassing the screenshot font wait produced an image but did not repair the font state; a later ordinary capture timed out. Treat a timeout workaround as a way to obtain an image whose typography still needs validation, not as proof the font loaded.
8. Performance, reliability, and cost
- Performance: A font readiness wait is condition-based; it completes when the currently needed fonts and layout settle rather than after a guessed fixed delay. Slow or blocked font resources can still extend the wait.
- Reliability: Make the application state observable with a locator or assertion, then wait for fonts. For strict typography requirements, explicitly request the face and inspect its status. Keep browser and Playwright versions with reproduction notes for browser-specific failures.
- Cost: This approach uses Playwright and the browser environment you run it in. The dossier does not establish a general price or benchmark for execution. Consider the time and infrastructure of your own browser runs; no external screenshot service is required for the DIY method.
9. Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server. One GET request can return a PNG, JPEG, WebP, or PDF. The API exposes capture options including waits, custom CSS and JavaScript, viewport and device settings, and full-page capture. See the ScreenshotNeo API docs for the request options.
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 import('node:fs/promises').then(({ writeFile }) => writeFile('shot.webp', Buffer.from(await res.arrayBuffer())));
Replace the example URL with the page you need and supply your API key. ScreenshotNeo removes cookie banners, newsletter popups, and chat widgets before the shot; bot checks, blank pages, and failed loads are never billed. Its MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots a month with no card, and paid plans start at $5 for 3,000. Sign up for 1,000 free screenshots a month, no card required.
10. FAQ
Does document.fonts.ready wait for every font in my CSS?
No. It waits for fonts the document currently needs. A declared but unused face may remain unloaded; explicitly request a required face with document.fonts.load().
Should I use networkidle instead?
No navigation milestone replaces the font API. Wait for the page state you need, then wait for fonts. Playwright also discourages networkidle as a general testing readiness signal.
Can I guarantee a font will load by waiting longer?
No. A longer delay cannot fix a missing or failed font resource. Check the face status, applied CSS, and resource response.
What if the app changes the text after fonts become ready?
Wait for the final content and style state first, then await readiness again immediately before capturing.


