Puppeteer Screenshot of an Indian E-commerce Product Page at Mobile Width
Set a mobile viewport before navigation, then capture a product page, the full page, or a single element with Puppeteer.
Use Puppeteer’s viewport emulation to set the page width and height before navigation, then call page.screenshot(). For example, this guide uses a 390 × 844 CSS-pixel viewport with mobile behavior and touch enabled. That is an explicit example configuration, not a standard width for Indian e-commerce pages. Choose dimensions that match your test or design requirement, and say whether you need the initial viewport, the full page, a clipped region, or one element.
1. Choose the capture you need
Decide what the screenshot should show before writing the capture code:
- Viewport-only: the visible screen at the selected mobile dimensions. This is Puppeteer’s default.
- Full page: the page’s full scrollable content, using
fullPage: true. - Clipped region: a rectangular area, using the
clipoption. - One element: a product image, price block, or other selected component, captured from its element handle.
These are different artifacts. A full-page screenshot can be much taller than a phone screen, while a viewport screenshot shows only what is initially visible. Element capture is useful when the deliverable is a product image or one specific section.
2. Install Puppeteer
In a new Node.js project, install Puppeteer:
npm install puppeteer
Puppeteer’s setViewport API takes dimensions in CSS pixels and supports device scale factor, mobile viewport behavior, touch, and orientation. Set the viewport before navigating: changing it later can affect the site, and some viewport changes may reload the page. See the official Page.setViewport documentation and Viewport type.
3. Capture a product page at an explicit mobile width
This ES module example captures the entire product page after network activity has mostly settled. Replace the example URL with the product page you are authorized to capture.
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch();
try {
const page = await browser.newPage();
await page.setViewport({
width: 390,
height: 844,
deviceScaleFactor: 1,
isMobile: true,
hasTouch: true,
});
await page.goto('https://example.in/product', {
waitUntil: 'networkidle2',
timeout: 60_000,
});
await page.screenshot({
path: 'product-mobile.png',
fullPage: true,
type: 'png',
});
} finally {
await browser.close();
}
Save it as capture.mjs and run node capture.mjs. The viewport is configured before goto(). Change fullPage to false or omit it for a viewport-only image.
What the viewport settings mean
| Setting | Effect | When to adjust it |
|---|---|---|
width, height |
Viewport dimensions in CSS pixels. | Use the dimensions required by the design check, bug report, or target layout. |
deviceScaleFactor |
Device pixel ratio for emulation. | Use a higher value when you need a higher-density raster output; compare it consistently across runs. |
isMobile |
Enables mobile viewport behavior. | Set it when the capture should exercise the page’s mobile layout behavior. |
hasTouch |
Enables touch support in the emulated environment. | Set it when the page’s behavior depends on touch capability. |
The example’s 390 × 844 dimensions are illustrative. The cited Puppeteer API documentation does not declare a universal mobile width for Indian e-commerce pages. A configured viewport is an emulation setting; it does not establish that every behavior matches a physical handset or a particular mobile network.
4. Use a named device profile when that matches the requirement
Puppeteer also provides KnownDevices and page.emulate(). A named profile applies device metrics and a user agent together. Use one when your test calls for that profile; use explicit viewport settings when you need to report and control the dimensions directly.
import puppeteer from 'puppeteer';
import { KnownDevices } from 'puppeteer';
const browser = await puppeteer.launch();
try {
const page = await browser.newPage();
const device = KnownDevices['iPhone 13'];
await page.emulate(device);
await page.goto('https://example.in/product', {
waitUntil: 'networkidle2',
timeout: 60_000,
});
await page.screenshot({ path: 'product-device.png', fullPage: false });
} finally {
await browser.close();
}
Apply emulation before navigation. The device profile is still browser emulation, not proof of full physical-device equivalence. See Puppeteer’s Page.emulate documentation and KnownDevices reference.
5. Choose output and capture options
page.screenshot() returns screenshot data and can also write to a path. Its documented options include image type, path, quality for supported formats, clipping, full-page capture, and background transparency. Consult the versioned ScreenshotOptions reference for the installed Puppeteer version.
// Initial viewport as JPEG
await page.screenshot({
path: 'product-viewport.jpg',
type: 'jpeg',
quality: 85,
});
// A specific rectangular region (CSS-pixel coordinates)
await page.screenshot({
path: 'product-region.png',
clip: { x: 12, y: 180, width: 366, height: 420 },
});
// Full page as WebP
await page.screenshot({
path: 'product-full.webp',
type: 'webp',
quality: 85,
fullPage: true,
});
Use a clip when you know the desired rectangle in page coordinates. Use an element screenshot when the boundary should follow the rendered element rather than manually chosen coordinates:
const productImage = await page.waitForSelector('.product-gallery img', {
visible: true,
timeout: 15_000,
});
if (!productImage) throw new Error('Product image was not found');
await productImage.screenshot({ path: 'product-image.png' });
Puppeteer scrolls an element into view if needed before taking its screenshot. Select a stable CSS selector from the page; selectors may differ between products or change as the site is updated. See the ElementHandle.screenshot reference.
6. Wait for the product content you need
networkidle2 is one navigation wait condition, but commerce pages may keep requests open or load important content later. When the relevant content has a reliable selector, wait for it explicitly and then capture:
await page.goto('https://example.in/product', {
waitUntil: 'domcontentloaded',
timeout: 60_000,
});
await page.waitForSelector('.product-title', {
visible: true,
timeout: 20_000,
});
await page.screenshot({ path: 'product-mobile.png', fullPage: true });
If images load lazily as the page scrolls, a full-page screenshot may not cause every image to finish loading in the way your workflow expects. For a specific image, wait for that image to load before element capture. For a long page, inspect the target content and choose an explicit wait strategy; do not treat a completed navigation event as proof that every product asset is ready.
7. Run the capture from other clients
Puppeteer is a Node.js library rather than an HTTP screenshot endpoint. There is no direct cURL or Python command that invokes page.screenshot() without running a Puppeteer process. These small wrappers let other clients request a capture from a Node service you control.
Minimal Node.js HTTP endpoint
import http from 'node:http';
import puppeteer from 'puppeteer';
const server = http.createServer(async (req, res) => {
const url = new URL(req.url, 'http://localhost');
if (req.method !== 'GET' || url.pathname !== '/capture') {
res.writeHead(404).end('Not found');
return;
}
const target = url.searchParams.get('url');
if (!target) {
res.writeHead(400).end('Missing url');
return;
}
let browser;
try {
const parsed = new URL(target);
if (!['http:', 'https:'].includes(parsed.protocol)) throw new Error('Unsupported protocol');
browser = await puppeteer.launch();
const page = await browser.newPage();
await page.setViewport({ width: 390, height: 844, deviceScaleFactor: 1, isMobile: true, hasTouch: true });
await page.goto(parsed.href, { waitUntil: 'networkidle2', timeout: 60_000 });
const image = await page.screenshot({ type: 'png', fullPage: true });
res.writeHead(200, { 'Content-Type': 'image/png' }).end(image);
} catch (error) {
res.writeHead(502, { 'Content-Type': 'text/plain' }).end(String(error));
} finally {
if (browser) await browser.close();
}
});
server.listen(3000);
This endpoint is a minimal illustration, not a production-ready public service. If exposed beyond a trusted local environment, add authentication, request limits, target URL restrictions, and protections against requests to internal network addresses. Otherwise, an arbitrary URL parameter can turn the service into an SSRF risk or exhaust browser resources.
cURL request to the wrapper
curl --get 'http://localhost:3000/capture' \
--data-urlencode 'url=https://example.in/product' \
--output product-mobile.png
Python request to the wrapper
import requests
response = requests.get(
'http://localhost:3000/capture',
params={'url': 'https://example.in/product'},
timeout=120,
)
response.raise_for_status()
with open('product-mobile.png', 'wb') as image:
image.write(response.content)
8. Troubleshooting
| Symptom | Likely cause | What to change |
|---|---|---|
| Screenshot uses the desktop layout | Viewport was set after navigation, or mobile behavior was not enabled. | Call setViewport or emulate before goto; set isMobile and touch behavior if needed. |
| Page navigation times out | The site continues network activity, or the timeout is shorter than the page load. | Try domcontentloaded and wait for the specific product selector; set a deliberate navigation timeout. |
| Product image or title is missing | Lazy loading, delayed client rendering, selector mismatch, or a failed resource. | Wait for the actual element and visibility; verify the selector and page state before capture. |
| Full-page image is unexpectedly tall | fullPage: true captures content beyond the initial screen. |
Set fullPage: false for the viewport or capture one element. |
| Clipped screenshot shows the wrong area | Clip coordinates do not match the rendered page position or dimensions. | Check the desired rectangle and use an element screenshot for content with a dynamic boundary. |
| JPEG or WebP options are rejected or ignored | Output type or option support differs by installed Puppeteer/Chromium version. | Check the installed version’s ScreenshotOptions documentation and use PNG as a straightforward fallback. |
| Browser process fails to launch in a container | Chromium dependencies or runtime sandbox configuration are missing. | Install the dependencies specified for the Puppeteer environment and consult its official troubleshooting guidance; avoid weakening browser isolation in a shared or public service. |
9. Performance, reliability, and cost
- Page weight drives capture time. Product galleries, recommendations, analytics, and third-party scripts can make navigation and image readiness variable. Wait for the specific content needed instead of adding a large fixed delay to every capture.
- Full-page captures use more memory and produce larger files. Prefer viewport or element screenshots when those satisfy the task. Use JPEG or WebP with an appropriate quality setting when smaller output matters; keep PNG when lossless output or transparency is required.
- Close resources reliably. The examples close the browser in
finally, including on errors. A long-running capture service should reuse browser processes carefully, limit concurrent pages, and recover when Chromium exits. - Keep runs comparable. Record the URL, viewport dimensions, device scale factor, emulation choice, wait condition, and capture mode. Dynamic prices, inventory, personalization, and experiments can make successive screenshots differ even with identical settings.
- Budget for your own runtime. Puppeteer has no per-screenshot API charge in this workflow, but you pay in compute, memory, storage, and operational effort for the machine and browser service you run. The dossier provides no benchmark for capture speed or resource use.
10. Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server. One GET request returns an image or PDF; the API parameters used by other screenshot services also work. See the ScreenshotNeo API documentation.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.in/product -o shot.webp
ScreenshotNeo accepts cookie and 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, failed loads, timeouts, and cache hits are not billed, with page verdict and billing headers in each response. Its MCP server provides take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients. The free plan includes 1,000 screenshots each 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.
11. FAQ
Is 390 × 844 the right mobile size for every Indian product page?
No. It is the explicit example used here, not a documented India-specific standard. Use the dimensions your design or test requires and report them.
Does Puppeteer emulate a real phone completely?
No physical-device equivalence is established by the viewport or named-profile APIs. They configure browser emulation, including device metrics and, for named profiles, a user agent.
Which capture mode should I use for a product listing image?
Use an element screenshot when the image alone is the output. Use a viewport or full-page capture when surrounding product information is part of the evidence.


