ScreenshotNeo

BlogHow-to

How to Capture a Responsive Website at Desktop and Mobile Breakpoints with Puppeteer

Capture the same responsive site at desktop and mobile sizes with Puppeteer. Learn when to use a viewport, device emulation, full-page capture, and custom readiness checks.

By the ScreenshotNeo team4 October 20268 min read

To capture a responsive website at desktop and mobile breakpoints with Puppeteer, configure the viewport or emulate a device before navigating, load the page, then save a screenshot. Use an explicit CSS viewport when you need to check a width and height; use device emulation when the capture should also use a device’s metrics and user agent. Set fullPage: true if you need the whole page rather than the visible viewport.

The example below saves one desktop screenshot and one mobile-device screenshot as separate files. The chosen desktop dimensions and device are examples; select sizes that match the site’s CSS and the screens you need to review. Puppeteer emulation is a browser simulation, not a physical-device test.

1. Install Puppeteer

Start a Node.js project and install Puppeteer:

npm init -y
npm install puppeteer

Use an ESM JavaScript file, such as capture.mjs. Puppeteer’s installation includes a compatible browser by default. If your environment manages the browser separately, make sure the browser executable and Puppeteer version are compatible.

2. Capture desktop and mobile configurations

This runnable example sets the desktop viewport, navigates, and saves a viewport screenshot. It then opens a second page, applies a named mobile device descriptor before navigation, and saves that screenshot. Keeping the pages separate makes each configuration explicit and avoids changing device metrics on an already-loaded page.

import puppeteer, {KnownDevices} from 'puppeteer';

const url = 'https://example.com';
const browser = await puppeteer.launch();

try {
  const desktop = await browser.newPage();
  await desktop.setViewport({width: 1440, height: 900});
  await desktop.goto(url, {waitUntil: 'networkidle2'});
  await desktop.screenshot({path: 'example-desktop.png'});

  const mobile = await browser.newPage();
  await mobile.emulate(KnownDevices['iPhone 17 Pro']);
  await mobile.goto(url, {waitUntil: 'networkidle2'});
  await mobile.screenshot({path: 'example-mobile.png'});
} finally {
  await browser.close();
}

Run it with node capture.mjs. Replace the URL, output paths, viewport dimensions, or device descriptor as needed. KnownDevices descriptors provide device metrics and a user agent; setViewport() sets page dimensions without applying a named device’s user agent. Consult Puppeteer’s Page.emulate() and Page.setViewport() documentation for the current API details.

3. Choose viewport dimensions or device emulation

Approach Use it when What it configures
page.setViewport({width, height}) You want to inspect a particular responsive width and height, such as a breakpoint used by your project. Page viewport dimensions.
page.emulate(KnownDevices[name]) You want a named device configuration. Device metrics and user agent, along with resizing the page.

A width is not a universal breakpoint. CSS breakpoints vary by site, so choose dimensions from the site’s styles, design requirements, or audience. To capture several widths, repeat the viewport setup and navigation for each configuration and give each output a distinct filename.

Puppeteer advises applying device emulation before navigation because sites may not expect phone metrics to change after they have loaded. Viewport changes can also reload a page in some cases, especially when changing mobile or touch settings. Set the conditions you intend to capture before visiting the URL.

4. Choose the screenshot extent and format

By default, page.screenshot() captures the current viewport. For a full-page image, pass fullPage: true:

await page.screenshot({
  path: 'example-full-page.png',
  fullPage: true
});

Use clip when you need a specific rectangle instead of the viewport or full page. The screenshot options also include an output path, image type, quality for supported lossy formats (not PNG), and omitBackground for capturing without the default background. See the official Puppeteer Screenshots guide and ScreenshotOptions reference for supported options and exact types.

For a single element, locate it and call its element handle’s screenshot() method. Puppeteer attempts to scroll a hidden element into view by default:

const element = await page.$('.product-card');
if (!element) {
  throw new Error('Could not find .product-card');
}
await element.screenshot({path: 'product-card.png'});

5. Make page readiness explicit

The example uses waitUntil: 'networkidle2', as in Puppeteer’s screenshot guide. Treat that as a navigation wait choice, not proof that every page is ready to capture. A page can keep network connections open, render content after navigation, load images lazily, or animate elements. Use a readiness condition that matches the site and the content you need to see.

For example, wait for a site-specific selector before capturing:

await page.goto(url, {waitUntil: 'domcontentloaded'});
await page.waitForSelector('[data-page-ready="true"]');
await page.screenshot({path: 'example-ready.png'});

The selector in this example must exist on the page being captured; it is a placeholder, not a Puppeteer convention. If there is no reliable page marker, use a deliberate delay only when appropriate for that site, and account for the extra time. For full-page screenshots, check that the content you expect is actually present; selecting fullPage does not guarantee that lazy content or third-party widgets have finished rendering.

