How to Use an AI Agent to Screenshot a Hindi Webpage with Correct Font Rendering
Wait for Hindi content, request the relevant fonts, and inspect the screenshot. This guide shows a reliable Playwright workflow and its limits.
To screenshot a Hindi webpage with correct font rendering, have your AI agent wait for the page’s final Hindi content, request any critical font with representative Devanagari text, wait for the browser’s used fonts to finish loading, then capture and inspect the result. document.fonts.ready confirms that currently used font loading and related layout work have settled; it does not prove that every Hindi glyph came from the intended typeface.
This guide uses Playwright because it exposes the page context and screenshot controls needed for this workflow. It does not prescribe a particular Hindi font: the right face depends on the page and its CSS. For visual comparisons, keep the browser and operating environment consistent with your baseline.
1. What “correct font rendering” means
Hindi text is written in Devanagari. A page may declare a preferred font but use fallback fonts when that font has not loaded, does not cover a character, or is unavailable in the browser environment. A capture can therefore finish successfully while the text still looks wrong.
Separate the task into two checks:
- Readiness: wait until the final content is present and the browser has finished loading fonts currently used by the document.
- Visual correctness: inspect the screenshot for fallback-looking glyphs, missing marks, or incorrect shaping. Font-loading promises do not certify glyph coverage or visual quality.
MDN documents the browser’s font loading APIs: FontFaceSet.ready, Document.fonts, and FontFaceSet.load().
2. Prepare Playwright
Install Playwright and its Chromium browser in a Node.js project:
npm install playwright
npx playwright install chromium
Save the following as screenshot-hindi.mjs. It accepts a URL, a CSS selector for the final Hindi content, and an optional font family name. The selector should target content whose visibility means the page is ready to capture; adapt it to the site rather than relying on navigation completion alone.
3. Complete runnable Playwright example
import { chromium } from 'playwright';
const url = process.argv[2];
const contentSelector = process.argv[3];
const fontFamily = process.argv[4];
if (!url || !contentSelector) {
console.error('Usage: node screenshot-hindi.mjs <url> <content-selector> [font-family]');
process.exit(2);
}
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage({ viewport: { width: 1365, height: 900 } });
try {
page.on('requestfailed', request => {
console.error('Request failed:', request.url(), request.failure()?.errorText);
});
page.on('console', message => {
if (message.type() === 'error') console.error('Page console error:', message.text());
});
const response = await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 30000 });
if (!response || !response.ok()) {
throw new Error(`Navigation did not return a successful document${response ? `: HTTP ${response.status()}` : ''}`);
}
await page.locator(contentSelector).first().waitFor({ state: 'visible', timeout: 30000 });
const fontResult = await page.evaluate(async ({ family }) => {
const sample = 'हिन्दी पाठ परीक्षण';
let requestedFaceCount = null;
if (family) {
const descriptor = `400 16px "${family.replaceAll('\\', '\\\\').replaceAll('"', '\\"')}"`;
const faces = await document.fonts.load(descriptor, sample);
requestedFaceCount = faces.length;
}
await document.fonts.ready;
return {
status: document.fonts.status,
requestedFaceCount,
sample,
};
}, { family: fontFamily });
console.log('Font loading state:', fontResult);
await page.screenshot({ path: 'hindi-page.png', fullPage: true });
console.log('Saved hindi-page.png. Inspect it at a readable scale for glyph and shaping problems.');
} finally {
await browser.close();
}
Run it with a real page URL and a selector that identifies the final Hindi content:
node screenshot-hindi.mjs 'https://example.com/hi/article' 'main article' 'YourHindiFont'
Omit the fourth argument if no particular font family must be explicitly requested:
node screenshot-hindi.mjs 'https://example.com/hi/article' 'main article'
The sample phrase in the script is only a trigger for loading faces relevant to those characters. Choose representative text that covers the characters and combinations in the actual capture. The returned face count and resolved promise are diagnostic signals, not proof that every displayed glyph uses the desired font.
4. Why the order of waits matters
- Navigate to the page. The example waits for
domcontentloadedto avoid treating a full load event as a proxy for app readiness. - Wait for the final content. A client-rendered application may still be fetching or rendering after navigation. Wait for a selector or application state that represents the content the screenshot is meant to show.
- Request the important font, if known.
document.fonts.load(fontDescriptor, text)asks the browser to load faces relevant to the descriptor and supplied text. Catching a rejection is important when font readiness is required; this example allows an error to fail the capture rather than silently pretending the requested face loaded. - Wait for used fonts and layout. Await
document.fonts.readyafter the relevant content is visible. The set of fonts in use can depend on the content and layout. - Capture, then inspect. Review the saved image at a scale where marks and shaping are legible. A technically completed capture may still contain fallback glyphs.
Some declared font faces may never be used, so document.fonts.ready does not mean every declaration on the stylesheet has loaded. Conversely, explicitly loading a face for sample text does not inspect each glyph on the page.
5. Waiting for a stable visual comparison
For one-off capture, call page.screenshot() after the readiness sequence. For screenshot regression tests, Playwright’s toHaveScreenshot() assertion can wait for two consecutive screenshots to match before comparing. This helps reduce captures taken during transient visual changes; it does not validate Hindi font coverage.
import { test, expect } from '@playwright/test';
test('Hindi page renders consistently', async ({ page }) => {
await page.goto('https://example.com/hi/article', { waitUntil: 'domcontentloaded' });
await page.locator('main article').waitFor({ state: 'visible' });
await page.evaluate(async () => {
await document.fonts.load('400 16px "YourHindiFont"', 'हिन्दी पाठ परीक्षण');
await document.fonts.ready;
});
await expect(page).toHaveScreenshot('hindi-article.png', {
fullPage: true,
animations: 'disabled',
caret: 'hide',
});
});
Animations and caret hiding can reduce incidental differences, but they do not fix missing font resources, unsupported glyphs, or shaping issues. Keep snapshots separated by browser and platform where rendering differs.
6. Options and decisions
| Decision | Use it when | Limit to remember |
|---|---|---|
| Wait for a content selector | The page is client-rendered or updates after navigation | The selector must correspond to the final state you need |
Use document.fonts.ready |
You need currently used font loading and layout work to settle | It does not load every declared face or validate individual glyphs |
Call document.fonts.load() with Hindi text |
A specific family is required and may otherwise not yet be used | Use representative text; resolution does not certify coverage |
| Take one screenshot | You need an artifact for review or downstream use | Inspect it visually when font correctness matters |
Use toHaveScreenshot() |
You need a repeatable visual regression comparison | Use a consistent browser and operating environment |
Set fullPage: true |
The full document is needed | Long pages can take longer and produce large image files |
| Set viewport dimensions | You need a specific responsive layout | Text wrapping and lazy content can change with viewport size |
7. Troubleshooting
| Symptom | Likely cause | What to check or change |
|---|---|---|
| Screenshot has fallback-looking Hindi characters | The intended font did not load, was not applied, or lacks relevant coverage | Inspect the page’s computed font styles and failed font requests; verify the family name and CSS rules; inspect the screenshot itself |
| Marks or conjuncts look malformed | The chosen face, browser, or shaping environment differs from the intended setup | Check the actual rendered output, font resource, CSS, browser engine, and operating environment |
document.fonts.load() rejects or returns no expected faces |
The resource may fail, the descriptor may not match a declared face, or the requested text may not select that face | Check the exact family and weight in CSS, network failures, and the supplied representative text |
| Capture happens before Hindi content appears | Navigation finished before client rendering or data loading did | Wait for the final content selector or a page-specific ready condition before waiting on fonts |
| Capture or font readiness times out | A resource or browser operation is stalled, or a browser-specific issue is involved | Record Playwright, browser-engine, and OS versions; inspect font-face states and failed font requests; investigate before bypassing readiness |
| Snapshots differ between machines | Rendering can vary with OS, browser version, settings, hardware, power source, or headless mode | Compare using the same browser and operating environment, or maintain separate baselines for each target environment |
| Full-page shot misses content loaded on scroll | The page loads images or sections lazily | Use a page-specific scroll or readiness routine before capture and confirm the required content is present |
A reported Playwright issue describes Linux WebKit screenshot capture waiting on fonts and timing out in Playwright 1.63.0, with a 1.60.0 control succeeding. It is an example report, not a confirmed general defect or official workaround. If your capture stalls, record versions and inspect the report’s current status before making a version-specific change: Playwright issue #42986.
8. Performance, reliability, and cost
Font readiness can add latency because the browser may need to fetch fonts before it can finish layout. Waiting for the actual content first avoids waiting against an incomplete view of the page. Keep navigation and selector timeouts bounded, log failed requests, and fail clearly when a required font load rejects. Do not skip the font wait merely to make a capture faster if correct Hindi rendering is the point of the job.
For dependable visual comparisons, pin the Playwright/browser setup used by your workflow and capture snapshots under the same operating conditions. If you run across different engines or platforms, treat them as distinct rendering baselines. Playwright explains that browser rendering can vary with host OS, browser version, settings, hardware, power source, and headless mode; see its visual comparison guidance.
With a self-hosted Playwright workflow, costs depend on your own compute and browser infrastructure; this workflow itself has no per-screenshot API charge. Account for runtime, storage, and maintenance in your environment. Playwright’s screenshot API details are in the Page API, and its screenshot assertion behavior is documented under PageAssertions.
9. Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server for developers. One GET request returns a PNG, JPEG, WebP, or PDF. For Hindi text, the same practical check still applies: inspect the capture and confirm the page’s output matches what you need. See the ScreenshotNeo API documentation.
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}`);
ScreenshotNeo accepts cookie or consent banners like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits cost nothing, and response headers identify the page verdict and billing status. Its MCP server gives AI agents tools for screenshots, page information, and PDF capture. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 screenshots.
Sign up for 1,000 free screenshots a month with no card.
10. FAQ
Does a resolved document.fonts.ready mean the Hindi font is correct?
No. It means the document’s currently used fonts and relevant layout work have settled. Inspect the rendered screenshot for the actual glyphs and shaping.
Does the browser need to load every font declared in the CSS?
No. Some declared faces may remain unused. Request a critical face explicitly with representative Devanagari text when it matters.
Can one screenshot baseline work across all operating systems?
Not reliably. Browser rendering can differ across operating systems and browser environments, so use matching conditions or maintain separate baselines.
Should I choose a particular Hindi font?
This workflow does not prescribe one. Use the typeface required by your page or design, then check that it loads, is applied, and renders the relevant text correctly.


