Puppeteer Screenshot of a Mobile Website at 360px Width for Indian Users
Set Puppeteer to a reproducible 360 CSS-pixel viewport, capture a mobile screenshot, and account for the locale, location, and emulation limits relevant to India-facing sites.
To screenshot a site at exactly 360 CSS pixels wide with Puppeteer, create a page, set its viewport to 360 pixels before navigating, then call page.screenshot(). The example below uses an 800-pixel viewport height, a device scale factor of 1, mobile mode, and touch input. Those are explicit test settings, not claims about every phone or user in India.
1. Set up Puppeteer
Use a current Node.js installation and install Puppeteer in a project directory:
npm install puppeteer
Puppeteer downloads a compatible browser by default. If your environment supplies its own Chrome or Chromium, configure the executable path as shown in the environment notes below.
2. Capture a page at 360px
Save this as screenshot-360.js. It accepts the target URL as a command-line argument and writes a viewport screenshot to mobile-360.png.
const puppeteer = require('puppeteer');
async function main() {
const url = process.argv[2];
if (!url) {
throw new Error('Usage: node screenshot-360.js https://example.com');
}
const browser = await puppeteer.launch({ headless: true });
try {
const page = await browser.newPage();
await page.setViewport({
width: 360,
height: 800,
deviceScaleFactor: 1,
isMobile: true,
hasTouch: true,
});
await page.goto(url, {
waitUntil: 'networkidle2',
timeout: 45_000,
});
await page.screenshot({ path: 'mobile-360.png' });
console.log('Saved mobile-360.png');
} finally {
await browser.close();
}
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
Run it with:
node screenshot-360.js https://example.com
The default screenshot captures the current viewport. To capture the full document instead, use await page.screenshot({ path: 'mobile-360-full.png', fullPage: true });. Choose one deliberately: a viewport capture is useful for checking the initial mobile view, while a full-page image is useful for inspecting content below the fold.
3. Understand the viewport settings
| Setting | What it controls | How to choose |
|---|---|---|
width |
CSS-pixel viewport width | Keep it at 360 for this test. |
height |
CSS-pixel viewport height | Set a height that represents the scenario you want to inspect. The example’s 800 is only a chosen value. |
deviceScaleFactor |
Relationship between CSS pixels and rendered device pixels | Use 1 for a straightforward baseline. Set a different value when you need to inspect higher-density rendering; record it so image dimensions are reproducible. |
isMobile |
Enables mobile viewport behavior | Set it when testing a mobile presentation. Some pages may reload when mobile emulation settings change. |
hasTouch |
Emulates touch capability | Enable it when site behavior depends on touch input. |
Set the viewport before navigation. Puppeteer documents that resizing after a page loads can be unexpected for sites that assume the viewport is stable, and changing isMobile or hasTouch can trigger a reload in some cases. See the official Page.setViewport API and Page.screenshot API.
4. Test a particular India-facing experience
A 360 CSS-pixel screenshot checks one responsive layout width in Chromium. It does not represent a universal or uniquely Indian handset size. If the page varies by user context, make those inputs part of the test instead of assuming a single India-wide setting.
- Language and locale: configure the browser context or page to match the language experience you intend to review. A single locale cannot stand in for all visitors.
- Location: emulate a relevant location when the site uses geolocation or location-based content. Choose a specific test location that matches your use case.
- Time zone: set a time zone if dates, opening hours, or other time-dependent content are important to the screenshot.
- Network and CPU: test throttled conditions if loading behavior matters. Chrome DevTools Device Mode provides network and CPU throttling controls.
- Orientation and touch: test these separately if the design or controls depend on them.
Puppeteer’s Page.emulate API can apply a named device’s metrics and user agent as a shortcut. For an exact 360 CSS-pixel check, explicit viewport dimensions make the intended width clear; a device preset should not be assumed to have that width without checking its metrics.
Chrome describes Device Mode as a first-order approximation of a mobile experience, and notes that code is not actually running on a mobile device. Emulation is useful for repeatable layout checks, but verify important hardware-dependent behavior on real devices as well. See Chrome DevTools Device Mode and Sensors: Emulate device sensors.
5. Choose when the screenshot is taken
networkidle2 waits until there are no more than two network connections for the relevant idle period. This is a useful general choice, but analytics, streaming, long polling, and other persistent requests can prevent a page from reaching a network-idle state. For those sites, wait for a meaningful selector or use a short delay after navigation.
// Wait for a page element that indicates the main content is ready.
await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 45_000 });
await page.waitForSelector('main', { timeout: 15_000 });
await page.screenshot({ path: 'mobile-360.png' });
If content appears after a known client-side update, a controlled delay can help, though selector-based waits are usually more tied to the page’s actual state. Lazy-loaded images may need scrolling into view or a full-page capture workflow before taking the final screenshot; inspect the result to confirm the content you need has loaded.
6. Optional capture variations
Use a named device profile
const devices = puppeteer.KnownDevices;
const device = devices['iPhone 13'];
await page.emulate(device);
await page.goto(url);
await page.screenshot({ path: 'device.png' });
A device profile applies its device metrics and user agent. Use it when the named profile itself is the scenario under test. For a precisely specified 360 CSS-pixel viewport, prefer the explicit settings in the main example and set any user-agent requirement intentionally.
Capture a selected element
const element = await page.waitForSelector('main');
await element.screenshot({ path: 'main-content.png' });
Element screenshots are useful for a component or content region. If the selector is missing or matches an element that is hidden, the wait or capture can fail; choose a stable selector and wait for it to become visible.
Set a locale or user agent
await page.setExtraHTTPHeaders({ 'Accept-Language': 'en-IN,en;q=0.9' });
await page.setUserAgent('YOUR_TEST_USER_AGENT');
These are examples of explicit request settings, not a claim that one language header or user agent represents Indian visitors. Choose values that correspond to the experience you need to validate.
7. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Screenshot has desktop layout | Viewport was set after navigation, or mobile behavior depends on mobile signals. | Set the viewport before page.goto(); confirm isMobile and, if relevant, the user agent. |
| Screenshot dimensions seem larger than 360 pixels | deviceScaleFactor is greater than 1, or the image is being interpreted in device pixels. |
Use factor 1 for a 360-pixel-wide output baseline, or account for the scale factor when reading image dimensions. |
| Navigation times out | The page keeps network connections open or loads slowly. | Use domcontentloaded, then wait for a relevant selector; adjust the timeout for the page and environment. |
| Screenshot is blank or incomplete | Capture happened before client-rendered content appeared, or the page failed to load. | Wait for a content selector, inspect navigation errors, and capture after the expected content is present. |
| Images below the fold are missing | They are lazy-loaded and have not entered the loading region. | Scroll through the page before capture or use a full-page capture after loading; check the output. |
Could not find Chrome or launch failure |
Browser download is unavailable or the runtime expects a system browser. | Install Puppeteer’s browser during setup or configure puppeteer.launch({ executablePath: '/path/to/chrome' }) for an installed Chrome/Chromium. |
| Emulation change unexpectedly reloads the page | Mobile or touch metrics changed after page creation or navigation. | Apply viewport and emulation settings before navigation, then wait for the page again. |
8. Performance, reliability, and cost
Launching a browser for each URL adds startup work. For a batch, reuse one browser process and create a fresh page per capture, while closing pages and the browser reliably. Limit concurrency to fit available CPU and memory; large full-page images and high device scale factors increase image size and processing work. Keep navigation and selector timeouts finite so a stalled site does not hold a job indefinitely.
For repeatable visual checks, keep width, height, scale factor, mobile signals, locale, location, wait condition, and capture scope fixed. Page content can still change between runs because of personalization, time-sensitive data, experiments, or network conditions. Save the configuration alongside the image when the result needs to be compared later.
Puppeteer itself is software rather than a per-screenshot service, but running Chromium has infrastructure costs: compute, memory, storage, and engineering time to manage browser versions and failures. For occasional checks, a local script is often enough. For recurring captures or integrations, compare the cost of maintaining that setup with a hosted screenshot API.
Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server. Its API accepts a URL in one GET request and returns an image or PDF. Cookie banners are accepted like a visitor and removed before the shot, along with supported consent platforms, newsletter popups, and chat widgets; each cleanup step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and responses identify the page verdict and billing status. Its MCP server lets AI agents use take_screenshot, get_page_info, and capture_pdf.
For this example, set the target URL to the page you want captured. See the ScreenshotNeo documentation for API details.
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}`);
The free plan includes 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 screenshots; all features are on every plan. Sign up for free and capture up to 1,000 screenshots a month with no card.
FAQ
Does 360px mean the screenshot file will be 360 pixels wide?
It is a 360 CSS-pixel viewport. The output image’s pixel dimensions also depend on the device scale factor and capture settings.
Is 360px the standard mobile width in India?
This guide does not establish that. It is one useful width to test, not a universal or India-specific device statistic.
Does Puppeteer emulation prove the page works on an actual phone?
No. It is a repeatable browser approximation. Use real devices for behavior that depends on hardware or a specific mobile browser.
Should I use a full-page screenshot?
Use a viewport capture for the initial visible screen and a full-page capture when reviewing the entire document. Lazy content may require additional loading before capture.


