ScreenshotNeo

BlogHow-to

Puppeteer Screenshot Testing for Hindi Web Pages: Fix Missing Fonts

Fix missing Hindi text in Puppeteer screenshots by checking Devanagari font coverage, font loading, CSS fallback, and the exact runtime that launches Chrome.

By the ScreenshotNeo team4 October 20268 min read

If Hindi text is missing, shown as boxes, or rendered in an unexpected typeface in a Puppeteer screenshot, check two things in the environment that launches Chrome: whether an available font contains the page’s Devanagari glyphs, and whether any web font has finished loading before capture. Installing or bundling an appropriate font fixes missing glyph coverage; waiting for fonts fixes incomplete loading. These are separate problems, so verify both in the same OS or container used by CI or production.

A CSS font-family declaration alone does not prove that Chrome can locate the font or that it covers the characters being captured. Puppeteer’s Linux troubleshooting guidance calls out fonts with suitable character-set coverage in containers, but it does not identify a universally correct Hindi font package. Verify a font’s coverage and package availability for your target runtime. Puppeteer’s troubleshooting guide provides the relevant Linux and container guidance.

1. Reproduce the screenshot in its actual runtime

Start by recording the operating system or container image, Puppeteer version, and browser version used for the failing capture. A desktop result is not a reliable comparison if CI runs in a different Linux image with different fonts.

  1. Run the same capture in the deployment image or a faithful local copy of it.
  2. Record the Puppeteer and Chrome versions and the image tag or base distribution.
  3. Use the Chrome for Testing version installed by Puppeteer where practical. Puppeteer says it works best with its paired Chrome for Testing release and does not guarantee compatibility with arbitrary Chrome versions. See Puppeteer’s supported browsers documentation.
  4. Save a screenshot of a small page containing representative Hindi text, then compare it with the real page’s output from that same runtime.

This comparison helps distinguish an environment font problem from application-specific rendering or CSS. It is a diagnostic procedure, not a claim that a particular font package or setup has been tested here.

2. Separate font loading from missing glyph coverage

Wait for navigation or an application-specific ready signal, then wait for browser font loading before taking a screenshot. In the page, document.fonts.ready resolves when font loading and related layout operations have settled. It cannot install a missing font or supply a glyph absent from all available faces.

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch({ headless: true });
  try {
    const page = await browser.newPage();
    await page.goto('https://example.com/hindi-page', {
      waitUntil: 'networkidle0',
      timeout: 60000,
    });

    // If the app renders asynchronously, wait for its own ready signal here.
    // For example: await page.waitForSelector('[data-page-ready="true"]');
    await page.evaluate(() => document.fonts.ready);

    await page.screenshot({ path: 'hindi-page.png', fullPage: true });
  } finally {
    await browser.close();
  }
})();

Replace the example URL and, if available, the application-specific readiness selector. Some pages keep network requests open, so networkidle0 may never be reached or may be the wrong signal. In those cases, use an app-ready selector or another condition that matches the page’s actual rendering lifecycle, then await document.fonts.ready.

Puppeteer’s PDF options document a waitForFonts option that waits for document.fonts.ready. That is a PDF option; do not assume page.screenshot() accepts the same option. For screenshots, explicitly wait in the page as shown above. See Puppeteer PDFOptions and the screenshot API.

3. Check CSS family, weight, style, and fallback

Inspect the styles applied to the affected Hindi text. Confirm the first available family is the one intended, and check that the requested weight and style are available. A browser may fall back when a family cannot be resolved or lacks the needed glyphs.

body {
  font-family: "Your Devanagari-Capable Font", sans-serif;
  font-weight: 400;
  font-style: normal;
}

Use a font you have verified contains the Devanagari characters and punctuation present in your content. The research for this guide does not establish one Hindi font as universally correct, so choose based on your design, licensing, distribution method, and actual glyph coverage. Confirm the family name used by CSS matches the name exposed by the font or web-font declaration.

4. Make the font available to the screenshot worker

Choose one of two delivery paths. Both require adequate Devanagari coverage and a CSS stack that resolves as intended.

Approach Use it when Check
Install a font package or font file in the Linux host/container You control the capture image and want the font available independently of the page’s network-loaded assets. Verify the package exists for your exact base distribution, rebuild the image, and confirm Chrome in that image can use the family.
Serve a web font through the page’s CSS The site already manages its typography as web assets or you need the page to select its own font. Confirm the request succeeds in the capture environment, the declared family and weight match, and font loading completes before capture.

For a container-installed font, add the appropriate distribution package or font file to the image that actually runs Chrome, rebuild that image, and repeat the capture. Package names and availability vary across distributions; check the target distribution rather than copying an old package list. Puppeteer’s troubleshooting guide gives Linux font guidance, but its example packages should not be treated as Hindi-specific solutions.

For a web font, check that the capture worker can reach its URL, the server returns the font successfully, and the page’s CSS references the correct family and weight. A successful document.fonts.ready wait says loading has settled; it does not by itself prove the intended face has Devanagari coverage.

