const viewports = [
{ width: 1920, height: 1080 }, // desktop full HD
{ width: 1366, height: 768 }, // most common laptop
{ width: 375, height: 812 }, // iPhone X/11/12
];
That's probably 80% of what you need. But if you're automating screenshots at scale — for visual regression, PDF generation, social previews — "probably" isn't good enough, and the edge cases will find you.
The viewport problem nobody warns you about
I wasted an entire afternoon once trying to figure out why a client's dashboard screenshots looked fine on my machine but came out wrong in CI. The page had a sidebar that collapsed at 1024px. My local browser was 1440px wide, the headless Chrome in CI defaulted to 800x600.
800x600. In 2026.
That's the default viewport for headless Chromium if you don't set one. Puppeteer uses it too unless you override it in launch() or page.setViewport(). Playwright defaults to 1280x720, which is slightly better but still catches people off guard.
// Puppeteer — you HAVE to set this
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.setViewport({ width: 1366, height: 768 });
# Playwright (Python) — set in browser context
context = browser.new_context(viewport={"width": 1920, "height": 1080})
Which sizes actually matter
Here's the thing — the "right" viewport depends on what you're screenshotting and why. There's no universal list.
For visual regression testing, match your actual user base. Pull your analytics. If 40% of your traffic is 1366x768 laptop users and 35% is mobile, test those two. Maybe throw in 1920x1080 for good measure. Three viewports covers most projects.
For social preview images (og:image, Twitter cards), you want exactly 1200x630. Not approximately. Exactly. Some tools crop from center, some from top-left, and if your viewport doesn't match the output ratio you'll get weird results.
For PDF generation from HTML, it's different again. You're not matching a real screen — you're matching a paper size. A4 at 96dpi is roughly 794x1123 pixels. Letter is 816x1056.
// PDF-oriented viewport
await page.setViewport({ width: 794, height: 1123 });
await page.pdf({ format: 'A4', printBackground: true });
Device emulation vs. viewport size
Setting the viewport width to 375px doesn't make your page behave like an iPhone.
Two things are missing: device pixel ratio and the user agent string. A real iPhone has a DPR of 3, so a 375px wide screen actually renders at 1125 physical pixels. If your page serves different images based on srcset or uses window.devicePixelRatio in JS, you'll get different results.
await page.setViewport({
width: 375,
height: 812,
deviceScaleFactor: 3
});
await page.setUserAgent(
'Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X)...'
);
Playwright has a nicer API for this — device descriptors built in:
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
iphone = p.devices["iPhone 13"]
browser = p.chromium.launch()
context = browser.new_context(**iphone)
That handles viewport, DPR, user agent, and touch events all at once. I wish Puppeteer had something this clean out of the box.
The full-page vs. visible-area trap
This trips people up constantly. When you set a viewport to 1366x768 and take a full-page screenshot, the width stays at 1366 but the height extends to cover the entire scrollable content. Your viewport height setting gets effectively ignored.
That's usually fine. Unless your page has sticky elements, lazy-loaded images, or scroll-triggered animations.
Sticky headers will repeat in every "fold" of a full-page screenshot in some tools. Lazy images below the fold won't load because the browser never actually scrolled. And scroll-triggered CSS animations will stay in their initial state.
Fixes:
// Force-scroll to trigger lazy loading
await page.evaluate(async () => {
await new Promise(resolve => {
let totalHeight = 0;
const distance = 400;
const timer = setInterval(() => {
window.scrollBy(0, distance);
totalHeight += distance;
if (totalHeight >= document.body.scrollHeight) {
clearInterval(timer);
resolve();
}
}, 100);
});
});
// Then scroll back to top
await page.evaluate(() => window.scrollTo(0, 0));
// Now take the screenshot
await page.screenshot({ fullPage: true });
Not elegant. Works though.
Responsive breakpoints worth testing
Skip the generic "test every device" advice. In practice, the breakpoints that catch bugs are the ones where your CSS layout shifts. Look at your own stylesheets:
/* These are YOUR breakpoints — test at these widths */
@media (max-width: 1024px) { /* tablet layout kicks in */ }
@media (max-width: 768px) { /* mobile nav appears */ }
@media (max-width: 480px) { /* single column */ }
Test at the breakpoint and one pixel above it. 1024 and 1025. 768 and 769. That's where the visual glitches hide — elements half-collapsed, overlapping text, buttons that are technically visible but pushed off to the side.
I usually test at these widths for a typical marketing site:
- 1920 — full desktop
- 1366 — laptop (this is the one people forget)
- 1024 — tablet landscape / small desktop
- 768 — tablet portrait
- 375 — mobile
Five viewports. Covers the real-world spread without going overboard.
Retina and HiDPI output
If you're generating screenshots for documentation, marketing pages, or app stores, you probably want 2x output even from desktop viewports. Set deviceScaleFactor: 2 and your 1366x768 viewport produces a 2732x1536 image.
await page.setViewport({
width: 1366,
height: 768,
deviceScaleFactor: 2
});
File sizes roughly quadruple. Keep that in mind if you're storing thousands of screenshots.
Quick reference
| Use case | Width | Height | DPR |
|---|---|---|---|
| Desktop screenshot | 1920 | 1080 | 1 |
| Laptop | 1366 | 768 | 1 |
| Tablet | 768 | 1024 | 2 |
| Mobile (iPhone-ish) | 375 | 812 | 3 |
| Social preview (og:image) | 1200 | 630 | 1 |
| PDF (A4) | 794 | 1123 | 1 |
| Retina desktop | 1440 | 900 | 2 |
Don't treat this table as gospel — check your own analytics and test the breakpoints that match your CSS. That's what actually catches bugs.
Top comments (0)