ScreenshotNeo

BlogHow-to

How to Screenshot a Responsive Website with a Specific User Agent

Set a viewport and user agent before navigation, then capture the visible view or full page with Playwright, Puppeteer, Chrome DevTools, or ScreenshotNeo.

By the ScreenshotNeo team4 October 202610 min read

To screenshot a responsive website as a particular browser or device, set both the viewport dimensions and user agent before navigating to the page. Then wait for the content you need, trigger lazy-loaded content if necessary, and capture either the visible viewport or the full page. A user-agent change alone does not set the screen size or guarantee a mobile layout.

For repeatable captures, use Playwright or Puppeteer. For a one-off manual check, use Chrome DevTools Device Mode. The examples below show how to configure the browser, save a screenshot, and handle common rendering differences.

1. Choose what you need to reproduce

Before capturing, decide which browser conditions matter. Responsive layouts primarily react to the viewport; sites may also branch on user agent, touch support, device pixel ratio, or other browser properties.

Setting What it controls When to specify it
Viewport width and height The page’s layout viewport in CSS pixels Always specify it for a repeatable responsive screenshot
User-agent string The browser identity sent with requests and exposed to page scripts When the site serves different content or behavior by browser identity
Device scale factor How CSS pixels map to rendered image pixels When image sharpness or a specific device profile matters
Touch and device settings Whether browser APIs report touch or mobile-device behavior When the page responds to these capabilities
Browser engine Rendering and browser behavior, such as Chromium versus WebKit When validating a browser-specific issue

A device preset may bundle several settings, but inspect its values when exact reproduction matters. If you change the viewport, do so before navigation. Puppeteer specifically advises emulating before navigation because some pages do not expect their dimensions to change after loading. [Playwright device emulation] [Puppeteer Page.emulate()]

2. Capture with Playwright

Install Playwright and its Chromium browser, then run this Node.js script. It creates a browser context with the requested viewport and user agent before opening the target URL. The screenshot is saved as a PNG.

npm install playwright
npx playwright install chromium
// screenshot.mjs
import { chromium } from 'playwright';

const url = process.argv[2] ?? 'https://example.com';
const output = process.argv[3] ?? 'responsive.png';
const userAgent = process.env.USER_AGENT ??
  'Mozilla/5.0 (Linux; Android 13; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36';
const width = Number(process.env.VIEWPORT_WIDTH ?? 390);
const height = Number(process.env.VIEWPORT_HEIGHT ?? 844);
const fullPage = process.env.FULL_PAGE === '1';

if (!Number.isInteger(width) || !Number.isInteger(height) || width <= 0 || height <= 0) {
  throw new Error('VIEWPORT_WIDTH and VIEWPORT_HEIGHT must be positive integers');
}

const browser = await chromium.launch({ headless: true });
try {
  const context = await browser.newContext({
    viewport: { width, height },
    userAgent,
    deviceScaleFactor: 1,
    isMobile: true,
    hasTouch: true,
  });
  const page = await context.newPage();
  const response = await page.goto(url, {
    waitUntil: 'domcontentloaded',
    timeout: 45_000,
  });

  if (!response) {
    throw new Error('Navigation did not produce a main document response');
  }
  if (!response.ok()) {
    throw new Error(`Main document returned HTTP ${response.status()}`);
  }

  // Wait for fonts and a brief settling period; replace this with a
  // page-specific selector when the required content is known.
  await page.evaluate(() => document.fonts.ready);
  await page.waitForTimeout(500);
  await page.screenshot({ path: output, fullPage });
  console.log(`Saved ${output} (${width}x${height}, ${fullPage ? 'full page' : 'viewport'})`);
  await context.close();
} finally {
  await browser.close();
}

Run it with custom values like this:

USER_AGENT='Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36' \
VIEWPORT_WIDTH=1365 VIEWPORT_HEIGHT=900 \
node screenshot.mjs https://example.com desktop.png

Use a Playwright device preset

Presets are useful when you want a known device profile, because they can include a user agent and other emulation properties. Spread the preset first, then override values that must be exact. The example retains the preset’s other settings while setting a specific viewport and user agent:

import { chromium, devices } from 'playwright';

const browser = await chromium.launch();
const pixel = devices['Pixel 7'];
const context = await browser.newContext({
  ...pixel,
  viewport: { width: 412, height: 915 },
  userAgent: 'YOUR_REQUIRED_USER_AGENT_STRING',
});
const page = await context.newPage();
await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
await page.screenshot({ path: 'pixel-view.png' });
await browser.close();

