ScreenshotNeo

BlogHow-to

How to capture a Puppeteer screenshot of a page with an Indian-language web font

Wait for the target text and its web fonts before capturing with Puppeteer. Learn script-aware font selection, failure checks, and a complete runnable workflow.

By the ScreenshotNeo team4 October 20268 min read

To capture a Puppeteer screenshot with the intended Indian-language web font, first wait for the page content containing that text, then wait for the document’s used fonts and layout to settle, and finally call page.screenshot(). If a particular font is essential, explicitly request that face for representative text and check whether it loaded: document.fonts.ready does not prove that the preferred font succeeded.

“Indian-language” covers many scripts. Choose a family that supports the language and glyphs on the page, configure a deliberate fallback stack, and verify the result in the browser runtime that will produce the screenshot.

1. Install Puppeteer and prepare the page

Use a selector that identifies the actual content to capture. Replace the example URL and selector with values for your page. The selector is important because navigation can finish before a client-rendered component or its text appears.

npm install puppeteer

Save this as screenshot.mjs and run it with node screenshot.mjs:

import puppeteer from 'puppeteer';

const browser = await puppeteer.launch();
try {
  const page = await browser.newPage();
  await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });

  // Replace this with a selector for the component containing the target text.
  const contentSelector = '[data-screenshot-content]';
  await page.waitForSelector(contentSelector, { visible: true });

  // Wait for used fonts and their associated layout work.
  await page.evaluate(async () => {
    await document.fonts.ready;
  });

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

Puppeteer’s documentation describes its browser automation API, including page navigation and screenshot capture. The screenshot call captures the current rendered page; waiting for the content and fonts is a separate synchronization step.

2. Select a font for the page’s script

Use a font family that covers the script actually present. For example, a page containing Hindi in Devanagari could use a stack such as:

body {
  font-family: "Noto Sans Devanagari", "Noto Sans", sans-serif;
}

This is an example, not a universal stack. For Tamil, use a Tamil-capable family; for other scripts, select an appropriate family and fallback. Check the page’s punctuation, numerals, symbols, and font weights as well as its main letters. Noto recommends choosing fonts for the scripts a site uses and avoiding unnecessary families or weights. See Noto’s web font guidance.

If the page hosts its own font files, make sure the @font-face family name and weight match the CSS request, and that the file is reachable from the Puppeteer runtime. A missing face, failed request, or missing glyph coverage can cause fallback rendering. Check the font status and network behavior rather than guessing from the screenshot alone.

3. Explicitly request a critical font

When a specific face must be used, request it after the target content appears and before capturing. Pass a CSS font shorthand and representative text from the target script:

await page.evaluate(async (selector) => {
  const sample = document.querySelector(selector)?.textContent ?? '';
  await document.fonts.load('400 16px "Noto Sans Devanagari"', sample);
  await document.fonts.ready;
}, '[data-screenshot-content]');

Replace the family, weight, size, and selector with those used by the page. This example only makes sense for a page configured with that Devanagari family. A resolved promise alone is not confirmation that the preferred face loaded. For a critical font, check the returned faces’ statuses and inspect the captured result.

The browser’s document.fonts API exposes the document’s font set. Its ready promise resolves after loading and layout operations for used fonts finish. Some declared faces can remain unloaded if they are not used, so wait for the relevant content first. Optional font-display behavior may also affect whether a face is used.

4. Complete example with a font check

This version waits for the content, explicitly requests a face for its text, waits for font readiness, and reports whether the requested face is loaded before saving a full-page image. Change the selector and font to match the site.

import puppeteer from 'puppeteer';

const url = 'https://example.com';
const contentSelector = '[data-screenshot-content]';
const fontFamily = 'Noto Sans Devanagari';
const fontShorthand = `400 16px "${fontFamily}"`;

const browser = await puppeteer.launch();
try {
  const page = await browser.newPage();
  await page.goto(url, { waitUntil: 'domcontentloaded' });
  await page.waitForSelector(contentSelector, { visible: true });

  const fontCheck = await page.evaluate(async ({ selector, shorthand }) => {
    const element = document.querySelector(selector);
    if (!element) throw new Error(`Content not found: ${selector}`);

    const sample = element.textContent ?? '';
    const faces = await document.fonts.load(shorthand, sample);
    await document.fonts.ready;

    return {
      requestedFaceCount: faces.length,
      statuses: faces.map((face) => face.status),
      check: document.fonts.check(shorthand, sample),
    };
  }, { selector: contentSelector, shorthand: fontShorthand });

  console.log('Font check:', fontCheck);
  await page.screenshot({ path: 'page.png', fullPage: true });
} finally {
  await browser.close();
}

