How to Capture a Website Screenshot with a Fixed Viewport in Playwright on Windows
Set a fixed Playwright viewport before navigation, capture the visible page on Windows, and control output scale and screenshot behavior.
To capture a website at a fixed viewport size with Playwright on Windows, set the viewport width and height when you create a browser context, navigate to the page, then call page.screenshot(). For example, a 1280 × 720 CSS-pixel viewport produces a screenshot of the visible browser page at that size by default. Use fullPage: true only when you want the whole scrollable page.
The same Playwright APIs work on Windows as on other supported operating systems. Set the viewport before navigation: changing it after a page loads can trigger responsive layout changes. See the official Page API and emulation guide.
1. Install Playwright on Windows
Use a current Node.js installation and run these commands in PowerShell from your project directory. This example uses Playwright’s library directly, rather than Playwright Test.
mkdir playwright-fixed-shot
cd playwright-fixed-shot
npm init -y
npm install playwright
npx playwright install chromium
Save the following as screenshot.js. Replace the example URL and dimensions as needed.
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', { waitUntil: 'load' });
await page.screenshot({ path: 'screenshot.png' });
await context.close();
} finally {
await browser.close();
}
})().catch((error) => {
console.error(error);
process.exitCode = 1;
});
Run it with:
node screenshot.js
The image is written to screenshot.png in the current directory. The viewport dimensions are CSS pixels; the output image’s physical pixel dimensions also depend on the screenshot scale option.
2. Choose where to configure the viewport
Configure the viewport at the narrowest level that matches your use case. Context settings apply to pages in that context; page settings are useful when only one page needs a change; test configuration is useful when a test suite should share the same dimensions.
Browser context: recommended for a standalone script
const context = await browser.newContext({
viewport: { width: 1440, height: 900 },
});
const page = await context.newPage();
await page.goto('https://example.com');
This sets the viewport before the page is created and navigated. It is a good default for repeatable one-off captures and for scripts that create multiple pages with the same viewport.
Page: set it before navigation
const page = await browser.newPage();
await page.setViewportSize({ width: 1280, height: 720 });
await page.goto('https://example.com');
await page.screenshot({ path: 'screenshot.png' });
page.setViewportSize() is handy for a single page. Call it before page.goto() when the initial layout must be rendered at the target dimensions. The Page API notes that setting the viewport resets the screen size; use context-level screen and viewport options if you need to control both.
Playwright Test: set a project-wide viewport
In playwright.config.js, set the viewport under use:
const { defineConfig } = require('@playwright/test');
module.exports = defineConfig({
use: {
browserName: 'chromium',
viewport: { width: 1280, height: 720 },
},
});
Install the test runner and its browser if needed:
npm install --save-dev @playwright/test
npx playwright install chromium
Or set the viewport for an individual test file:
const { test, expect } = require('@playwright/test');
test.use({ viewport: { width: 1280, height: 720 } });
test('captures the page at a fixed viewport', async ({ page }) => {
await page.goto('https://example.com');
await page.screenshot({ path: 'screenshot.png' });
});
Playwright documents project and test settings in its Test use options reference.
3. Choose the capture area and image scale
With fullPage omitted or set to false, Playwright captures the currently visible viewport. Set it to true to capture the full scrollable page. A full-page image is taller than the viewport and can require more time and memory on long pages.
// Visible viewport only (the default)
await page.screenshot({ path: 'viewport.png' });
// Entire scrollable page
await page.screenshot({ path: 'full-page.png', fullPage: true });
Playwright infers the image format from the filename extension. PNG is the default. The documented formats include PNG, JPEG, and WebP; specify type when you want to make the format explicit.
await page.screenshot({ path: 'capture.webp', type: 'webp' });
await page.screenshot({ path: 'capture.jpg', type: 'jpeg', quality: 85 });
Use scale to choose the output pixel density:
| Option | Output | Use it when |
|---|---|---|
scale: 'css' |
One image pixel per CSS pixel | You want dimensions aligned with the configured viewport, such as 1280 × 720. |
scale: 'device' |
Output uses device pixels and can be larger on high-DPI displays | You need more pixel detail and can accept a larger image. |
await page.screenshot({
path: 'css-scale.png',
scale: 'css',
});
For example, a 1280 × 720 CSS-pixel viewport with a device scale factor of 2 can yield a 2560 × 1440 image at device scale. Choose the scale intentionally when comparing image dimensions or managing file size.
4. Configure screen size, device scale, and browser context
A viewport is the browser page’s layout area. It is distinct from the emulated screen size and from device pixel ratio. Set these context options together when the capture should represent a specific screen and pixel density:
const context = await browser.newContext({
viewport: { width: 1280, height: 720 },
screen: { width: 1280, height: 720 },
deviceScaleFactor: 1,
});
Not every capture needs a custom screen or deviceScaleFactor. For a fixed desktop layout, setting viewport is usually the essential part. Avoid changing the viewport after loading unless the goal is specifically to observe the page’s responsive reaction to a resize.
5. Make the screenshot more reproducible
Viewport dimensions alone do not make screenshots pixel-identical. Rendering can vary with the operating system, browser version, browser settings, hardware, power source, and headless mode. For visual regression baselines, run captures in the same environment as the baseline and keep the Playwright and browser versions stable. See Playwright’s visual comparisons guidance.
Control capture details only when they matter to the task:
animations: 'disabled'disables animations for the screenshot.caret: 'hide'hides the text caret.clipcaptures a specified rectangle.styleapplies CSS while capturing, useful for hiding an element or stabilizing a visual detail.
await page.screenshot({
path: 'stable.png',
animations: 'disabled',
caret: 'hide',
});
These options do not set the viewport dimensions. Keep viewport configuration and screenshot-specific controls separate so it’s clear which setting controls layout and which controls the final image.
6. Common problems and fixes
| Symptom | Likely cause | Fix |
|---|---|---|
| Screenshot has the wrong layout or breakpoints | The viewport was changed after navigation, or the context uses a different size than intended. | Set viewport on browser.newContext() or call page.setViewportSize() before page.goto(). Confirm the width and height are CSS pixels. |
| Image is twice as large as expected | scale: 'device' and a device scale factor above 1 increase output pixel dimensions. |
Use scale: 'css' for one output pixel per CSS pixel, or set the context’s deviceScaleFactor deliberately. |
| Only the visible part of a long page appears | fullPage is off, which is the default. |
Pass fullPage: true. Expect a taller output and potentially more memory use. |
| Navigation times out | The page is slow, a resource hangs, or the selected navigation condition is not reached. | Check the URL and network access. Choose an appropriate waitUntil such as domcontentloaded for pages that keep network connections open; wait for a specific selector when the screenshot depends on particular content. |
| Screenshot is blank or content is missing | The page may not have rendered the needed content before capture, or content loads lazily. | Wait for a meaningful selector or for the content to become visible before capturing. Avoid assuming that navigation completion means every third-party or lazy-loaded resource is ready. |
| Browser executable is missing | The Playwright package is installed, but its browser binary has not been installed in this environment. | Run npx playwright install chromium from the project directory. |
| Visual baseline differs on Windows | The baseline was produced with another OS, browser version, rendering mode, or environment. | Use the same host and browser versions for baseline generation and comparison. Fixed dimensions are necessary for consistent layout but do not guarantee identical pixels across environments. |
7. Performance, reliability, and cost
A viewport screenshot captures less content than a full-page screenshot, so it generally avoids the extra work of rendering and encoding a long scrollable page. Larger dimensions, device-scale output, and complex pages can increase image size, memory use, and capture time. Prefer the smallest viewport and output scale that meet the requirement.
For reliability, close contexts and browsers in a finally block, as in the runnable example. When automating many pages, reuse a browser process where appropriate and create isolated contexts for captures with different settings. Keep navigation and content waits tied to the page’s actual needs instead of relying on arbitrary long delays.
Playwright is an open-source automation library; this workflow does not make a per-screenshot API charge. Your operational costs are the machine, execution time, storage, and any CI infrastructure you use. Full-page and high-density captures may use more of those resources.
8. Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server from ScreenshotNeo. One GET request returns a PNG, JPEG, WebP, or PDF. Its API accepts the familiar screenshot parameters used by other screenshot APIs, and the API documentation covers its options.
curl -G "https://api.screenshotneo.com/v1/shot" \
-d access_key=YOUR_API_KEY \
--data-urlencode url=https://example.com \
-o shot.webp
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://example.com"},
timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({
access_key: 'YOUR_API_KEY',
url: 'https://example.com',
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
const image = Buffer.from(await res.arrayBuffer());
require('node:fs').writeFileSync('shot.webp', image);
- Cookie and consent banners, newsletter popups, and chat widgets are removed before the shot; each cleanup step can be turned off.
- Bot checks, blank pages, timeouts, failed loads, and cache hits are never billed. Response headers say which verdict applied and whether the request was billed.
- An MCP server provides
take_screenshot,get_page_info, andcapture_pdftools for Claude, Cursor, and other MCP clients. - 1,000 screenshots a month are free with no card; paid plans start at $5 for 3,000 screenshots.
Sign up for ScreenshotNeo’s free plan and get 1,000 screenshots a month with no card.
9. FAQ
Does Windows need a special viewport screenshot API?
No. Use the same Playwright viewport and screenshot APIs on Windows. The main Windows-specific setup step is installing the browser binary with Playwright.
Does a 1280 × 720 viewport guarantee a 1280 × 720 PNG?
Only when the output scale is CSS pixels. With device scale, the image can have more pixels depending on the device scale factor.
Should I use a fixed delay before taking the screenshot?
Only when a known delay is appropriate for the page. For more reliable results, wait for the specific content the screenshot needs to show.
Can I capture the full page and still set a fixed viewport?
Yes. The viewport determines the page’s layout width and visible height; fullPage: true extends the capture through the scrollable content.


