How to Capture Mobile Website Screenshots for Indian-Language Pages with Playwright
Capture mobile pages with Playwright and avoid premature screenshots of Indian-language text by configuring the browser, waiting for fonts, and checking glyphs.
Use a Playwright mobile browser context, set a locale when the site depends on browser language, wait for used fonts and layout to settle, then capture the viewport or full page. Inspect the output with representative text in the target script: document.fonts.ready prevents some early captures, but it does not prove that every glyph is supported or rendered correctly.
Playwright device profiles simulate browser and device parameters; they are not screenshots from a physical phone. If the image must reflect a particular Android handset, validate on that device. Playwright documents Android automation as experimental. See the official emulation guide, screenshot guide, and Android guide.
1. Install Playwright and choose a capture target
This runnable Node.js example uses Playwright Test’s device descriptors. It captures a mobile-sized viewport and a full-page image. Pick a device profile that matches the viewport and device-scale behavior you want to reproduce, or set those context options explicitly. The profile simulates parameters such as screen size, user agent, touch behavior, and device scale factor.
npm init -y
npm install -D @playwright/test
npx playwright install chromium
Save the following as capture-mobile.mjs. Replace the URL, locale, and sample text with the page and script you need to inspect.
import { chromium, devices } from '@playwright/test';
const targetUrl = 'https://example.com';
const locale = 'hi-IN';
const sampleText = 'नमस्ते दुनिया';
const browser = await chromium.launch();
const context = await browser.newContext({
...devices['iPhone 13'],
locale,
});
const page = await context.newPage();
page.on('console', message => {
if (message.type() === 'error') console.error('Browser console:', message.text());
});
page.on('pageerror', error => console.error('Page error:', error.message));
try {
await page.goto(targetUrl, { waitUntil: 'networkidle', timeout: 60_000 });
await page.evaluate(async () => {
await document.fonts.ready;
});
// Record useful rendering signals alongside the image.
const fontReport = await page.evaluate(text => ({
language: navigator.language,
status: document.fonts.status,
sampleText: text,
bodyFont: getComputedStyle(document.body).fontFamily,
}), sampleText);
console.log(fontReport);
await page.screenshot({ path: 'mobile-viewport.png' });
await page.screenshot({ path: 'mobile-full-page.png', fullPage: true });
} finally {
await context.close();
await browser.close();
}
Run it with node capture-mobile.mjs. The example uses Chromium and an iPhone profile to produce a repeatable simulated mobile browser capture. Use the browser engine and device configuration required by your project, and record them with the output so later captures can use the same setup.
2. Set viewport, locale, and output scale deliberately
A device descriptor is convenient when you want a named profile. For custom dimensions, create a context with explicit settings. Viewport dimensions are in CSS pixels; screenshot scale determines whether the image is sized in CSS pixels or device pixels. State the intended pixel dimensions when images are used for visual comparisons or publication.
const context = await browser.newContext({
viewport: { width: 390, height: 844 },
deviceScaleFactor: 2,
isMobile: true,
hasTouch: true,
locale: 'ta-IN',
});
const page = await context.newPage();
await page.goto('https://example.com', { waitUntil: 'networkidle' });
await page.evaluate(() => document.fonts.ready);
await page.screenshot({ path: 'page.png', scale: 'css' });
await page.screenshot({ path: 'page-device-scale.png', scale: 'device' });
Use either a device descriptor or explicit context settings appropriate to the target. Avoid accidentally combining settings that conflict with the descriptor. Locale affects navigator.language, the Accept-Language request header, and browser number/date formatting; it does not translate site content or install fonts. A page may still choose its language from its URL, stored preferences, server configuration, or its own language selector.
3. Wait for fonts and verify the target script
After navigation, await document.fonts.ready resolves when loading of fonts used by the document and the associated layout operations have completed. It is a useful synchronization step before capture, especially when a page loads web fonts asynchronously. It is not a glyph-coverage test. A font can load successfully while lacking characters in the text, causing fallback or visibly different shaping.
await page.evaluate(async () => {
await document.fonts.ready;
});
const details = await page.evaluate(() => ({
status: document.fonts.status,
faces: [...document.fonts].map(face => ({
family: face.family,
status: face.status,
weight: face.weight,
style: face.style,
})),
bodyFont: getComputedStyle(document.body).fontFamily,
}));
console.log(details);
If you need to request loading for faces whose declared Unicode ranges include characters in a sample, use document.fonts.load() with the relevant CSS font shorthand and text. This can help trigger loading, but it does not test whether each individual glyph is present.
await page.evaluate(async text => {
await document.fonts.load('16px "Noto Sans Devanagari"', text);
await document.fonts.ready;
}, 'नमस्ते दुनिया');
Choose a representative sample rather than a generic Latin string. Depending on the page, inspect combining marks, conjuncts, punctuation, numerals, and mixed-script content that users actually see. Confirm visually in the generated image; browser font status alone cannot establish correct glyph rendering.
4. Capture the right region
Playwright supports distinct screenshot scopes. A viewport capture is the visible screen at the current scroll position; a full-page capture covers the page’s scrollable extent; an element capture targets one locator. Choose based on the artifact you need.
// Current viewport
await page.screenshot({ path: 'viewport.png' });
// Entire scrollable page
await page.screenshot({ path: 'full-page.png', fullPage: true });
// One component or content region
await page.locator('main article').screenshot({ path: 'article.png' });
For long pages, full-page capture can produce a tall image and may expose lazy-loading behavior different from an ordinary viewport. If content appears only after scrolling, scroll through the page before capture and allow its content and fonts to settle. If you only need a mobile screen as a user sees it, capture the viewport and avoid treating it as a full-page artifact.
5. When you need a real Android screenshot
Mobile emulation is useful for consistent automated captures, but it does not certify identical font shaping or glyph output on a particular handset. If the deliverable must come from Chrome for Android or Android WebView on real hardware, use Playwright’s Android automation and screenshot API. The official guide describes Android support as experimental, requires ADB and an Android device or Android Virtual Device (AVD), and notes that the device must be awake to produce screenshots.
For a real-device workflow, follow the setup and device connection steps in the Playwright Android documentation, then capture from the connected Android device:
import { _android as android } from 'playwright';
const devices = await android.devices();
if (devices.length === 0) {
throw new Error('No Android device or AVD found. Check ADB and device connection.');
}
const device = devices[0];
try {
const page = await device.launchBrowser();
await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
await page.evaluate(() => document.fonts.ready);
await device.screenshot({ path: 'android-device.png' });
} finally {
await device.close();
}
Use the Android device screenshot method for an artifact from the device. Validate on the actual target handset when its installed fonts, browser version, or rendering behavior are part of the requirement. An AVD can support Android automation without requiring a physical phone, but it is still not the specific handset.
6. Troubleshoot missing or substituted characters
| Symptom | Likely cause | What to check or change |
|---|---|---|
| Boxes or replacement symbols | The chosen font or fallback chain lacks a needed glyph, or the font request failed. | Inspect computed font family, font-face statuses, and browser network/console errors. Check the page’s font declarations and verify the output with representative text. |
| Latin characters look right but an Indian script does not | The Latin subset loaded, while the required script subset or font resource did not; alternatively the selected font lacks that script. | Inspect font requests and the page’s font-face declarations. Wait for used fonts, then view the rendered image. Do not treat document.fonts.ready as proof of coverage. |
| The page is in the wrong language | Locale is only a browser language signal and formatting setting, not a translation command. | Use the site’s language route or language control if available; set context locale when browser language negotiation matters. |
| Screenshot captures fallback text | Capture happened before the used web font and layout settled. | Wait for navigation and document.fonts.ready. If the site loads fonts after interaction, perform the necessary interaction first, then wait again. |
| Full-page image omits content | Content may be lazy-loaded only as it enters the viewport. | Scroll the page in increments, wait for newly visible content and fonts, and capture again. Use viewport scope if that is the actual requirement. |
Navigation times out at networkidle |
The site maintains ongoing network activity or never reaches the chosen idle condition. | Wait for domcontentloaded or a page-specific selector, then wait for fonts and the relevant content. Choose a deterministic readiness condition for the site. |
| Android screenshot fails or finds no device | ADB/device setup is incomplete, the device is asleep, or Android automation requirements are unmet. | Follow the Android guide, confirm ADB sees the device or AVD, and wake it before taking the screenshot. |
7. Make captures repeatable and efficient
- Record the environment: store Playwright version, browser engine, device profile or explicit context values, locale, target URL, screenshot scope, and scale with the capture process.
- Wait for the page condition you need: network idle is convenient but can be unsuitable for pages with persistent traffic. A page-specific selector plus font readiness can be more reliable.
- Keep output dimensions intentional: CSS scale yields CSS-pixel dimensions; device scale yields device-pixel dimensions. Higher scale creates larger image files and can take more memory for long pages.
- Separate loading from visual validation: font readiness tells you used-font loading and layout have settled; visual inspection of target-script samples checks the result.
- Use the smallest useful scope: a viewport or element capture is generally a smaller artifact than a full-page capture. Full-page images can be costly to store and inspect when pages are very long.
- For exact handset requirements: repeat on the actual device and Android browser. Emulation improves reproducibility, while real-device validation checks the target environment.
8. Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server for developers. Its one-call API can return an image or PDF, and its request parameters support mobile viewport and device options. See the ScreenshotNeo API documentation.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com -o shot.webp
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://example.com"},
timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://example.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await Bun.write('shot.webp', res);
Before capture, ScreenshotNeo accepts cookie or consent banners like a visitor and removes more than 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 are not billed, and response headers identify the page verdict and billing status. Its MCP server provides screenshot tools for AI agents. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 shots.
Sign up for 1,000 free screenshots a month, with no card required.
9. Frequently asked questions
Does setting locale: 'hi-IN' translate the page?
No. It changes browser language signals and formatting behavior. Use the site’s own localized route or controls to request translated content.
Does document.fonts.ready guarantee correct Devanagari, Tamil, or Bengali glyphs?
No. It indicates that used-font loading and related layout have completed. Inspect the actual output with representative characters; font readiness does not verify individual glyph coverage.
Should I use a mobile profile or a real Android device?
Use a profile for repeatable simulated browser captures. Use Android automation on a device or AVD when the capture must come from Android, and test the actual handset when that specific hardware is the target.
Can one screenshot represent every Indian phone?
No. A capture reflects its browser engine, viewport, scale, fonts, and page state. Choose and record the target configuration, and validate additional targets when they matter.


