Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Use Puppeteer’s device emulation before you navigate. Call page.emulate() with a descriptor from puppeteer.KnownDevices, or set the viewport and user agent yourself, then capture with page.screenshot(). This reproduces browser-facing viewport, scale, touch, and user-agent conditions; it is not a guarantee of every physical-phone behavior.

What Puppeteer mobile emulation changes

Puppeteer emulation configures the browser page, not a real handset. A known device descriptor combines a user agent with device metrics. The shortcut is equivalent to setting the user agent and viewport separately, according to the Page API.

Viewport width and height are CSS pixels. Device scale factor controls the relationship between CSS pixels and rendered device pixels. isMobile controls whether the page’s meta viewport tag is honored, and hasTouch enables touch support. These are independent settings documented in the Viewport interface.

Emulation should happen before page.goto(). Many sites do not expect a phone-sized resize after navigation, and changing mobile or touch settings can reload a page. Check the descriptor name against the Puppeteer version installed in your project; the available KnownDevices collection is version-sensitive.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Prerequisites and project setup

  • Node.js with a project directory.
  • Puppeteer installed with npm install puppeteer.
  • A device name that exists in your installed release’s puppeteer.KnownDevices.
  • A URL you are authorized to capture.

The examples use ECMAScript modules. Add "type": "module" to package.json, or adapt the import to your project’s module system.

Capture a known mobile device

This complete script emulates an iPhone descriptor, waits for the network to become reasonably idle, and writes a full-page PNG.

import puppeteer from 'puppeteer';

const browser = await puppeteer.launch();
try {
  const page = await browser.newPage();
  const device = puppeteer.KnownDevices['iPhone 13'];
  if (!device) throw new Error('Device descriptor is not available in this Puppeteer version');

  await page.emulate(device);
  await page.goto('https://example.com', { waitUntil: 'networkidle2' });
  await page.screenshot({ path: 'mobile.png', fullPage: true });
} finally {
  await browser.close();
}

The official screenshot guide shows the same general sequence—navigate with networkidle2, then call page.screenshot()—but network idleness is not proof that animations, lazy images, or application data have finished. Add an application-specific wait when those states matter.

List available device descriptors

Do not assume a descriptor exists across releases. Print the names from the package you actually installed:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
import puppeteer from 'puppeteer';
console.log(Object.keys(puppeteer.KnownDevices).sort());

Use one of the printed keys exactly. If a name is missing after an upgrade, select another descriptor or configure the metrics manually.

Configure a phone without a preset

Separate settings are useful when you need a nonstandard viewport or want to make each behavior explicit.

import puppeteer from 'puppeteer';

const browser = await puppeteer.launch();
try {
  const page = await browser.newPage();
  await page.setViewport({
    width: 390,
    height: 844,
    deviceScaleFactor: 3,
    isMobile: true,
    hasTouch: true
  });
  await page.setUserAgent(
    'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1'
  );
  await page.goto('https://example.com', { waitUntil: 'networkidle2' });
  await page.screenshot({ path: 'custom-mobile.png' });
} finally {
  await browser.close();
}

Choosing viewport values

Goal Setting Effect
Trigger responsive layout width, height CSS-pixel viewport dimensions
Represent a dense display deviceScaleFactor Device scale factor; default is 1
Honor mobile meta viewport isMobile: true Includes the page’s meta viewport behavior; default is false
Exercise touch interactions hasTouch: true Reports touch support; default is false

A high device scale factor changes rendered pixel density, not the CSS width used by responsive breakpoints. Set width and height for layout testing, then choose scale for the output density you need.

Choose the screenshot you actually need

Viewport or full document

The default screenshot captures the visible viewport. Use fullPage: true to request the entire document:

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
await page.screenshot({
  path: 'long-page.webp',
  type: 'webp',
  quality: 85,
  fullPage: true
});

PNG is the default format. JPEG and WebP can reduce file size; quality ranges from 0 to 100 and does not apply to PNG. Confirm the options for your installed release in the ScreenshotOptions reference.

Capture a region

Use clip for a rectangle rather than the document:

await page.screenshot({
  path: 'hero.png',
  clip: { x: 0, y: 0, width: 390, height: 300 },
  captureBeyondViewport: true
});

captureBeyondViewport controls whether the clipped area may lie outside the current viewport. A clip and fullPage solve different problems: one selects coordinates, the other requests the whole document.

Transparent output

Hide the default page background with omitBackground: true:

await page.screenshot({ path: 'transparent.png', omitBackground: true });