5. Validate with representative Hindi content

Use a minimal fixture containing the characters and punctuation that appear on the affected page. Capture it in the deployment runtime, inspect the output, then repeat with the full page. This separates font availability from application timing or page-specific styling.

<!doctype html>
<html lang="hi">
<head>
  <meta charset="utf-8">
  <style>
    body { font-family: "Your Devanagari-Capable Font", sans-serif; }
  </style>
</head>
<body>
  <p>यह एक उदाहरण है। अपने पेज का वास्तविक पाठ यहाँ रखें।</p>
</body>
</html>

The sample is a starting point. Replace it with representative content from your page, including punctuation, numerals, or less common characters that matter to your users. A passing result for one short string does not establish coverage for every character your application may render.

Common errors and fixes

Symptom Likely cause Fix
Hindi characters appear as empty boxes or replacement glyphs No font available to Chrome has the needed Devanagari glyphs. Install or bundle a font with verified coverage in the screenshot runtime; rebuild and capture there.
Text falls back to a different face than the browser on your desktop The runtime lacks the preferred family, the CSS family name does not resolve, or the requested weight/style is unavailable. Check the runtime’s installed fonts and the exact CSS family, weight, style, and fallback order.
The first capture is wrong but a later capture looks right The screenshot ran before a network-loaded web font finished loading. Wait for the application’s ready condition and then await document.fonts.ready before capture.
networkidle0 times out The page has persistent network traffic or never becomes idle under that condition. Wait for a page-specific ready selector or another application readiness signal, then wait for fonts.
The font works locally but not in CI or production The environments use different OS images, font packages, or browser versions. Reproduce in the deployed image, pin the capture environment, and use Puppeteer’s paired Chrome for Testing version where practical.
A font package name from an online example cannot be installed The example targets another distribution or an outdated package repository. Check the package name and availability for your current base image, or bundle an appropriate font file.
document.fonts.ready resolves but Hindi is still missing Font loading has settled, but no available face covers the missing glyphs or the CSS stack selects the wrong face. Verify actual glyph coverage and family resolution; make a suitable font available in the runtime.

Reliability, performance, and cost considerations

  • Reproducibility: Keep the OS/container image, font assets, Puppeteer version, and browser version consistent between local debugging and CI. A font change can alter line breaks and page height as well as glyph appearance.
  • Readiness: Waiting for network idle can delay or prevent capture on pages with continuing requests. Prefer an application-ready condition where one exists, then wait for fonts before taking the screenshot.
  • Deployment: A container-installed font makes the dependency part of the capture image. A web font keeps delivery with the page but depends on network access and successful loading. Either path needs explicit verification in the real runtime.
  • Cost: The dossier provides no benchmark or cost figures for font installation, web-font loading, or Puppeteer capture. Account for the operational work of maintaining a reproducible image and the time your readiness conditions add; do not assume a universal performance difference between the two font paths.

Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server from Yorker Media. A single request captures a URL as an image or PDF; see the ScreenshotNeo API documentation. For a Hindi page, first ensure the page itself serves a Devanagari-capable font and renders the intended content: a screenshot API does not replace a font missing from the page or guarantee a specific font environment.

curl -G "https://api.screenshotneo.com/v1/shot" \
  -d access_key=YOUR_API_KEY \
  --data-urlencode url=https://example.com/hindi-page \
  -o hindi-page.webp

Python:

import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={
        "access_key": "YOUR_API_KEY",
        "url": "https://example.com/hindi-page",
    },
    timeout=90,
)
r.raise_for_status()
with open("hindi-page.webp", "wb") as image_file:
    image_file.write(r.content)

Node.js:

const q = new URLSearchParams({
  access_key: 'YOUR_API_KEY',
  url: 'https://example.com/hindi-page',
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
const image = Buffer.from(await res.arrayBuffer());
await import('node:fs/promises').then(fs => fs.writeFile('hindi-page.webp', image));

With ScreenshotNeo, cookie banners, newsletter popups, and chat widgets are removed before the shot; bot checks, blank pages, and failed loads are never billed; an MCP server lets AI agents take screenshots; and 1,000 screenshots a month are free with no card, with paid plans starting at $5 for 3,000. The API response includes page-verdict and billing headers. Create a free account and get 1,000 screenshots a month with no card.

FAQ

Does setting lang="hi" install a Hindi font?

No. The language attribute identifies the document language; Chrome still needs an available font with the glyphs required by the content.

Does waiting for document.fonts.ready fix boxes?

It can prevent a capture before web fonts finish loading. It cannot supply glyphs missing from the fonts available to Chrome.

Is there one font package that fixes every Puppeteer Hindi screenshot?

No universal package is established here. Choose a font with verified Devanagari coverage and confirm it is available in your specific runtime.

Should I use networkidle0 for every page?

No. It is one possible navigation condition. Pages with persistent requests may need an application-specific ready signal instead.