Fix Node.js Playwright Screenshots That Render Text in the Wrong Font
Diagnose fallback fonts in Playwright screenshots by checking font loading, Linux dependencies, capture timing, and whether your CI environment matches your baseline.
When a Node.js Playwright screenshot uses the wrong font, first find out whether the browser lacks the intended font, the page has not finished loading it, or the screenshot is being rendered in a different environment from the baseline. Playwright does not choose a CSS font: the browser applies the page’s font rules using fonts available to it. Check the computed font family and font files in the failing runtime, make capture timing deterministic, and compare snapshots in a pinned, matching environment before changing visual-diff thresholds.
This guide walks through that diagnosis for local development, Linux containers, and CI. It includes runnable Node.js examples, checks for common failure modes, and a final option for capturing pages without managing a browser environment yourself.
1. Identify what differs between the passing and failing runs
Start by recording the conditions under which the screenshot is produced. A local-to-CI mismatch is a clue to compare environments; it does not, by itself, show that the application’s CSS changed. Playwright notes that screenshot rendering can vary with the host OS, browser version, browser settings, hardware, power source, and headless mode. Fonts are among the sources of differences across browsers and platforms. See Playwright’s visual comparisons guide.
| Record | Why it matters |
|---|---|
| Playwright package version | The package and browser binaries should be compatible. |
| Browser project and version | Chromium, Firefox, and WebKit can render fonts differently. |
| Host OS or container image | Available system fonts and font rendering can vary by environment. |
| Headless or headed mode | Rendering behavior can differ between modes. |
| Local, CI, or both | Shows whether to focus on environment drift or a wider issue. |
| Page font source | Distinguish a locally installed font from a web font served by the application. |
Keep the screenshot baseline and comparison run in the same rendering environment where practical. If you intentionally compare multiple operating systems or browsers, use baselines appropriate to each environment rather than expecting identical font rasterization.
2. Check the page’s font rules and actual font availability
Inspect the CSS font-family declaration for the affected element and its ancestors. A family list is a preference order: if the first family cannot be used, the browser can render a later fallback. Check the CSS rules and the font files shipped with the app or installed in the runtime, especially when the problem only occurs in Linux or a container.
For a quick in-page diagnostic, log the computed family and ask the browser whether it recognizes the requested family. This is a diagnostic, not proof that a particular font file was loaded or selected for every glyph:
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto('https://example.com', { waitUntil: 'load' });
const fontInfo = await page.locator('h1').evaluate((element) => {
const style = getComputedStyle(element);
return {
fontFamily: style.fontFamily,
fontSize: style.fontSize,
fontWeight: style.fontWeight,
fontStyle: style.fontStyle,
requestedFirstFamily: style.fontFamily.split(',')[0].trim().replace(/^['"]|['"]$/g, ''),
browserRecognizesFamily: document.fonts.check(`16px ${style.fontFamily.split(',')[0]}`),
};
});
console.log(fontInfo);
await browser.close();
})().catch((error) => {
console.error(error);
process.exitCode = 1;
});
Replace the URL and selector with the failing page and element. The computed value tells you what CSS requests, not necessarily which physical font supplied every character. If the intended family is a web font, check the page’s font requests and browser console for failed or blocked resources. If it is a system font, inspect the actual container or runner rather than assuming your laptop’s font installation is present there.
3. Install the required browser and font dependencies in Linux
Playwright documents installing a browser and its system dependencies together. For Chromium, run:
npx playwright install --with-deps chromium
Use the corresponding browser argument if your project runs Firefox or WebKit. This installs Playwright’s browser dependencies; it does not guarantee that every font your site needs, particularly application-specific or proprietary fonts, is available. Install and verify the fonts your application actually requires according to the base distribution and your font licensing terms. See the official browser installation documentation.
For Docker, use a Playwright image tag that matches the Playwright version in your project. The documented image includes browser binaries and system dependencies, while the Playwright package still needs to be installed in the project. A version mismatch can prevent Playwright from finding browser executables. Check the current Playwright Docker documentation and choose the matching tag rather than copying an unverified version number.
A minimal Docker setup can install your project’s dependencies and browsers as follows. Select a base image and font installation commands suitable for your application, then pin the Playwright image version to the package version you use:
# Use a Playwright image tag matching the version in package.json.
FROM mcr.microsoft.com/playwright:v<matching-version>-noble
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
# If the app needs additional fonts, install the appropriate packages
# for this base distribution here, subject to your font licenses.
CMD ["npx", "playwright", "test"]
The placeholder is intentional: use the tag documented for your project’s exact Playwright release. Do not assume a font package name or that the base image contains a font family your application requires.
4. Wait for the page and web fonts before capturing
A screenshot can be taken while the page is still changing or before a web font finishes loading. Wait for the application’s own ready state and, when relevant, the browser’s font-loading set before taking the screenshot:
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch();
const page = await browser.newPage({ viewport: { width: 1280, height: 800 } });
await page.goto('https://example.com', { waitUntil: 'load' });
await page.locator('h1').waitFor({ state: 'visible' });
// Wait for fonts currently requested by the document to finish loading.
await page.evaluate(() => document.fonts.ready);
await page.screenshot({ path: 'page.png', fullPage: true });
await browser.close();
})().catch((error) => {
console.error(error);
process.exitCode = 1;
});
document.fonts.ready waits for the document’s font-loading set to settle; it does not make a failed font request succeed or install a missing system font. If the application loads content or styles after the initial page load, wait for the relevant app-specific condition too. Prefer a real readiness signal, such as a visible page element or app state, over an arbitrary delay.
For Playwright Test visual assertions, toHaveScreenshot() waits for two consecutive screenshots to match before comparing the final image. That helps with transient visual changes, but it cannot supply a missing font or make separate operating systems equivalent. See visual comparisons and the PageAssertions API.
5. Use a reproducible Playwright Test setup
This example captures a page after navigation, an application-specific readiness check, and font readiness. It assumes Playwright Test is installed and configured in the project:
const { test, expect } = require('@playwright/test');
test('page renders with its intended fonts', async ({ page }) => {
await page.goto('https://example.com', { waitUntil: 'load' });
await page.locator('h1').waitFor({ state: 'visible' });
await page.evaluate(() => document.fonts.ready);
await expect(page).toHaveScreenshot('home.png', {
fullPage: true,
});
});
Run the baseline update and comparison in the same pinned environment when possible. If the product deliberately supports multiple platforms, keep environment-specific snapshots. Use screenshot assertion options only after confirming that the intended font is available and loaded; loosening pixel thresholds can hide a font regression.
6. Choose the environment strategy that fits your project
| Approach | Good fit | Trade-off |
|---|---|---|
| One pinned container and one canonical browser | Teams that want stable visual baselines | Requires maintaining the image and its required fonts. |
| Host-managed CI runners | Tests intended to reflect a managed host environment | Host image or installed fonts may change; keep track of runner changes. |
| Separate baselines by OS or browser | Products that explicitly support and test multiple rendering environments | More baselines to generate and maintain. |
| Application-served web fonts | Consistent typography across machines when font delivery works | Requires reliable font requests and correct readiness handling. |
| Locally installed fonts | Controlled environments with a known font package | Every runtime needs the font, and redistribution terms may apply. |
Choose based on reproducibility, maintenance, fidelity to the production environment, and font licensing. The right choice depends on whether your goal is a stable regression baseline or a faithful capture of each supported platform.
7. Troubleshoot common wrong-font symptoms
| Symptom | Likely cause | What to check or fix |
|---|---|---|
| Looks correct locally, wrong in Linux CI | The CI runtime lacks the intended system font or differs from the baseline environment. | Inspect fonts in the actual CI image; install required fonts and align the baseline environment. |
| Only Docker screenshots are wrong | Different image, dependencies, or Playwright/browser version. | Use the documented Playwright image with a tag matching the project version; verify the required fonts separately. |
| Screenshot sometimes uses fallback | Capture happens before a web font finishes loading, or the font request fails intermittently. | Wait for app readiness and document.fonts.ready; inspect font requests and console errors. |
Computed font-family looks right, rendered text does not |
The declaration names a family, but the font may be unavailable, a weight/style may be missing, or glyph coverage may be incomplete. | Verify the actual font resources and required weights/styles/glyphs in the runtime. |
| All screenshots differ slightly across machines | OS, browser version, rendering settings, hardware, or headless mode differ. | Use the baseline environment or maintain environment-specific baselines. |
| Increasing the diff threshold makes the failure disappear | The comparison became more tolerant; the root font mismatch may remain. | Confirm font availability and environment first, then tune assertion settings for legitimate rendering variation. |
| Browser install succeeds but a font remains missing | Browser dependencies are present, but the application-specific font is not. | Install or serve the required font explicitly; browser dependency installation is not a universal font installer. |
8. Or skip the browser setup
If you need a page capture without installing and maintaining Playwright browsers in your own runtime, ScreenshotNeo is a website screenshot API and MCP server. One GET request returns an image or PDF, and its options include custom viewport and full-page capture.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com -o shot.webp
See the ScreenshotNeo API documentation for parameters and response details. Cookie banners are accepted and removed before the shot, along with 60+ known consent platforms, newsletter popups, and chat widgets; each step can be turned off. Bot checks, blank pages, failed loads, timeouts, and cache hits cost nothing, and response headers report the page verdict and billing status. An MCP server lets AI agents use screenshot tools. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 screenshots.
Sign up free for 1,000 screenshots a month, with no card required.
FAQ
Does Playwright choose a different font for my page?
Playwright drives the browser. The browser applies the page’s CSS using fonts it can access in that rendering environment.
Will waiting for network idle guarantee that the font is ready?
Do not treat network idle alone as proof that a particular font loaded correctly. Wait for the application’s state and check font readiness and failed font requests.
Should I update the snapshot or change the threshold?
First confirm the intended font, its availability, and a matching rendering environment. Update a baseline only when the rendered result is an intentional change.
Can a screenshot service make local and CI fonts identical?
A hosted capture service can remove the need to manage your local browser installation for that capture, but it does not make its rendering environment identical to your Playwright runner. Use a controlled, consistent environment for reproducible visual regression tests.


