ScreenshotNeo

BlogHow-to

How to Set the Aspect Ratio of Playwright Screenshots

Set a Playwright screenshot’s shape with viewport dimensions or an exact clip rectangle. Learn how fullPage, scale and device density affect output.

By the ScreenshotNeo team1 October 20266 min read

Set the aspect ratio by controlling the rectangle Playwright captures. For a viewport screenshot, choose a viewport width and height whose ratio matches your target. For an exact crop, pass a clip object with the required x, y, width and height to page.screenshot(). The ratio is width / height. The scale option and deviceScaleFactor change pixel density; they do not change the CSS-space aspect ratio.

For example, 16:9 can be 1600×900, 1280×720 or 960×540. Use a viewport when the page should lay itself out at that size. Use clip when the page can keep its normal viewport but the saved image must have an exact frame.

Choose the right Playwright control

Goal Use What determines the ratio?
Render the page at a particular shape viewport or page.setViewportSize() Viewport width / height
Save an exact rectangle page.screenshot({ clip }) clip.width / clip.height
Capture all scrollable content fullPage: true Content-dependent height; no fixed ratio
Increase or normalize output pixels scale: 'css'|'device' and context deviceScaleFactor Underlying CSS rectangle stays the same

Playwright Test documents a 1280×720 default viewport, which is 16:9. Set dimensions explicitly when geometry must remain predictable across machines and releases. Test options reference.

Set a viewport aspect ratio

Set the viewport before navigation where possible. Responsive sites can change their layout when the viewport changes, and the Page API warns that resizing may produce unexpected page behavior. Page.setViewportSize()

import { test, expect } from '@playwright/test';

test.use({
  viewport: { width: 1200, height: 800 }, // 3:2
});

test('capture a viewport screenshot', async ({ page }) => {
  await page.goto('https://example.com');
  await page.screenshot({ path: 'viewport.png', scale: 'css' });

  // A separate, exact 16:9 crop.
  await page.screenshot({
    path: 'crop.png',
    clip: { x: 0, y: 0, width: 1600, height: 900 },
  });
});

For a standalone script:

import { chromium } from 'playwright';

const browser = await chromium.launch();
const page = await browser.newPage({ viewport: { width: 1600, height: 900 } });
await page.goto('https://example.com', { waitUntil: 'networkidle' });
await page.screenshot({ path: '16-9.png', scale: 'css' });
await browser.close();

Python Playwright

from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch()
    page = browser.new_page(viewport={"width": 1600, "height": 900})
    page.goto("https://example.com", wait_until="networkidle")
    page.screenshot(path="16-9.png", scale="css")
    browser.close()

Capture an exact width and height with clip

clip is an image-space rectangle measured in CSS pixels. It gives you a deterministic output shape without forcing the page to reflow. The rectangle must be inside the page’s available content area. Playwright documents the four required fields in the Page.screenshot API.

await page.goto('https://example.com');
await page.screenshot({
  path: 'social-card.png',
  clip: { x: 0, y: 0, width: 1200, height: 630 }, // 40:21
  scale: 'css',
});

To calculate a height from a target width, use height = width / ratio. For a 4:3 frame at 1200 pixels wide, use height 900. To calculate width from a target height, use width = height * ratio.

A clip does not automatically scroll to an element. Scroll first, wait for layout, then clip the coordinates you need:

const chart = page.locator('#chart');
await chart.scrollIntoViewIfNeeded();
await page.waitForTimeout(100);
const box = await chart.boundingBox();
if (!box) throw new Error('Chart is not visible');
await page.screenshot({
  path: 'chart.png',
  clip: { x: box.x, y: box.y, width: box.width, height: box.height },
});

Understand fullPage screenshots

fullPage: true captures the complete scrollable page. Because the page height comes from its content, the result can be extremely tall and its aspect ratio changes as content changes. It is unsuitable when a fixed ratio is a requirement. Use a fixed viewport or clip for a fixed rectangle. Playwright Page API.

await page.screenshot({ path: 'whole-page.png', fullPage: true });

Lazy-loaded images, infinite scroll, animations and consent dialogs can also change full-page height. Wait for the relevant content or use a bounded clip when reproducibility matters.

Scale, deviceScaleFactor and output pixels

scale: 'css' produces one screenshot pixel per CSS pixel. scale: 'device' produces one pixel per device pixel, so a high-density context can create a larger bitmap while keeping the same shape. The browser context’s documented default deviceScaleFactor is 1. These settings affect density, file size and sharpness, not the intended CSS ratio. Page API · Browser context API.

