How to Capture a Website Screenshot with a Custom Viewport in Playwright
Set a custom Playwright viewport before navigation, then capture the viewport, full page, or a selected region with the right pixel scale.
To capture a website at a specific size in Playwright, set the page viewport width and height in CSS pixels before navigation, then call page.screenshot(). For example, a 1280 × 720 viewport produces a screenshot of the visible page area at that viewport size.
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch();
try {
const page = await browser.newPage();
await page.setViewportSize({ width: 1280, height: 720 });
await page.goto('https://example.com');
await page.screenshot({ path: 'screenshot.png' });
} finally {
await browser.close();
}
})();
Install Playwright and its browser if they are not already in your project: npm install playwright, followed by npx playwright install chromium. The examples below use Chromium, but the viewport API is part of Playwright’s Page API. See the official setViewportSize reference and screenshot guide.
1. Set the viewport on a page
Use page.setViewportSize({ width, height }). Both values are pixel counts in CSS pixels. Set the viewport before page.goto() so the site lays out at the intended size from its initial navigation. Responsive sites can behave differently if resized after load, and some sites do not expect a phone-sized page to change size.
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch();
try {
const page = await browser.newPage();
await page.setViewportSize({ width: 390, height: 844 });
await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
await page.screenshot({ path: 'mobile.png' });
} finally {
await browser.close();
}
})();
Choose dimensions that match the CSS layout you want to inspect. A 390 × 844 viewport, for example, is a size you might use to inspect a narrow responsive layout; it does not, by itself, emulate a particular phone’s hardware, device pixel ratio, touch input, or user agent.
2. Set a viewport for a context or test project
Apply it to pages in a browser context
If multiple pages need the same viewport, configure it when creating the context. Context settings also make it straightforward to set viewport and screen dimensions together for emulation.
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch();
try {
const context = await browser.newContext({
viewport: { width: 1280, height: 720 },
});
const page = await context.newPage();
await page.goto('https://example.com');
await page.screenshot({ path: 'desktop.png' });
await context.close();
} finally {
await browser.close();
}
})();
Configure Playwright Test
Set the viewport with test.use() for a test file or in a project’s use configuration when the size should apply throughout that project. This example is runnable in a project with @playwright/test installed:
import { test } from '@playwright/test';
test.use({ viewport: { width: 1280, height: 720 } });
test('capture at a custom viewport', async ({ page }) => {
await page.goto('https://example.com');
await page.screenshot({ path: 'screenshot.png' });
});
In playwright.config.ts, the equivalent project setting is:
import { defineConfig } from '@playwright/test';
export default defineConfig({
use: {
viewport: { width: 1280, height: 720 },
},
});
Use a page setting for a one-off capture, a context setting for related pages, and test configuration for a repeatable test suite.
3. Combine a device preset with a custom viewport
Playwright’s device descriptors provide a set of emulation defaults. Spread the descriptor first and then specify viewport to override its viewport dimensions. Set deviceScaleFactor when the intended device pixel ratio matters too. A custom viewport overrides the descriptor’s viewport; it does not guarantee that every real-device behavior is reproduced.
const { chromium, devices } = require('playwright');
(async () => {
const browser = await chromium.launch();
try {
const device = devices['iPhone 13'];
const context = await browser.newContext({
...device,
viewport: { width: 390, height: 844 },
deviceScaleFactor: 3,
});
const page = await context.newPage();
await page.goto('https://example.com');
await page.screenshot({ path: 'device-sized.png' });
await context.close();
} finally {
await browser.close();
}
})();
Device descriptor names and settings are supplied by the Playwright version installed in your project. Check the official emulation guide and your installed version’s available descriptors.
4. Choose what the screenshot captures
The default screenshot shows the current viewport. Use fullPage: true for the full scrollable document, or clip to capture a rectangular area in page coordinates. These control the capture extent; they do not change the responsive layout viewport.
// Current viewport only
await page.screenshot({ path: 'viewport.png' });
// Entire scrollable page
await page.screenshot({ path: 'full-page.png', fullPage: true });
// A 600 by 300 CSS-pixel rectangle starting at x=100, y=200
await page.screenshot({
path: 'region.png',
clip: { x: 100, y: 200, width: 600, height: 300 },
});
To capture one element, use the locator screenshot method:
await page.locator('main article').screenshot({ path: 'article.png' });
For pages with lazy-loaded images, full-page capture may need extra care: the browser may not load content far below the fold until it is scrolled into view. If the resulting image misses lazy content, scroll through the page or otherwise trigger the content before capture, then take the screenshot. See the official screenshot guide.
5. Pick output format and pixel scale
Playwright’s screenshot API supports PNG, JPEG, and WebP. The file extension determines the format unless you set type. The quality option applies to JPEG and WebP. Choose a scale deliberately: css produces one output pixel per CSS pixel, while device produces device pixels and can make high-density images much larger.
// One output pixel per CSS pixel
await page.screenshot({ path: 'css-pixels.png', scale: 'css' });
// Device-pixel output; useful when high-DPI dimensions are wanted
await page.screenshot({ path: 'device-pixels.png', scale: 'device' });
// Compressed WebP output
await page.screenshot({ path: 'compressed.webp', type: 'webp', quality: 80 });
// JPEG output
await page.screenshot({ path: 'photo.jpg', type: 'jpeg', quality: 85 });
Transparent backgrounds are available for supported formats such as PNG; JPEG does not support transparency. Consult the screenshot API reference for the complete option list supported by your Playwright version, including path, type, quality, fullPage, clip, scale, and background handling.
If you omit path, page.screenshot() returns image bytes in a buffer. That is useful when sending the image to an image-processing library or another storage layer without first writing a local file.
6. Make captures repeatable
A fixed viewport controls layout dimensions, but it does not make screenshots identical across machines. Browser version, operating system, fonts, rendering environment, settings, hardware conditions, and headless mode can all affect pixels. For visual comparisons, keep the environment and Playwright version consistent, in addition to the viewport. The Playwright visual comparisons guide explains the environment consistency requirement.
Also make page readiness explicit. Navigation completion is not always the same as the page being visually ready: client-side rendering, fonts, animations, and asynchronous data may continue after navigation. Wait for a meaningful selector or application state before capture rather than relying on an arbitrary delay:
await page.goto('https://example.com');
await page.locator('main').waitFor({ state: 'visible' });
await page.screenshot({ path: 'ready.png' });
For stable visual checks, use deterministic test data and avoid capturing while animations or time-dependent content are changing. Those are workflow controls; the viewport option alone cannot remove such sources of variation.
7. Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server. One GET request returns a screenshot or PDF. See the API documentation for request options.
curl -G "https://api.screenshotneo.com/v1/shot" \
-d access_key=YOUR_API_KEY \
--data-urlencode url=https://stripe.com \
-d width=1280 \
-d height=720 \
-o shot.webp
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={
"access_key": "YOUR_API_KEY",
"url": "https://stripe.com",
"width": 1280,
"height": 720,
},
timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({
access_key: 'YOUR_API_KEY',
url: 'https://stripe.com',
width: '1280',
height: '720',
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await Bun.write('shot.webp', res);
These examples pass a 1280 × 720 viewport using width and height parameters. ScreenshotNeo also accepts the parameter names used by other screenshot APIs, which can make switching easier. Cookie banners are accepted and removed before capture, along with supported newsletter popups and chat widgets; each cleanup step can be turned off. Bot checks, blank pages, failed loads, timeouts, and cache hits cost nothing, and response headers report the page verdict and billing status. Its MCP server gives AI agents tools to take screenshots, get page information, and capture PDFs.
The free plan includes 1,000 screenshots a month with no card. Paid plans start at $5 for 3,000 shots; all features are available on every plan. Sign up for 1,000 free screenshots a month, with no card.
8. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| The screenshot has the wrong responsive layout | The viewport was set after navigation, or the dimensions differ from the intended CSS viewport. | Set width and height before page.goto(); verify the numbers are CSS pixels. |
setViewportSize is not a function |
The value is not a Playwright Page, or code is using another browser automation API. | Call page.setViewportSize({ width, height }) on a Playwright page. Puppeteer uses different method names. |
| The output dimensions are larger than expected | Device-pixel scaling or a non-default device scale factor is in effect. | Set scale: 'css' for CSS-pixel output, or account for the device scale factor when device pixels are desired. |
| The full-page capture misses images or content | Lazy-loaded content has not been triggered, or asynchronous rendering was still in progress. | Scroll through the page to trigger lazy loading and wait for a content-specific selector before capturing. |
| The capture is clipped or includes the wrong area | clip coordinates or dimensions are incorrect, or the desired output was a full-page capture. |
Check the rectangle’s x, y, width, and height; use fullPage: true when the whole document is needed. |
| Visual snapshots differ between runs or machines | Browser, OS, fonts, rendering environment, animations, or page data changed. | Keep the visual test environment and data consistent, and wait for a stable page state before capturing. |
| Browser launch fails after installation | The browser binary for the selected Playwright version may not be installed. | Run npx playwright install chromium for the project’s Playwright version. |
9. Performance, reliability, and cost
Screenshot capture cost in a self-managed Playwright workflow comes from the compute, browser process, network activity, and storage or image processing you choose; Playwright itself does not make a hosted screenshot-service billing promise. Reuse a browser process for batches of captures and create separate contexts when pages need isolated settings. Avoid unnecessarily large viewports and device-pixel images when a CSS-pixel result is sufficient, since larger captures use more memory and produce larger files.
For reliability, close pages, contexts, and browsers when finished, set navigation and operation timeouts appropriate to the target site, and handle failed navigation or capture errors. A remote website can be slow, unavailable, or protected by bot checks, so retries should be limited and considered carefully; repeated requests can increase load and do not guarantee a successful capture. For hosted captures, review how that provider reports failures and bills them. ScreenshotNeo exposes page verdict and billed status in response headers and states that bot checks, blank pages, failed loads, timeouts, and cache hits are not billed.
10. FAQ
Does changing the viewport resize the browser window?
page.setViewportSize() changes the page viewport and resets the page’s screen size. Configure viewport and screen together in a browser context when you need more control over emulated screen dimensions.
Is a custom viewport the same as a device preset?
No. A viewport sets layout width and height. A device preset supplies additional emulation values; combine the preset with an explicit viewport when you need a custom size.
Can I capture without saving a file?
Yes. Omit path and use the buffer returned by page.screenshot().
Which should I use: fullPage or clip?
Use fullPage: true for the scrollable document and clip for a specific rectangular region. Use a locator’s screenshot method for a particular element.


