How to Capture High-Resolution Screenshots with Playwright
Use Playwright's device scale, viewport, full-page, clip, and locator options to capture reproducible high-resolution screenshots.
Use scale: "device" and set the browser context’s deviceScaleFactor explicitly. Playwright’s device scale captures one image pixel per device pixel, so a high-DPI context produces more pixels than the same CSS viewport at scale 1. Choose the capture area separately with a viewport screenshot, fullPage, clip, or a locator screenshot.
The following recipe creates a reproducible 2× viewport capture:
import { chromium } from 'playwright';
const browser = await chromium.launch();
const context = await browser.newContext({
viewport: { width: 1280, height: 800 },
deviceScaleFactor: 2,
});
const page = await context.newPage();
await page.goto('https://example.com', { waitUntil: 'networkidle' });
await page.screenshot({
path: 'capture.png',
scale: 'device',
});
await browser.close();
deviceScaleFactor: 2 is an example setting. It does not change the CSS layout width; it changes the device-pixel environment. The resulting file dimensions depend on the rendered CSS dimensions, scale factor, and the selected capture area. Playwright documents these controls in its Page API, screenshots guide, and emulation guide.
1. How Playwright resolution works
A screenshot has two independent decisions:
| Decision | What it controls | Typical options |
|---|---|---|
| Pixel scale | How many output pixels represent each CSS pixel | scale: 'device' or scale: 'css' |
| Capture scope | Which part of the rendered page is included | Viewport, fullPage, clip, or locator |
With scale: 'device', Playwright preserves device pixels. On a high-DPI device this can make the image twice as wide and twice as tall as the CSS viewport, producing roughly four times as many pixels. With scale: 'css', one output pixel represents one CSS pixel and files are smaller. Specify the value instead of relying on defaults when screenshots must be reproducible across scripts.
Playwright’s Page screenshot reference reports 'device' as the default for page screenshots, while screenshot assertions document a different default. Explicit configuration avoids confusion when comparing visual captures.
2. Set viewport and device scale factor
The viewport is measured in CSS pixels. The device scale factor controls the emulated display density. Keep both values in your capture configuration so another developer can reproduce the same image.
import { chromium } from 'playwright';
const browser = await chromium.launch();
const context = await browser.newContext({
viewport: { width: 1440, height: 900 },
deviceScaleFactor: 2,
colorScheme: 'light',
});
const page = await context.newPage();
await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
await page.screenshot({
path: 'viewport@2x.png',
scale: 'device',
animations: 'disabled',
});
await browser.close();
Use a larger viewport when you need a wider layout, and use the scale factor when you need more pixels for inspection or image processing. Increasing the scale factor alone does not reveal responsive content that is hidden at a smaller CSS width.
3. Choose the screenshot area
Viewport screenshot
Without an area option, Playwright captures the visible viewport.
await page.screenshot({
path: 'viewport.png',
scale: 'device',
});
Full scrollable page
Set fullPage: true to capture the full scrollable document. This is independent of resolution: combine it with scale: 'device' when the entire page needs device-pixel detail.
await page.screenshot({
path: 'full-page.png',
fullPage: true,
scale: 'device',
});
Long pages can create large images and consume more memory. If a page loads content while scrolling, wait for the relevant content or use a deterministic loading strategy before capture.
Clipped rectangle
Use clip for a bounded rectangle in CSS-pixel coordinates:
await page.screenshot({
path: 'region.png',
clip: { x: 120, y: 80, width: 900, height: 500 },
scale: 'device',
});
The clip rectangle must be inside the page’s layout area. Keep coordinates and dimensions in CSS pixels; the device scale determines the output pixel count.
Element or component screenshot
Capture one component with a locator. This avoids calculating coordinates and is usually more stable when the page layout changes.
const card = page.locator('[data-testid="pricing-card"]').first();
await card.screenshot({
path: 'pricing-card.png',
scale: 'device',
});
Make sure the locator resolves to one visible element. If the element is below the fold, Playwright can scroll it into view before the screenshot; still wait for its content and fonts when those affect the pixels.
4. Wait for a stable, complete render
Resolution cannot repair an incomplete render. Navigate with an appropriate wait condition, then wait for page-specific selectors, fonts, images, or application state.
await page.goto('https://example.com/dashboard', {
waitUntil: 'networkidle',
});
await page.locator('[data-ready="true"]').waitFor();
await page.evaluate(() => document.fonts.ready);
await page.screenshot({
path: 'dashboard.png',
scale: 'device',
});
networkidle is not a guarantee that every visual element is ready on applications with persistent connections or delayed rendering. A selector that represents the finished state is often more reliable. Disable or wait for animations when comparing images:
await page.screenshot({
path: 'stable.png',
scale: 'device',
animations: 'disabled',
});
For lazy-loaded content, use full-page capture or explicitly scroll the page before taking a viewport or component shot. Avoid arbitrary delays unless the page has no observable ready signal.
5. Save files or process screenshot bytes
Pass path when you need an image artifact. Omit it to receive a buffer for an upload, computer-vision pipeline, hash, or custom encoder.
const bytes = await page.screenshot({
type: 'png',
scale: 'device',
});
console.log(`Captured ${bytes.length} bytes`);
// upload bytes or pass them to an image-processing library
PNG is lossless and useful for visual comparison or text. JPEG is smaller but lossy; WebP can reduce size when your downstream system supports it. Choose the format explicitly when file size or fidelity matters.
6. Complete runnable script
import { chromium } from 'playwright';
const url = process.argv[2] || 'https://example.com';
const browser = await chromium.launch();
const context = await browser.newContext({
viewport: { width: 1280, height: 800 },
deviceScaleFactor: 2,
});
const page = await context.newPage();
try {
await page.goto(url, { waitUntil: 'networkidle', timeout: 60_000 });
await page.evaluate(() => document.fonts.ready);
await page.screenshot({
path: 'high-resolution.png',
fullPage: true,
scale: 'device',
animations: 'disabled',
});
} finally {
await browser.close();
}
Run it after installing Playwright and its browser binaries:
npm install playwright
npx playwright install chromium
node capture.mjs https://example.com
7. Or skip the browser setup
ScreenshotNeo provides a website screenshot API when you want one HTTP request instead of maintaining a Playwright browser. Its API accepts the URL and capture options, and the documentation lists the available parameters.
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}`);
ScreenshotNeo removes cookie and consent banners, newsletter popups, and chat widgets before capture. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and response headers identify the page verdict and billing status. It also provides an MCP server with take_screenshot, get_page_info, and capture_pdf tools for AI agents, plus full-page, element, device, custom CSS and JavaScript, waiting, blocking, authentication, caching, signed-link, asynchronous, and bulk-capture options. The Free plan includes 1,000 screenshots per month without a card; paid plans start at $5 for 3,000 shots.
Create a free ScreenshotNeo account to get started.
8. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| The image is sharp but the layout is wrong | The CSS viewport is not the intended width | Set viewport explicitly; scale changes density, not responsive layout. |
| The output is unexpectedly small | scale: 'css' or a low device scale factor |
Use scale: 'device' and set deviceScaleFactor. |
| Text or icons look different between runs | Fonts or web fonts were not ready | Wait for document.fonts.ready and use the same browser, viewport, and scale. |
| Images are missing | Lazy loading has not been triggered | Use fullPage, scroll through the page, or wait for image-specific selectors. |
| Full-page capture times out | The document is very long or continuously loading | Increase the navigation timeout, stop ongoing activity, or capture bounded sections. |
| A locator screenshot fails | The locator matches zero or multiple unsuitable elements | Use a specific selector, call first() when appropriate, and wait for visibility. |
| Animations cause flaky diffs | Transitions or video are changing pixels | Use animations: 'disabled' and wait for a stable state. |
| Clip coordinates are rejected | The rectangle is outside the page or has invalid dimensions | Use non-negative x/y, positive dimensions, and CSS-pixel coordinates inside the page. |
9. Performance, reliability, and cost considerations
- Pixel count: doubling both output dimensions increases the number of pixels substantially. Use device scale only where inspection or processing benefits from it.
- Scope: locator and clip captures are cheaper to store and process than very tall full-page images.
- Determinism: record URL, viewport, device scale factor, browser version, color scheme, wait condition, and screenshot options with each artifact.
- Memory: avoid holding many full-page buffers at once; write files or process each buffer before starting the next capture.
- Retries: retry navigation failures selectively. A retry cannot fix a deterministic selector, authentication, or blocked-resource problem.
- External services: ScreenshotNeo’s billing model charges only clean shots; bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, with verdict and billing information returned in headers.
10. FAQ
Does high resolution mean a larger viewport?
No. The viewport controls CSS layout dimensions. Device scale controls how many output pixels represent those CSS pixels.
Should I use scale: 'device' for visual tests?
Use it when the test is intended to compare device pixels. Use an explicit scale consistently across the baseline and comparison captures.
Is a full-page screenshot automatically high resolution?
No. fullPage: true selects the complete scrollable document. Add scale: 'device' when you also need device-pixel output.
Can I capture only one component?
Yes. Use locator.screenshot() for an element or clip for a coordinate rectangle.
When should I use an API instead of Playwright?
Use Playwright when you need browser-level control in your own runtime. Use ScreenshotNeo when you prefer a hosted screenshot request, automatic removal of consent UI, billing protection for failed captures, or MCP tools for AI agents.