6. Capture a set of responsive sizes

For a breakpoint review, keep the viewport list and filenames together so the resulting files are easy to identify. The following helper captures several CSS viewport sizes. Each size is configured before its navigation, and every output gets a distinct name.

import puppeteer from 'puppeteer';

const url = 'https://example.com';
const viewports = [
  {name: 'mobile', width: 390, height: 844},
  {name: 'tablet', width: 768, height: 1024},
  {name: 'desktop', width: 1440, height: 900}
];

const browser = await puppeteer.launch();
try {
  for (const viewport of viewports) {
    const page = await browser.newPage();
    await page.setViewport({width: viewport.width, height: viewport.height});
    await page.goto(url, {waitUntil: 'networkidle2'});
    await page.screenshot({path: `example-${viewport.name}.png`});
    await page.close();
  }
} finally {
  await browser.close();
}

These dimensions are illustrative, not prescribed breakpoints. Add or change entries to test the widths your site supports. If the purpose is named-device emulation rather than CSS layout coverage, create a separate page for each device and call emulate() before navigating.

Or skip the browser setup

ScreenshotNeo provides a screenshot API and MCP server for developers. A single GET request captures a URL as an image or PDF. See the ScreenshotNeo API documentation for options and setup, and visit ScreenshotNeo for an overview.

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}`);
const fs = await import('node:fs/promises');
await fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));
  • Cookie and consent banners are accepted like a visitor, and more than 60 known consent platforms, newsletter popups, and chat widgets are removed before the shot. Each step can be turned off.
  • Bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing. Responses include X-Page-Verdict and X-Billed headers to say what happened.
  • An MCP server provides take_screenshot, get_page_info, and capture_pdf tools for AI agents and MCP clients.
  • The free plan includes 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 screenshots.

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

Performance, reliability, and cost considerations

Each browser launch has setup cost, so a batch can reuse one browser and open or close pages for each viewport, as the batch example does. The time for a capture also depends on navigation and readiness conditions; waiting for a page-specific marker can avoid capturing too early, while overly broad waits can hold up a batch. Choose a wait condition based on the site’s behavior.

For reliable files, use distinct paths, close each page after a batch capture, and close the browser in a finally block so it is also cleaned up if navigation or capture throws. If a page is long or content changes while capturing, inspect the resulting image and use a site-specific readiness condition. Puppeteer screenshots run in an emulated browser environment and do not establish how a physical phone renders the site.

Self-hosted Puppeteer has no per-screenshot API charge described here, but you are responsible for running Node.js and the browser and for the compute and time they use. ScreenshotNeo’s published tiers are Free: 1,000 per month; 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 available on every plan. Compare your capture volume and operational needs against those listed plan limits.

Troubleshooting

Symptom Likely cause Fix
Mobile screenshot looks like desktop The viewport or emulation was applied after navigation, or only a CSS width was set when device metrics and user agent were needed. Apply setViewport() or emulate() before goto(). Use a device descriptor when the capture requires its metrics and user agent.
Screenshot is blank or missing content The capture ran before the page-specific content rendered, or navigation did not reach the expected state. Check the navigation result and wait for a selector that indicates the needed content is ready before capturing.
Navigation hangs on network idle The page may keep network activity open or use long-running requests. Choose a navigation condition that fits the page, then wait for a specific element or another site-specific readiness signal.
Full-page image omits expected content Lazy-loaded images or dynamic content may not have been rendered when capture began. Wait for the page’s content to appear and inspect the output. Add a site-specific readiness step when needed.
Output file is overwritten Multiple viewport captures use the same path. Include the viewport or device name in every output filename.
Element screenshot fails or captures the wrong area The selector matches no element, matches a different element, or the target is not in the expected state. Verify the selector, wait for it to appear, and use the element handle’s screenshot method for a single element.
Viewport change causes unexpected reload Some viewport changes, particularly mobile or touch changes, can reload the page. Set the intended viewport or device before navigation and capture each configuration with its own navigation.

FAQ

Does setting a mobile viewport test a real phone?

No. Puppeteer emulates browser device conditions. It does not replace checking behavior on physical hardware.

Does fullPage: true work with mobile emulation?

It requests a full-page screenshot for the page’s current emulated configuration. Content readiness still depends on the site, so confirm lazy or dynamic content is present.

Should I use viewport dimensions or a device name?

Use dimensions to check a chosen responsive size. Use a device descriptor when its device metrics and user agent are part of what you need to capture.

Where are the official screenshot options documented?

See Puppeteer’s Screenshots guide, ScreenshotOptions reference, Page.emulate() reference, and Page.setViewport() reference.