Check the available preset names in the installed Playwright version. A preset can supply a platform-specific user agent; override it if your test requires a different exact value. Playwright also documents leaving the preset user agent undefined when the running test platform’s own user agent is desired. [Playwright emulation documentation]

Wait for the right content

domcontentloaded is a useful starting point, but it does not mean every image, client-rendered component, or third-party widget is ready. Prefer a meaningful selector when you know what the capture must contain:

await page.goto(url, { waitUntil: 'domcontentloaded' });
await page.locator('main article h1').waitFor({ state: 'visible', timeout: 15_000 });
await page.screenshot({ path: 'article.png', fullPage: true });

For lazy-loaded images, scroll through the document before taking a full-page shot so the page has a chance to request them:

await page.evaluate(async () => {
  const step = Math.max(250, Math.floor(window.innerHeight * 0.8));
  for (let y = 0; y < document.body.scrollHeight; y += step) {
    window.scrollTo(0, y);
    await new Promise(resolve => setTimeout(resolve, 100));
  }
  window.scrollTo(0, 0);
});
await page.screenshot({ path: 'full.png', fullPage: true });

Use fullPage: false (the default) for the visible viewport. Use fullPage: true for the whole scrollable document. Playwright also supports element screenshots, which are useful when the deliverable is one chart, card, or component rather than the page. [Playwright screenshots]

3. Capture with Puppeteer

Puppeteer’s Page.emulate(device) applies a device’s metrics and user agent; its documentation describes the method as a shortcut for setting the user agent and viewport. For a custom identity and size, set both explicitly before navigating.

npm install puppeteer
// screenshot-puppeteer.mjs
import puppeteer from 'puppeteer';

const browser = await puppeteer.launch({ headless: true });
try {
  const page = await browser.newPage();
  await page.setUserAgent(
    process.env.USER_AGENT ?? 'YOUR_REQUIRED_USER_AGENT_STRING'
  );
  await page.setViewport({
    width: Number(process.env.VIEWPORT_WIDTH ?? 390),
    height: Number(process.env.VIEWPORT_HEIGHT ?? 844),
    deviceScaleFactor: 1,
    isMobile: true,
    hasTouch: true,
  });

  const response = await page.goto(
    process.argv[2] ?? 'https://example.com',
    { waitUntil: 'domcontentloaded', timeout: 45_000 }
  );
  if (!response || !response.ok()) {
    throw new Error(`Navigation failed${response ? `: HTTP ${response.status()}` : ''}`);
  }
  await page.evaluate(() => document.fonts.ready);
  await page.screenshot({
    path: process.argv[3] ?? 'responsive.png',
    fullPage: process.env.FULL_PAGE === '1',
  });
} finally {
  await browser.close();
}

For a known Puppeteer device profile, apply page.emulate(device) before page.goto(). For arbitrary dimensions or a required custom string, use the explicit setters shown above. [Puppeteer Page.emulate()]

4. Capture manually with Chrome DevTools

  1. Open the page in Chrome and open DevTools.
  2. Turn on Device Mode using the device toolbar.
  3. Choose Responsive and enter the desired width and height, or select a device profile.
  4. Set the device pixel ratio, user-agent string, or device type in the Device Mode controls when needed.
  5. Reload the page after changing its emulation settings so the initial request and layout use the chosen conditions.
  6. Use DevTools’ screenshot controls to capture the visible area or a full-size screenshot.

DevTools is convenient for a single visual inspection. Scripted contexts are easier to parameterize and rerun across URLs or test cases, an editorial conclusion based on the manual and automation workflows documented by Chrome, Playwright, and Puppeteer. [Chrome DevTools Device Mode]

5. Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server from ScreenshotNeo. One GET request captures a URL. The example uses the documented API endpoint and parameters; see the API documentation for supported options and response 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}`);
if (!res.ok) throw new Error(`Screenshot request failed: HTTP ${res.status}`);
await Bun.write('shot.webp', new Uint8Array(await res.arrayBuffer()));

ScreenshotNeo removes cookie and consent banners, newsletter popups, and chat widgets before capture; each cleanup step can be turned off. Bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and responses include X-Page-Verdict and X-Billed headers. Its MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other 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 free for 1,000 screenshots a month, with no card required.

6. Options, edge cases, and reliable captures

Viewport and device identity are separate

A mobile-looking user agent with a wide viewport may still render a desktop layout. A narrow viewport with a desktop user agent may trigger responsive CSS but desktop-specific server content. Set both to match the question. For device-specific behavior, also set touch and device scale factor as appropriate.

Viewport screenshot versus full page

  • Viewport: captures what is currently visible and is typically smaller and faster.
  • Full page: captures the entire document, which can produce very tall images and may expose differences from a user scrolling through the page.
  • Element: captures one selected element; use it for component review and reduce irrelevant page content.

Dynamic pages and network idle

Pages with polling, analytics, ads, or persistent connections may never become fully idle. A fixed networkidle wait can therefore be unreliable. Prefer a visible selector or a deliberate short delay after the essential content appears. Use a bounded navigation timeout and report HTTP status so a failed page is not mistaken for a valid screenshot.

Lazy content and sticky elements

Full-page capture does not always trigger every lazy-loaded resource. Scroll through the document first, then return to the top before capture. Sticky headers may appear repeated or cover content depending on how the browser assembles a full-page image; if that matters, use viewport captures at planned scroll positions or temporarily hide the sticky element with page CSS.

Reproducibility

  • Pin the browser automation package and browser version in repeatable jobs.
  • Use a stable user-agent string, viewport, scale factor, locale, and color scheme when they affect the page.
  • Wait for the exact content needed and use a fixed output format and naming scheme.
  • Close the browser and context in a finally block so failed navigations do not leave browser processes running.
  • Remember that emulation reproduces selected browser parameters, not every physical-device behavior. Verify on target hardware when hardware-specific behavior is material. This limitation follows from the documented scope of emulation controls. [Playwright emulation] [Chrome Device Mode]

7. Performance, reliability, and cost

Local browser automation has no per-capture API charge, but it uses compute, memory, browser binaries, and maintenance time. Reuse a browser process for batches while creating an isolated context per configuration or test; avoid launching a new browser for every URL unless isolation requirements call for it. Full-page images take more time and storage than viewport shots, especially on long documents.

For reliable output, apply all emulation before navigation, fail clearly on navigation errors, and wait for a page-specific readiness condition. A timeout is a limit, not proof that a page is ready. If a site blocks automation or shows a bot challenge, the resulting capture reflects that response rather than the intended page. Do not interpret an emulated screenshot as evidence of identical rendering on a physical device.

For hosted API captures, include API usage and plan limits in cost planning. ScreenshotNeo’s published plans 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 on every plan. Its billed-response headers can help distinguish clean captures from unbilled failure or cache outcomes.

8. Troubleshooting

Symptom Likely cause Fix
Desktop layout appears with a mobile user agent The viewport is still wide, or mobile/device properties were not set Set the viewport explicitly before navigation; set touch and mobile emulation if relevant.
Page shows desktop content despite a narrow viewport The server chooses markup from the user agent or initial request Set the required user agent before navigation and reload after changing emulation.
Screenshot is blank or missing an app section Client rendering has not completed, navigation failed, or the app needs authentication Check the response status, wait for a meaningful selector, and provide authorized cookies or headers if needed.
Images are missing in a full-page shot Lazy loading has not been triggered or image requests are still pending Scroll the page before capture; wait for the relevant images or their load state.
Capture times out on a page that looks loaded Persistent requests prevent the chosen idle condition Wait for a page-specific selector or use a bounded delay rather than waiting for all network activity to stop.
Text wraps differently from the target device Viewport width, device scale factor, fonts, or browser engine differ Match the CSS-pixel viewport and scale factor, wait for fonts, and use the relevant engine when required.
Full-page image is unexpectedly huge The document is very tall or contains expanding content Capture the viewport or a specific element, or capture a defined set of scroll positions.
Site displays a bot check or CAPTCHA The site has challenged the automated browser Do not treat the challenge page as the target content; use an authorized browsing path or a screenshot service suited to the workflow.

9. Frequently asked questions

Does changing the user agent make a page responsive?

No. Responsive CSS generally responds to viewport dimensions. A user agent can affect server responses and scripts, so set both when you need a particular device-like result.

Can I use any user-agent string?

You can provide a custom string in browser automation, but a string only changes the reported identity. It does not install that browser, add its rendering engine, or reproduce every device capability.

Should I use a device preset or custom settings?

Use a preset for a convenient bundle of device properties. Use explicit values when the test specifies exact dimensions or a user-agent string, and override preset fields that differ.

Is a screenshot from emulation the same as a real phone screenshot?

It can reproduce selected viewport and browser settings, but it does not prove that hardware-dependent behavior matches a physical phone. Test on the actual device when that distinction matters.