const browser = await chromium.launch();
const context = await browser.newContext({
  viewport: { width: 1200, height: 675 }, // 16:9
  deviceScaleFactor: 2,
});
const page = await context.newPage();
await page.goto('https://example.com');
await page.screenshot({ path: 'retina.png', scale: 'device' });
await browser.close();

Choose css for stable pixel dimensions in visual tests and pipelines. Choose device when you explicitly need device-density output and can accommodate larger files.

Make captures repeatable

  1. Set an explicit viewport or clip rectangle.
  2. Set the viewport before navigation.
  3. Wait for the page state that matters: a selector, a known delay or network idle.
  4. Disable or freeze animations when comparing images.
  5. Use a consistent browser, font set, timezone and device scale factor.
  6. Save with scale: 'css' when exact bitmap dimensions matter.
await page.setViewportSize({ width: 1440, height: 810 }); // 16:9
await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
await page.locator('main').waitFor({ state: 'visible' });
await page.addStyleTag({ content: `*, *::before, *::after { animation: none !important; transition: none !important; }` });
await page.screenshot({ path: 'stable.png', scale: 'css' });

Common mistakes and fixes

Symptom Cause Fix
The image is the wrong shape Viewport dimensions or clip dimensions do not have the target ratio. Compute width / height; set both values explicitly.
fullPage output is very tall Full-page height follows document content. Use a fixed viewport or clip.
Changing scale did not fix the ratio Scale changes density, not the CSS rectangle. Change viewport or clip dimensions.
Layout differs after resizing Responsive breakpoints or scripts react to viewport changes. Set the viewport before goto, or keep the normal viewport and use clip.
Clip throws an out-of-bounds error The rectangle extends beyond the page or viewport. Inspect the element bounding box and keep x + width and y + height within available bounds.
Images or fonts are missing Capture ran before resources finished loading. Wait for a selector, network idle or the specific image/font condition.
Visual diffs vary between runs Animations, timestamps, ads, fonts or device density differ. Freeze motion, control inputs and use a fixed context configuration.

Performance, reliability and cost

Viewport captures usually require less scrolling and image processing than full-page captures. Large clips and high device scale factors produce more pixels and larger files. Full-page captures can be slower and less stable when pages lazy-load content or use infinite scrolling.

For reliable jobs, bound navigation and selector waits with timeouts, retry transient navigation failures, and record the viewport, clip, browser version and scale in your job metadata. Do not treat a screenshot’s pixel dimensions as proof that all page resources loaded; explicitly wait for the content your use case needs.

Playwright itself runs in your environment, so cost depends on your browser infrastructure, execution time and storage. If you only need a URL-to-image request, a managed capture API can remove browser installation and maintenance.

Or skip the browser setup

ScreenshotNeo accepts one GET request and returns a PNG, JPEG, WebP or PDF. Its capture options include full-page shots, custom viewports, device presets, retina scale and CSS-element capture.

See the ScreenshotNeo API documentation for the complete option list. A basic request is:

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 failed: ${res.status}`);
const body = Buffer.from(await res.arrayBuffer());
require('fs').writeFileSync('shot.webp', body);

Cookie and consent banners, newsletter popups and chat widgets are removed before the shot. Bot checks, blank pages, timeouts, failed loads and cache hits are not billed, and the response identifies the result with X-Page-Verdict and X-Billed headers. An MCP server lets Claude, Cursor and other MCP clients call take_screenshot, get_page_info and capture_pdf. The Free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.

FAQ

What dimensions should I use for 16:9?

Any proportional pair works, such as 1280×720, 1600×900 or 1920×1080. Pick dimensions that match your downstream pixel and file-size requirements.

Can I get a fixed ratio from fullPage: true?

No. Full-page height depends on document content. Capture a fixed clip or viewport instead.

Does deviceScaleFactor: 2 make a 4:3 screenshot?

No. It increases device pixels per CSS pixel. Set the viewport or clip ratio separately.

Should I use viewport or clip for social images?

Use a clip when the page should keep its normal responsive layout but the exported frame must be exact. Use a viewport when the page should render responsively at the target dimensions.

Playwright captures the page as rendered. Locate and dismiss the banner before capture, hide it with test CSS, or use a service that handles consent state before taking the shot.