The page must itself allow transparency; an opaque CSS background will still be rendered.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Capture one element

For a component rather than the page, locate it and call ElementHandle.screenshot(). Puppeteer attempts to scroll a hidden element into view first.

const card = await page.$('[data-testid="product-card"]');
if (!card) throw new Error('Product card not found');
await card.screenshot({ path: 'product-card.png' });

Wait for a stable mobile render

Navigation completion and visual completion are different. Combine a navigation policy with waits that match your application:

  1. Navigate after emulation, using waitUntil: 'networkidle2' when ongoing background requests are not expected.
  2. Wait for a meaningful selector, such as the mobile navigation or main content.
  3. Wait for images or fonts if your page reveals them after JavaScript runs.
  4. Disable or finish animations where deterministic pixels matter.
  5. Capture only after lazy-loaded content has been brought into the document’s rendered area.
await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
await page.waitForSelector('main');
await page.evaluate(() => document.fonts?.ready);
await new Promise(resolve => setTimeout(resolve, 500));
await page.screenshot({ path: 'stable-mobile.png', fullPage: true });

A fixed delay is a fallback, not a universal guarantee. Prefer a selector or application event that represents the state you intend to document.

Make captures repeatable

  • Use the same Puppeteer version, Chromium revision, viewport, scale factor, user agent, and locale for comparisons.
  • Emulate before every navigation, including after opening a new page.
  • Use a deterministic URL and test data; personalized or time-dependent pages will legitimately differ.
  • Record the descriptor name and custom settings alongside each image.
  • Choose full-page capture only when the document length is part of the test; viewport captures are faster and easier to compare.
  • Keep screenshots in a format appropriate to the job: PNG for pixel fidelity, JPEG or WebP for smaller photographic output.

Emulation covers browser-visible configuration. It should not be treated as proof that a physical phone’s GPU, battery, sensors, camera, OS text rasterization, or every browser quirk behaves identically.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Troubleshooting common failures

“Cannot read properties of undefined” for a device

Cause: the descriptor name is not present in this Puppeteer release. Fix: print Object.keys(puppeteer.KnownDevices), use an exact available key, or switch to setViewport() and setUserAgent().

The site still shows a desktop layout

Cause: emulation was applied after navigation, the viewport is too wide, or the page’s responsive rules depend on a user agent or meta viewport. Fix: create a new page, emulate before goto(), verify CSS-pixel width, and set isMobile: true when configuring manually.

Touch handlers do not run

Cause: hasTouch is false. Fix: use a descriptor that supplies touch or set hasTouch: true. This reports touch capability; it does not reproduce every hardware gesture.

Images are missing in a full-page shot

Cause: lazy loading has not been triggered or the capture started before the application finished. Fix: scroll through the page, wait for the image selectors or load events, then capture.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Best Value
The SQL Programming Language: .
  • Used Book in Good Condition

The screenshot is unexpectedly huge

Cause: fullPage: true includes the complete document, and a large device scale factor increases output pixels. Fix: use a viewport capture or clip, lower the scale factor, or resize the resulting image for delivery.

Changing settings reloads the page

Cause: Puppeteer may reload when mobile or touch metrics change. Fix: set all metrics before navigation and wait for the page to settle after any unavoidable change.

Network idle never arrives

Cause: analytics, WebSockets, polling, or advertisements keep connections open. Fix: use domcontentloaded plus explicit selectors and application readiness checks instead of waiting indefinitely for network idle.

Or skip the browser setup

ScreenshotNeo provides a website screenshot API and MCP server. One GET request returns PNG, JPEG, WebP, or PDF, while the service handles browser setup remotely. For a mobile-style capture, pass the viewport and device-related options supported by its API; the parameter names used by other screenshot APIs also work, which can simplify migration. See the ScreenshotNeo documentation for the current option names.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
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}`);

It removes cookie-consent banners, newsletter popups, and chat widgets before the shot. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and whether the request was billed. Its MCP server lets Claude, Cursor, and other MCP clients call take_screenshot, get_page_info, and capture_pdf. The Free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 screenshots. Create a free ScreenshotNeo account.

FAQ

Does Puppeteer emulate a real iPhone?

It emulates documented browser metrics and user-agent behavior. It does not establish complete physical-device fidelity.

Should I use page.emulate() or manual settings?

Use a known descriptor for a standard device profile. Use manual viewport and user-agent settings for custom dimensions or explicit control.

What is the difference between fullPage and clip?

fullPage requests the entire document; clip selects a rectangular region.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.