Interpret the check in the context of the page’s CSS and font configuration. If the face is critical and the result is unexpected, inspect browser console and network errors and examine the screenshot. Font API checks help diagnose loading; visual inspection remains useful for glyph coverage and shaping.

5. Choose the right wait condition

Wait What it helps with Limit
waitUntil: 'domcontentloaded' Waits for the initial document parse milestone. Does not establish that client-rendered content or fonts are ready.
waitForSelector() Confirms that a chosen page component exists; visible: true also asks Puppeteer to wait until it is visible. Choose a selector tied to the content you need. A generic selector may appear too early.
document.fonts.ready Waits for loading and layout work for fonts currently used in the document. Does not force every declared font to load or certify that your preferred face succeeded.
document.fonts.load() Requests a particular CSS font shorthand for supplied text. Use the correct family, weight, and representative script text; check the result.
Network idle Can help when the page settles after network activity. It is not proof that the intended text exists or the intended font was selected.

For asynchronously rendered text, wait for its selector or a page-specific predicate before checking fonts. Puppeteer documents its navigation and page-state waiting options in the Page API.

6. Troubleshooting

Symptom Likely cause What to check or change
Boxes, missing glyphs, or incorrect characters The chosen face lacks the required script or glyphs, or the font file failed to load. Confirm script coverage, inspect font requests and console errors, and provide a suitable fallback family.
The screenshot uses a fallback despite document.fonts.ready Readiness means used-font loading and layout have settled; it does not certify the preferred face. Request the critical face with document.fonts.load(), inspect returned face statuses, and verify the result visually.
Text is absent or only partly rendered The page’s client-side content has not appeared when the font check or screenshot runs. Wait for a selector or predicate tied to the target text, then wait for fonts.
Local captures work but CI or container captures do not The runtime may not be able to reach the font asset, or may use different browser and font configuration. Check asset reachability and errors in the same runtime that captures the page. Validate that environment directly.
Some punctuation, numerals, or symbols look different The primary family may not cover every character, or the browser may be using fallback glyphs. Test representative page text, select suitable fallbacks, and verify the weights and symbols the page actually uses.
Capture waits indefinitely or fails before screenshot The selector may never appear, navigation may fail, or a page-specific wait may have no timeout strategy. Confirm the URL and selector, surface navigation errors, and apply a finite timeout appropriate to your page.

7. Performance, reliability, and cost

  • Wait for the needed state: A content-specific selector plus font readiness usually expresses the requirement more directly than waiting an arbitrary fixed delay. Add a delay only when the page has a known behavior that needs it.
  • Keep font loading focused: Request the specific family and weight needed for the target text. Loading unnecessary script families and weights can add work and network requests.
  • Make checks bounded: Use finite navigation and selector timeouts in production automation, and log which wait failed. This makes broken pages and inaccessible font assets diagnosable.
  • Validate the capture runtime: Browser version, page CSS, asset access, and runtime configuration can affect output. Check the actual environment used for scheduled or CI captures.
  • Control external dependencies: A remotely hosted font depends on that asset being reachable during capture. Self-hosting can make delivery more predictable if the application supports it, but the file still must be available to the browser and correctly configured.
  • Cost: Puppeteer is browser automation you operate, so account for the compute and maintenance of your own runtime and any font delivery costs that apply to your setup. The sources here provide no universal cost or timing benchmark.

Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server. It can return a PNG, JPEG, WebP, or PDF from one GET request. For example, this captures a page as WebP:

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 request options. 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, timeouts, failed loads, and cache hits cost nothing, and response headers report the page verdict and billing status. Its MCP server provides take_screenshot, get_page_info, and capture_pdf tools for AI agents using Claude, Cursor, or another MCP client. 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, no card required.

FAQ

Does document.fonts.ready guarantee that my chosen font is in the screenshot?

No. It resolves when loading and layout operations for used fonts have finished. Request and check an essential face explicitly, then inspect the rendered result.

Should I wait for networkidle instead?

It can be useful for pages that settle after network activity, but it does not confirm that your target text appeared or that the preferred font was used. Use a content-specific wait and a font check for those requirements.

Can one font stack cover every Indian language?

Do not assume so. Choose families for the scripts, glyphs, and weights present on your page, with fallbacks for characters the main family does not cover.

Will the screenshot look identical in every environment?

That is not guaranteed by font readiness. Validate the browser and asset-loading environment that will produce the capture, especially when exact glyph appearance matters.