ScreenshotNeo

BlogAI agents

How to Capture Indian-Language Webpage Screenshots with an AI Agent Without Broken Fonts

Capture readable screenshots of Indian-language pages with Playwright. Check font loading, glyphs, and browser consistency before trusting the image.

By the ScreenshotNeo team4 October 20268 min read

Short answer: An AI agent cannot fix every font problem with one browser setting. Use a recorded browser environment, wait for the page’s actual content and fonts to load, inspect the rendered script for missing or malformed glyphs, and only then capture the screenshot. Verify the output image: a successful capture does not prove the text rendered correctly.

This guide uses Playwright because its official API documents screenshot capture and its visual-comparison guidance explains why rendering can vary across environments. The same checks apply to other browser automation frameworks, though their APIs differ.

1. Make the capture environment reproducible

Before diagnosing a font, record the conditions that produced the image. Browser rendering can vary with the host operating system, browser version and settings, hardware, power source, and headless mode. For repeatable visual comparisons, Playwright recommends using the same environment that generated the baseline.

  • Automation framework and version
  • Browser engine and version
  • Operating system or container image
  • Headless or headed mode
  • Viewport dimensions and device scale factor
  • Fonts installed in the environment and any page-loaded web fonts

Do not assume all Indian languages need the same font. Check the actual script and text on the target page. A font may be unavailable, fail to load, or lack a needed character; fallback or missing glyphs can then change the appearance and wrapping.

2. Wait for meaningful page readiness

Navigate, then wait for a page-specific condition that indicates the content you need is present. Examples include a heading becoming visible, a known content container appearing, or an application-specific ready marker. Playwright discourages treating networkidle as a general readiness signal and recommends relying on web assertions. A quiet network does not prove that the relevant text or font is ready.

A fixed sleep can help investigate a timing issue, but it is a poor production readiness condition: it may waste time on fast loads and still be too short on slow ones.

3. Inspect font readiness and the actual text

After the target content appears, inspect the browser’s font readiness state where supported. The Font Loading API exposes document.fonts.status and font-face states. These checks are diagnostic signals, not a guarantee of complete coverage for any script.

Also inspect the text visually. Look for empty boxes or replacement glyphs, unexpected fallback appearance, broken conjuncts or diacritics, and line wrapping that differs from the expected page. Font readiness can say loading has settled while the selected font still lacks a particular character.

4. Runnable Playwright example in Python

Install Playwright and its Chromium browser in the environment that will run the capture:

python -m pip install playwright
python -m playwright install chromium

Save this as capture_page.py. It waits for a page-specific selector, waits for font loading to settle, prints diagnostic state, and saves a full-page PNG. Replace the URL and selector with ones for the target page. The script does not automatically determine whether every glyph is correct; inspect the saved image.

import asyncio
from pathlib import Path
from playwright.async_api import async_playwright

URL = "https://example.com"
READY_SELECTOR = "main"  # Prefer a selector for the actual target content.

async def main():
    async with async_playwright() as p:
        browser = await p.chromium.launch(headless=True)
        page = await browser.new_page(viewport={"width": 1440, "height": 1000}, device_scale_factor=1)
        await page.goto(URL, wait_until="domcontentloaded", timeout=60_000)
        await page.locator(READY_SELECTOR).wait_for(state="visible", timeout=30_000)

        # Diagnostic only: this does not prove target-script glyph coverage.
        try:
            await page.evaluate("() => document.fonts.ready")
            font_state = await page.evaluate("""() => ({
                status: document.fonts.status,
                faces: Array.from(document.fonts).map(face => ({
                    family: face.family,
                    status: face.status,
                    weight: face.weight,
                    style: face.style
                }))
            })""")
            print("Font state:", font_state)
        except Exception as exc:
            print("Could not inspect font state:", exc)

        await page.screenshot(path="screenshot.png", full_page=True, animations="disabled")
        await browser.close()

asyncio.run(main())

document.fonts.ready resolves when the document’s font loading and layout operations have settled. It does not ensure that the page selected the intended font or that it contains every required glyph. The output remains subject to the browser and host environment.

5. Runnable Playwright example in Node.js

Install Playwright and Chromium:

npm install playwright
npx playwright install chromium

Save as capture-page.mjs and run with node capture-page.mjs. Change the target URL and readiness selector for the page you need.

import { chromium } from 'playwright';

const url = 'https://example.com';
const readySelector = 'main';

const browser = await chromium.launch({ headless: true });
const page = await browser.newPage({
  viewport: { width: 1440, height: 1000 },
  deviceScaleFactor: 1,
});

try {
  await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 60_000 });
  await page.locator(readySelector).waitFor({ state: 'visible', timeout: 30_000 });

  try {
    await page.evaluate(() => document.fonts.ready);
    const fontState = await page.evaluate(() => ({
      status: document.fonts.status,
      faces: Array.from(document.fonts, face => ({
        family: face.family,
        status: face.status,
        weight: face.weight,
        style: face.style,
      })),
    }));
    console.log('Font state:', fontState);
  } catch (error) {
    console.warn('Could not inspect font state:', error);
  }

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

6. Capture options and what they do

Playwright’s page.screenshot() supports viewport and full-page capture, among other output options. Choose settings deliberately and keep them consistent when comparing images.

