How to Fix Playwright Screenshots with Missing Web Fonts
Wait for the content you need, then for the browser’s font set before capturing. Diagnose missing faces, failed requests, and screenshot timeouts with runnable Playwright examples.
To fix missing web fonts in a Playwright screenshot, first wait for the page content you intend to capture, then wait for the browser’s font set, and only then take the screenshot:
await page.goto(url);
await page.getByRole('heading', { name: 'Account overview' }).waitFor();
await page.evaluate(() => document.fonts.ready);
await page.screenshot({ path: 'page.png' });
Replace the example heading with a locator or assertion for the content your page actually renders. document.fonts.ready waits for fonts used by the document and the associated layout work to settle. It does not prove that every CSS-declared font loaded, or that a particular brand font was selected. If the result still uses fallback typography, inspect the matching font face and its network request rather than adding an arbitrary sleep.
1. Wait for the page state that uses the font
Navigation milestones and application readiness are different. DOMContentLoaded and load describe stages of navigation; your component may render later, or its text may trigger a font load only when it appears. Wait for the specific content before asking the browser for font readiness.
import { test, expect } from '@playwright/test';
test('captures the account heading with loaded fonts', async ({ page }) => {
await page.goto('https://example.com/account');
const heading = page.getByRole('heading', { name: 'Account overview' });
await expect(heading).toBeVisible();
await page.evaluate(() => document.fonts.ready);
await page.screenshot({ path: 'account.png', fullPage: true });
});
Use a meaningful locator, visibility assertion, or application-specific readiness condition. Playwright discourages using networkidle as a test-readiness shortcut; prefer web assertions that describe the state you need. Pages with analytics, polling, or long-lived connections may never become network-idle, and an idle network does not by itself prove that the intended text or font face is ready. See the Playwright Page API.
2. Wait for the browser font set
Once the relevant text is rendered, wait on the document’s font set:
await page.evaluate(() => document.fonts.ready);
document.fonts is the page’s FontFaceSet. Its ready promise resolves after used-font loading and related layout operations complete, when no further font loads are needed for that state. A page may declare faces that remain unused and unloaded. This behavior is why the order matters: render the content first, then wait for the font set. See MDN’s Document.fonts and FontFaceSet.ready references.
The promise is a synchronization point, not an assertion that a named family, weight, style, or glyph subset succeeded. If typography is critical, also inspect computed styles, matching face status, and font requests.
3. Complete runnable Playwright example
This TypeScript example waits for meaningful content, records font-face state if the expected face is not loaded, and captures only after the font set settles. Change the URL, locator, and expected family to match your application.
import { test, expect } from '@playwright/test';
test('captures the page after web fonts settle', async ({ page }) => {
await page.goto('https://example.com');
const title = page.getByRole('heading', { name: 'Account overview' });
await expect(title).toBeVisible();
const fontState = await page.evaluate(async () => {
await document.fonts.ready;
return {
status: document.fonts.status,
faces: Array.from(document.fonts, face => ({
family: face.family,
weight: face.weight,
style: face.style,
status: face.status,
})),
};
});
console.log('Font state:', JSON.stringify(fontState, null, 2));
await page.screenshot({ path: 'page.png', fullPage: true });
});
For a test that must fail when a specific face is unavailable, add a project-appropriate check after readiness. For example, check the reported face entries for the expected family and successful status, then verify the computed style on the target element. Remember that a face can be declared but unused, and that computed font-family lists requested families rather than proving which physical font rendered every glyph.
4. Diagnose the missing typeface
Inspect font faces and document status
Capture the state directly from the browser context:
const diagnostics = await page.evaluate(() => ({
fontSetStatus: document.fonts.status,
faces: Array.from(document.fonts, face => ({
family: face.family,
weight: face.weight,
style: face.style,
status: face.status,
})),
headingFamily: getComputedStyle(
document.querySelector('h1')!
).fontFamily,
}));
console.log(diagnostics);
Use a selector that exists on your page; handle a missing element if the diagnostic runs before the target renders. Check that the requested family, weight, and style match an available @font-face declaration. A page may load the regular face while the heading requests a bold face that is missing, or it may need a different subset for the characters shown.
Check stylesheets and font requests
- Confirm the stylesheet containing
@font-faceloaded successfully. - In the browser’s network log, inspect the font file request for status, redirects, blocked requests, and console errors.
- Check that the font URL works from the test environment, including any authentication, origin, or access restrictions.
- Compare the face’s declared family, weight, and style with the computed style of the text being captured.
- Make sure the relevant text was present before you awaited
document.fonts.ready.
If a font fails to load, the browser may render fallback typography while the page itself otherwise appears complete. Fix the stylesheet or font request; waiting longer cannot repair a wrong URL, unavailable resource, or mismatched face.
5. Keep screenshot stability separate from font correctness
Playwright’s toHaveScreenshot assertion waits until consecutive screenshots match before comparing the result with the expectation. This helps with visual stability, but it does not verify that a particular web font loaded. Keep a font readiness wait and, when necessary, a face or rendered-output check when typography matters.
import { test, expect } from '@playwright/test';
test('compares a stable screenshot after fonts settle', async ({ page }) => {
await page.goto('https://example.com');
await expect(page.getByRole('heading', { name: 'Account overview' })).toBeVisible();
await page.evaluate(() => document.fonts.ready);
await expect(page).toHaveScreenshot('account.png');
});
See the PageAssertions API for screenshot assertion behavior. A stable screenshot can still be consistently wrong if the browser used a fallback font.
6. Handle hangs and timeouts carefully
If waiting on document.fonts.ready or screenshot preparation hangs, first determine which operation is blocked and collect the browser, operating system, Playwright version, and font state. Increasing a timeout may help distinguish a slow request from a persistent failure, but it does not establish that the intended font loaded.
A user-submitted Playwright issue opened September 29, 2026 reports screenshot preparation timing out while waiting for fonts on Linux WebKit with Playwright 1.63.0 and bundled WebKit 26.6. The report says a Playwright 1.60.0 control completed on the same public page, but it does not isolate the cause or establish the first affected version. Treat this as a version-specific clue: reproduce with your own page, fonts, browser engine, and container before drawing conclusions. See Playwright issue #42986.
The report also describes PW_TEST_SCREENSHOT_NO_FONTS_READY=1 allowing a diagnostic screenshot to complete in that reproduction. It did not repair font state, and a later normal capture timed out again. Use such a bypass only to investigate whether screenshot font waiting is involved; a completed capture may still contain fallback typography. Do not make it a correctness fix without independently verifying the output.
An older issue records a component-test question involving an external stylesheet with @font-face declarations; it is context for checking stylesheet and font timing, not evidence of a general failure or an official resolution. See Playwright issue #18640.
7. Troubleshooting checklist
| Symptom | Likely cause | What to do |
|---|---|---|
| Screenshot has fallback font | Capture happened before relevant content or font use; font request failed; requested face does not match. | Wait for the content first, then document.fonts.ready; inspect face status, stylesheet, request, and computed style. |
document.fonts.ready resolves, but brand font is absent |
Readiness covers used-font loading, not every declared face or proof of a specific family. | Check the expected family, weight, style, glyph coverage, and the actual rendered result. |
Font face remains unloaded |
That face may be declared but unused, or the target content did not trigger it. | Confirm the target text and style are rendered before inspecting readiness; check matching CSS. |
Font face is error |
Font file or stylesheet failed, was blocked, or could not be used. | Inspect browser network and console output; correct the resource or access configuration. |
| Font wait or screenshot preparation times out | Slow or stuck request, browser issue, or engine/version-specific behavior. | Record font state and failed requests; reproduce across the project’s Playwright version, engine, OS, and container. |
networkidle never happens |
Persistent network activity or a readiness condition unrelated to page content. | Use a locator or web assertion for the required application state instead. |
| Visual assertion passes but font is wrong | Stable output is not the same as correct font selection. | Assert the expected font state or verify the page output separately. |
8. Performance, reliability, and cost
Waiting for font readiness adds time only when used fonts and their layout work have not settled yet; the actual delay depends on your page and font delivery. Keep the wait after the relevant content appears so the browser does not settle an earlier state and then load more fonts when the target renders.
For reliability, make font diagnostics available when a wait or screenshot fails. Log the Playwright version, browser engine, OS or container, font-face statuses, and failed requests. When investigating a version change, keep content and screenshot settings constant while comparing engine and version; a single report is not a general failure rate.
For cost, a local Playwright capture uses your own test infrastructure and browser execution. The main practical tradeoff is the time and maintenance required to manage browser versions, font resources, and CI behavior. If you use a screenshot API instead, compare its billing behavior and font handling against your actual capture requirements.
9. Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server. One GET request returns a PNG, JPEG, WebP, or PDF, so you can capture a page without setting up Playwright browsers in your own environment.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
See the ScreenshotNeo API documentation for the request options. ScreenshotNeo accepts cookie and consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each step can be turned off. Bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers report the page verdict and billing status. Its MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients.
The free plan includes 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 screenshots; every feature is available on every plan. Sign up free for 1,000 screenshots a month, with no card.
10. FAQ
Does page.goto wait for web fonts?
Navigation waiting covers a navigation milestone. Wait for your application content and then explicitly await document.fonts.ready when font readiness matters.
Does document.fonts.ready load every font declared in CSS?
No. It settles loading for fonts used by the document; declared but unused faces can remain unloaded.
Should I use a fixed sleep?
Usually not. A sleep does not identify whether the intended font loaded. Wait for the relevant content and font set, then inspect face and request state if the result is wrong.
Can screenshot assertions replace the font wait?
No. They help ensure successive captures are stable for comparison, but do not establish that a particular font face succeeded.
Is the WebKit timeout issue proof that Playwright 1.63 is broken?
No. The cited report is one reproduction on Linux WebKit and does not establish a general issue or affected-version range. Reproduce with your own setup.


