How to Take a Full-Page Website Screenshot at a Custom Mobile Viewport Size
Set a custom mobile viewport, then capture the whole page with Chrome DevTools, Playwright, or Puppeteer. Learn which settings affect mobile rendering and how to troubleshoot captures.
To capture a full webpage at a custom mobile size, set the browser viewport to the width and height you want, then use a full-page screenshot command. In Chrome DevTools, select Responsive, enter the dimensions, and choose More options → Capture a full size screenshot. For repeatable captures, use Playwright or Puppeteer. A full-page capture includes content below the visible viewport; it does not mean the page was rendered on a physical phone.
This guide covers the manual Chrome workflow, complete Playwright and Puppeteer examples, device settings that can affect the result, troubleshooting, and an API option. Chrome DevTools documents the responsive dimensions and full-size capture workflow in its Device Mode guide.
1. Set the custom mobile viewport in Chrome DevTools
- Open the page in Chrome, then open DevTools.
- Turn on the Device toolbar using the device icon or Ctrl+Shift+M on Windows/Linux, or Cmd+Shift+M on macOS.
- In the device dimensions control, choose Responsive.
- Enter the target width and height in CSS pixels. For example, enter
390by844for a 390 × 844 CSS-pixel viewport. - Choose the device type if the page depends on mobile rendering or touch. Set the device pixel ratio (DPR) if the output needs a particular pixel density.
- Open the DevTools menu, choose More options → Capture a full size screenshot. The regular screenshot command captures only the visible viewport.
Use the dimensions control for the viewport, not the dimensions of the screenshot file. The page lays out at the viewport’s CSS-pixel width; DPR can make the resulting image’s pixel dimensions larger. Chrome’s Device Mode is an approximation of a mobile environment, not a physical-device browser run. If hardware-specific behavior matters, check the page on an actual device too.
Viewport dimensions are only part of the setup
Width and height control the responsive layout, but a page can also react to device type, touch support, DPR, user agent, and its viewport meta tag. Match those settings when you are trying to reproduce a particular device or bug. Chrome documents custom device fields such as DPR, user agent, and device type in the Device Mode guide.
- Viewport width and height: The CSS-pixel dimensions available to the page. These usually determine responsive breakpoints and the visible area.
- Device type: Controls whether Chrome emulates mobile or desktop behavior and which interaction events it uses.
- DPR: Affects the relationship between CSS pixels and output pixels. It matters when comparing image dimensions or checking high-density assets.
- Touch and user agent: Can affect scripts and page behavior even when the viewport size is unchanged.
- Viewport meta tag: A page that lacks an appropriate mobile viewport declaration may not lay out at the expected CSS width on mobile.
2. Capture repeatably with Playwright
Playwright lets you set the viewport for a browser context and capture the full scrollable page with fullPage: true. Its screenshot API also supports a scale setting: css produces CSS-pixel sizing and device uses device-pixel-ratio output. See the official emulation and screenshot documentation.
Install Playwright and its Chromium browser:
npm init -y
npm install playwright
npx playwright install chromium
Save this as full-page.mjs, then run node full-page.mjs https://example.com 390 844:
import { chromium } from 'playwright';
const [url, widthArg = '390', heightArg = '844'] = process.argv.slice(2);
if (!url) throw new Error('Usage: node full-page.mjs <url> [width] [height]');
const width = Number(widthArg);
const height = Number(heightArg);
if (!Number.isInteger(width) || width < 1 || !Number.isInteger(height) || height < 1) {
throw new Error('Width and height must be positive integers in CSS pixels.');
}
const browser = await chromium.launch({ headless: true });
try {
const context = await browser.newContext({
viewport: { width, height },
isMobile: true,
hasTouch: true,
deviceScaleFactor: 1,
});
const page = await context.newPage();
const response = await page.goto(url, { waitUntil: 'networkidle', timeout: 60000 });
if (!response) console.warn('Navigation returned no main-resource response.');
else if (!response.ok()) console.warn(`Main resource returned HTTP ${response.status()}.`);
await page.screenshot({ path: 'full-page.png', fullPage: true, scale: 'css' });
console.log(`Saved full-page.png at viewport ${width} × ${height} CSS pixels.`);
await context.close();
} finally {
await browser.close();
}
For a non-mobile browser context at a narrow width, remove isMobile and hasTouch; this preserves a desktop-style context with a small viewport. To model higher pixel density, set deviceScaleFactor to a value such as 2 and use scale: 'device' if you want device-pixel output. The screenshot option’s scale affects image output; the viewport remains expressed in CSS pixels.
Waiting for content before capture
networkidle is useful on pages that settle after loading, but it can time out on sites with long-lived network activity. For pages with a known content element, navigate with domcontentloaded and wait for that element instead:
await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 60000 });
await page.locator('main').waitFor({ state: 'visible', timeout: 15000 });
await page.screenshot({ path: 'full-page.png', fullPage: true });
Choose a selector that represents the content you actually need. A visible main element does not guarantee every image, font, animation, or client-rendered section is finished. If images load only as the user scrolls, trigger the page’s loading behavior before capture and confirm the resulting page content.
3. Capture with Puppeteer
Puppeteer’s screenshot API provides fullPage: true for the full page and documents captureBeyondViewport for captures beyond the viewport. See the official ScreenshotOptions API.
Install Puppeteer, which downloads a compatible browser by default:
npm init -y
npm install puppeteer
Save this as full-page.cjs and run node full-page.cjs https://example.com 390 844:
const puppeteer = require('puppeteer');
(async () => {
const [url, widthArg = '390', heightArg = '844'] = process.argv.slice(2);
if (!url) throw new Error('Usage: node full-page.cjs <url> [width] [height]');
const width = Number(widthArg);
const height = Number(heightArg);
if (!Number.isInteger(width) || width < 1 || !Number.isInteger(height) || height < 1) {
throw new Error('Width and height must be positive integers in CSS pixels.');
}
const browser = await puppeteer.launch({ headless: true });
try {
const page = await browser.newPage();
await page.setViewport({ width, height, deviceScaleFactor: 1, isMobile: true, hasTouch: true });
const response = await page.goto(url, { waitUntil: 'networkidle2', timeout: 60000 });
if (!response) console.warn('Navigation returned no main-resource response.');
else if (!response.ok()) console.warn(`Main resource returned HTTP ${response.status()}.`);
await page.screenshot({ path: 'full-page.png', fullPage: true });
console.log(`Saved full-page.png at viewport ${width} × ${height} CSS pixels.`);
} finally {
await browser.close();
}
})().catch((error) => {
console.error(error);
process.exitCode = 1;
});
networkidle2 waits until network activity is sufficiently quiet for Puppeteer to consider the page idle. On pages with persistent requests, use a less restrictive navigation condition and wait for a meaningful selector, as in the Playwright example. Puppeteer and Playwright option names and defaults can differ; check the API documentation for the version installed in your project.
4. Understand full-page output and mobile fidelity
A full-page screenshot extends the capture beyond the visible viewport. It does not create a single exceptionally tall mobile screen or guarantee that every offscreen element has already loaded. Lazy-loaded images, infinite-scroll lists, sticky elements, animations, and content that appears after interaction can change what the capture contains.
| Need | Setting or action | What it affects |
|---|---|---|
| Responsive layout at a custom size | Set viewport width and height | The CSS-pixel space used for layout |
| Mobile-specific page behavior | Enable mobile/device emulation and touch as needed | Mobile mode and interaction behavior |
| High-density output | Set DPR or device scale factor; choose output scale where available | Image pixel dimensions and density |
| Content below the fold | Use DevTools full-size capture or automation full-page capture | Whether the screenshot extends beyond the visible area |
| Lazy content | Scroll or otherwise trigger its loading before capture | Whether offscreen content exists when the screenshot is taken |
Long pages may produce very large images and take longer to capture. For visual regression, keep the browser version, viewport, device settings, wait condition, and page state consistent between runs. A browser-emulated capture is useful for responsive checks, but hardware behavior, mobile browser chrome, and real-device rendering can differ.
5. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Screenshot shows only the first screen | The visible-viewport command or default screenshot was used. | In DevTools choose Capture a full size screenshot; in Playwright or Puppeteer set fullPage: true. |
| Layout looks like desktop squeezed into a phone | The page may lack a mobile viewport declaration, or only the width was changed while mobile emulation was not enabled. | Check the page’s viewport meta tag and choose mobile device mode when the goal requires mobile behavior. Compare with a real device if the distinction matters. |
| Image dimensions do not equal the viewport dimensions | The output may use device-pixel scaling, or the full-page image includes the entire document height. | Compare CSS-pixel viewport dimensions separately from output pixel dimensions. In Playwright, use scale: 'css' when CSS-pixel output is wanted. |
| Bottom sections or images are missing | Lazy loading, scrolling-triggered content, or client-side rendering had not completed. | Scroll to trigger loading, wait for relevant selectors or images, and capture after the content is present. Full-page mode alone cannot make the page load content it has not requested. |
| Navigation wait times out | Persistent analytics, streaming, or other network activity prevents an idle condition. | Use domcontentloaded or another suitable navigation condition, then wait for a specific content selector with a bounded timeout. |
| Mobile menu or hover state is wrong | The capture was taken before the desired interaction, or touch and pointer behavior differ. | Use automation to perform the required click or touch action before capture; set touch emulation when needed. |
| Capture is blank or shows an error page | The site may have blocked automation, failed to load, redirected, or returned an error response. | Inspect the navigation response and browser console, verify the URL is reachable in that environment, and handle authentication or access requirements explicitly. |
| Very tall capture is slow or memory-heavy | The document has a large scroll height, many images, or complex rendering. | Capture only when needed, wait for required content rather than arbitrary long delays, and consider whether a viewport capture or selected element is enough for the task. |
6. Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server. Its API accepts a URL and returns a screenshot or PDF; the API documentation describes the available parameters. This one-call example uses the API’s default viewport behavior. Set the documented viewport parameters if you need a particular mobile width and height.
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,
)
r.raise_for_status()
with open("shot.webp", "wb") as f:
f.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: ${res.status}`);
await require('node:fs/promises').writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));
ScreenshotNeo removes cookie and consent banners, newsletter popups, and chat widgets before the capture; each cleanup step can be turned off. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and response headers report the page verdict and billing status. Its MCP server provides screenshot tools for AI agents. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000. Sign up for 1,000 free screenshots a month, with no card.
7. Cost, reliability, and workflow choices
Chrome DevTools is convenient for a one-off manual capture. Playwright or Puppeteer is better when captures need to be repeated in scripts, tests, or build workflows; this is a practical distinction based on the manual and automation interfaces described above. Automation makes the steps repeatable, but the page can still vary because of changing content, network conditions, animations, or access restrictions.
Browser automation has compute and maintenance costs: it launches a browser, loads the page, and must be kept compatible with the browser and framework versions in use. A hosted screenshot API avoids managing that capture browser yourself, but has its own service plan and request limits; consult the provider’s current documentation and plan details before choosing it. For ScreenshotNeo, the stated plans are Free (1,000 shots/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.
For reliable repeat captures, record the viewport, device type, DPR, browser/framework version, wait condition, and any interactions performed before capture. Use bounded navigation and selector timeouts, inspect HTTP responses, and save diagnostics when a capture fails. Avoid treating a screenshot as proof that the page works on every physical device.
8. FAQ
Does full-page mean the image has the same height as the mobile viewport?
No. The viewport controls the page’s layout width and visible area; a full-page screenshot extends down the document and can be much taller.
Should I use a real phone or an emulator?
Use browser emulation for repeatable responsive checks. Use a physical device when hardware, browser chrome, or real mobile behavior is part of what you need to verify.
Can I capture a page that requires login?
Yes, if your browser session or automation context is authenticated and allowed to access it. Establish the session before capture, and avoid placing credentials in source code or logs.
Why does my full-page screenshot omit content that appears when I scroll?
Some pages request content only after scrolling or interaction. Trigger that behavior and wait for the content to load before taking the full-page capture.