Choice Use it when Consideration
Viewport capture (default) You need what a visitor sees in the current viewport Content below the fold is omitted
full_page: true / fullPage: true You need the entire document in one image Long pages produce large images; lazy content may need scrolling or page-specific loading first
PNG Text clarity or pixel comparison matters Typically larger than compressed alternatives
Fixed viewport and scale You need repeatable wrapping and dimensions Changing either can alter line breaks and image size
Disabled animations You want less movement between captures Does not stabilize every dynamic element

For a page that loads content on scroll, full-page capture alone may not trigger every lazy-loaded section. Scroll or use the page’s own readiness behavior before capturing, then verify that the content is present. Do not use a screenshot’s successful completion as evidence that fonts or content are correct.

7. Use cURL, Python, or Node.js with ScreenshotNeo

If you want a managed screenshot request instead of installing and maintaining a browser, ScreenshotNeo accepts a URL and returns an image or PDF. Its request parameters include viewport and full-page capture controls; see the ScreenshotNeo API documentation for parameter names and response details. For a specific Indian-language page, inspect the returned image to confirm the script rendered as intended.

cURL

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

Python

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)

Node.js

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

Or skip the browser setup

Make one request to ScreenshotNeo. The examples below use the supplied example target; replace it with the page you need. See the API documentation for options and response handling.

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

ScreenshotNeo removes cookie banners, newsletter popups, and chat widgets before the shot. Bot checks, blank pages, and failed loads are never billed, and response headers say the page verdict and whether it was billed. An MCP server gives AI agents tools to take screenshots, get page information, and capture PDFs. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000.

Sign up free for 1,000 screenshots a month with no card.

8. Visual regression: keep the baseline comparable

Playwright’s toHaveScreenshot() visual assertion waits until two consecutive screenshots match before it compares or saves a result. This helps with capture stability, but it does not prove that a particular Indian script has correct glyph coverage.

Use the same framework and browser version, operating system or container image, viewport, scale, and relevant font setup for baseline and new captures. When a difference appears, first decide whether it is an environment change, a font-loading problem, a page-content change, or an expected rendering difference. Preserve the image and environment details so the issue can be reproduced.

9. Troubleshooting

Symptom Likely checks Next step
Boxes or missing characters Whether the selected font loaded and contains the needed characters; whether fallback is being used Inspect the target text and font-face state, check font availability in the actual runtime environment, and compare with a known-good browser environment
Text looks different or wraps differently Browser version, host OS/container, viewport, scale, headless mode, and font setup Record and match those conditions before comparing; inspect whether the page-loaded font completed
Screenshot occurs before text appears Whether the readiness selector represents the target content Wait for a page-specific visible condition or application ready marker rather than relying on network silence
Navigation or font wait hangs Page readiness, browser version, engine, and font loading/error state Capture diagnostic logs and test a controlled version/environment change; do not hide the issue by skipping font readiness and call the result correct
Full-page image misses content Lazy loading or content that only appears after scrolling Trigger the page’s required loading behavior and verify content before the screenshot
Visual assertion is unstable Animation, dynamic content, or mismatched baseline environment Stabilize page state, disable animations where suitable, and run baseline and comparison in matching environments

A September 2026 issue report describes a font-wait timeout in Playwright 1.63.0 with Linux WebKit on one public page; the report says a 1.60.0 control succeeded in the described setup, but intermediate versions were not bisected and the cause was not identified. Treat this as a version- and environment-specific report, not a general conclusion about WebKit. When your capture hangs, check versions and reproduce on a controlled setup.

10. Performance, reliability, and cost

  • Readiness: A page-specific condition is usually more meaningful than an arbitrary delay. Font readiness adds a useful diagnostic wait but is not a glyph correctness test.
  • Image size: Full-page images and higher device scale factors can increase output dimensions and processing time. Use the smallest viewport and capture area that meet the task.
  • Reproducibility: Pin browser/framework and runtime images for visual comparisons. A browser or host update can change rendering independently of page code.
  • Reliability: Treat navigation, readiness, font state, and visual inspection as separate checks. Capture errors and rendering errors are different failure modes.
  • Cost: A self-hosted Playwright flow has no per-shot ScreenshotNeo charge, but requires maintaining browser binaries and the execution environment. ScreenshotNeo’s free tier is 1,000 shots monthly; paid plans are Starter $5 for 3,000, Growth $15 for 15,000, Pro $39 for 60,000, Scale $99 for 250,000, and Business $249 for 1,000,000. Yearly billing gives two months free, and every feature is on every plan.

Frequently asked questions

Does waiting for document.fonts.ready guarantee correct Indian-language text?

No. It indicates font loading has settled, not that the chosen font covers every character or that the visual result is correct. Inspect the rendered target text.

Which browser engine is best for every Indian script?

The cited material does not establish a universal winning engine or a script-by-script support table. Compare the target page in the environment that matters to you and verify its glyphs.

Can I use networkidle instead of a page-specific wait?

Playwright discourages network-idle as a general readiness signal. Wait for the actual content or application state needed for the capture.

Does a stable visual assertion prove the font is right?

No. Matching consecutive captures checks stability. It does not validate script-specific glyph coverage.