Fix Playwright Mobile Screenshots That Are the Wrong Size
If a Playwright mobile screenshot is larger than its viewport, check device-pixel scaling, viewport overrides, and full-page capture. Here’s how to diagnose and fix it.
If a Playwright mobile screenshot has more pixels than the configured viewport, set scale: 'css' on page.screenshot() when you need one output pixel per CSS pixel. Its documented default is 'device', which captures at device-pixel resolution and can make an image larger on a high-DPI device. Then check the active device preset, viewport overrides, and whether full-page capture is enabled.
1. Check the image dimensions and screenshot scale
Compare the saved image’s width and height in pixels with the page’s CSS viewport dimensions. If both image dimensions are a clean multiple of the viewport dimensions, device-pixel scaling is a useful diagnostic clue, but it is not proof: inspect the capture options and context configuration too.
For a viewport-sized image measured in CSS pixels:
await page.screenshot({ path: 'mobile.png', scale: 'css' });
Use scale: 'device' when you specifically want device-pixel resolution. The output size depends on the emulated device scale factor, so do not assume every mobile image will be exactly twice the viewport dimensions. [Playwright page.screenshot() API]
2. Configure the mobile device and viewport intentionally
Playwright device presets bundle settings such as viewport, screen size, user agent, and touch behavior. If you spread a preset and then want a project-specific viewport, place the viewport property after the spread so it overrides the preset value.
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
projects: [
{
name: 'mobile-chromium',
use: {
...devices['iPhone 13'],
viewport: { width: 390, height: 844 },
},
},
],
});
The device name and dimensions here are examples, not universal targets. Confirm that the preset exists in the Playwright version installed by your project, and choose dimensions that match the test you intend to represent. [Playwright device emulation]
Look for other viewport values in your project, test, or context setup. A later configuration layer may be setting a different viewport than you expect. If you resize a page directly, do so before navigation where practical:
await page.setViewportSize({ width: 390, height: 844 });
await page.goto('https://example.com');
await page.screenshot({ path: 'mobile.png', scale: 'css' });
page.setViewportSize() resizes the page and resets screen. Playwright recommends setting the viewport before navigation because a site may not expect a phone-sized page to change size mid-session. [Playwright page.setViewportSize() API]
3. Check whether the capture is full-page
A viewport screenshot has the viewport’s dimensions. With fullPage: true, Playwright captures the full scrollable page, so a tall image is expected. Remove that option when the required artifact must have the fixed viewport height; keep it when below-the-fold content belongs in the image.
// Visible viewport only
await page.screenshot({ path: 'viewport.png', scale: 'css' });
// Entire scrollable document
await page.screenshot({ path: 'full-page.png', fullPage: true, scale: 'css' });
[Playwright screenshots guide]
4. Distinguish screenshots from visual assertions
page.screenshot() and expect(page).toHaveScreenshot() are different APIs. Their documented default scales differ: page.screenshot() defaults to 'device', while the screenshot assertion API documents 'css'. If exact artifact dimensions matter, set the scale explicitly in the API you use and check the documentation for your installed Playwright version.
import { expect, test } from '@playwright/test';
test('mobile page matches its screenshot', async ({ page }) => {
await page.setViewportSize({ width: 390, height: 844 });
await page.goto('https://example.com');
await expect(page).toHaveScreenshot({ scale: 'css' });
});
[Playwright toHaveScreenshot() API]
5. Runnable example: capture a mobile viewport
In a project with @playwright/test installed, this test sets the viewport before navigation, captures only the visible viewport, and asks for CSS-pixel output. Save it as a Playwright test file and run it with your project’s existing Playwright test command.
import { test } from '@playwright/test';
test('save a CSS-pixel mobile screenshot', async ({ page }) => {
await page.setViewportSize({ width: 390, height: 844 });
await page.goto('https://example.com');
await page.screenshot({
path: 'mobile.png',
fullPage: false,
scale: 'css',
});
});
If you need a device preset, configure it in the project and keep any intended viewport override after the preset spread. A custom viewport alone does not necessarily reproduce every property of a real device preset, such as user agent and touch behavior.
6. Troubleshooting
| Symptom | Likely cause | What to do |
|---|---|---|
| Image width and height are both larger than the viewport | page.screenshot() uses device-pixel scale by default, or the active context has a high device scale factor. |
Set scale: 'css' for CSS-pixel output. If device-pixel output is intended, retain device scale and interpret dimensions accordingly. |
| Image is much taller than expected | fullPage: true captures the scrollable page. |
Remove fullPage for a viewport capture, or keep it if the full document is required. |
| Screenshot uses an unexpected viewport | A preset, project setting, test setting, or later resize determines the effective viewport. | Inspect the final context configuration and put a desired viewport override after the device preset spread. |
| Layout looks desktop-sized despite a phone-sized image | The viewport may be right, but the intended device emulation settings may not be active; a viewport override by itself is not a complete device profile. | Use the relevant Playwright device preset, then override only the values your test intentionally changes. |
| Visual comparison still fails after dimensions match | Rendering can vary with browser, operating system, browser settings, hardware, power source, or headless mode. | Run comparisons in a stable environment that matches the one used to create the baseline. [Playwright visual comparisons] |
| Assertion and saved image have different dimensions | The assertion and direct screenshot APIs document different default scales. | Set scale explicitly for both capture paths and check the installed version’s API documentation. |
7. Choose the right capture settings
| Choice | Use it when | Effect |
|---|---|---|
scale: 'css' |
Output dimensions should correspond to CSS viewport pixels. | One output pixel per CSS pixel. |
scale: 'device' |
You need device-pixel detail. | Output follows device-pixel resolution and may be larger than the CSS viewport. |
| Viewport capture | You need a fixed-height image of what is visible. | Captures the viewport. |
fullPage: true |
You need the entire scrollable page. | Produces an image that may be much taller than the viewport. |
| Device preset | You want a coherent mobile emulation setup. | Provides a bundle of device-related settings; later overrides take precedence. |
| Custom viewport | You need specific dimensions for a test. | Sets the viewport, but does not by itself guarantee all other device emulation settings. |
8. Performance, reliability, and cost
CSS-scale output can contain fewer pixels than device-scale output, which may reduce image size and the work needed to store or compare it. Choose resolution based on the artifact’s purpose; keep the same settings across a visual test suite so snapshots remain comparable. This is a consequence of output dimensions, not a documented Playwright performance benchmark.
For reliable visual comparisons, keep the browser, operating system, settings, and execution mode consistent with the baseline environment. A correct image size does not guarantee identical rendering across environments. Confirm defaults against documentation for the Playwright version your project uses, since device registries and rolling documentation can change.
Playwright’s screenshot capture is part of your browser test workflow; the research for this fix identifies no required paid product or hardware. If you instead want a hosted screenshot API, ScreenshotNeo is one option: clean shots remove cookie and consent banners, newsletter popups, and chat widgets before capture, and only clean shots are billed. Its free plan includes 1,000 shots per month without a card; paid plans start at $5 for 3,000 shots.
Or skip the browser setup
ScreenshotNeo takes a screenshot with one GET request. See the ScreenshotNeo API documentation for the API parameters and response details.
cURL
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
Python
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)
Node.js
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 banners, popups, and chat widgets before the shot. Bot checks, blank pages, and failed loads are never billed. Its MCP server lets AI agents take screenshots, and 1,000 screenshots a month are free with no card; paid plans start at $5 for 3,000.
Sign up for ScreenshotNeo’s free plan and get 1,000 screenshots a month with no card.
FAQ
Does scale: 'css' change the page’s CSS viewport?
No. It controls screenshot output scale. Set the viewport separately through the device or context configuration.
Should every mobile screenshot use CSS scale?
No. Use it when CSS-pixel dimensions are the target. Device scale is appropriate when the output should preserve device-pixel resolution.
Why can two screenshots of the same page still differ?
Browser and operating system versions, settings, hardware, power source, and headless mode can affect rendering. Keep the comparison environment consistent with the baseline.
Primary references: page.screenshot() API, device emulation, screenshots guide, viewport sizing API, screenshot assertion API, and visual comparisons guide.


