How to Test Screenshot API Rendering with a Fixed Viewport
Set a fixed browser viewport, control device emulation, and compare screenshots against reviewed baselines. Includes runnable Playwright code and a debugging guide.
To test screenshot API rendering at a fixed viewport, set an explicit width and height in CSS pixels before navigating to the page, capture the visible viewport, and compare the result with a reviewed baseline. Keep the browser version and test environment consistent as well: a fixed viewport controls layout dimensions, but it does not make rendering identical across operating systems, browser versions, settings, hardware, or headless modes.
This guide uses Playwright Test for a reproducible visual regression check. It also explains device emulation, screenshot scale, baseline review, tolerances, common failures, and how to use a screenshot API for repeatable captures.
1. Set up a fixed-viewport visual test
Install Playwright Test and its browser binaries, then create a test that sets the viewport on the browser context before navigation. The test below uses a 1280 × 800 CSS-pixel viewport and checks a screenshot against a committed reference.
npm init -y
npm install --save-dev @playwright/test
npx playwright install chromium
Create tests/viewport.spec.ts:
import { test, expect } from '@playwright/test';
test('landing page at a fixed viewport', async ({ browser }) => {
const context = await browser.newContext({
viewport: { width: 1280, height: 800 },
deviceScaleFactor: 1,
});
const page = await context.newPage();
await page.goto('https://example.com', { waitUntil: 'networkidle' });
await expect(page).toHaveScreenshot('landing-1280x800.png', {
fullPage: false,
animations: 'disabled',
caret: 'hide',
maxDiffPixelRatio: 0.01,
});
await context.close();
});
Run it once to generate the initial snapshot, inspect that image, and commit the reviewed reference. Subsequent runs compare captures against it. The initial generated image is only a valid baseline after someone reviews it; it can faithfully record an unintended state too.
npx playwright test --update-snapshots
npx playwright test
Playwright’s screenshot assertion API and its baseline workflow are documented in Visual comparisons and PageAssertions.
2. Choose the viewport and emulation inputs
A viewport is the visible page area in CSS pixels. Set both dimensions explicitly. A width-only choice is incomplete because the height affects what is visible, sticky elements, and viewport-relative layout. Set context options before creating/navigating the page so the initial load uses the intended dimensions.
| Input | What it controls | When to specify it |
|---|---|---|
| Viewport width and height | Layout viewport dimensions, in CSS pixels | Every fixed-viewport test |
| Device scale factor | Relationship between CSS pixels and device pixels | When testing high-density output or matching a device |
| Mobile emulation | Mobile behavior such as a mobile viewport | When the target is a mobile browser experience |
| Touch support | Whether touch input is available | When page behavior depends on touch capability |
| User agent | Browser identity presented to the site | When behavior varies by browser or device identity |
| Screen dimensions | Emulated screen dimensions, distinct from viewport dimensions | When the test specifically depends on screen emulation |
These controls are related, but they are not interchangeable. A device preset can set several of them, and an explicitly configured viewport can override its viewport dimensions. If using a preset, inspect its settings and state the values the test actually relies on. See Playwright’s Emulation guide and Page API.
Example: emulate a mobile viewport
const context = await browser.newContext({
viewport: { width: 390, height: 844 },
screen: { width: 390, height: 844 },
deviceScaleFactor: 2,
isMobile: true,
hasTouch: true,
userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1',
});
const page = await context.newPage();
await page.goto('https://example.com');
This is a configuration example, not a guarantee that a desktop browser reproduces every aspect of a particular physical phone. Choose emulation settings to match the behavior your test is meant to cover, and keep them unchanged when producing and comparing baselines.
3. Capture the right area at the right pixel scale
For a fixed-viewport test, use a viewport screenshot. Full-page capture answers a different question: it captures the whole scrollable document, so its image dimensions and content extend beyond the visible viewport. Enable it only when the test is meant to validate the full page.
Playwright’s screenshot scale option can be css or device. CSS scale produces one image pixel per CSS pixel. Device scale uses device pixels, so a high device scale factor produces a larger raster image for the same CSS viewport. Choose intentionally and keep it constant in the baseline and test runs.
// Viewport capture, one output pixel per CSS pixel
await page.screenshot({ path: 'viewport.png', fullPage: false, scale: 'css' });
// Viewport capture at device-pixel scale
await page.screenshot({ path: 'viewport-retina.png', fullPage: false, scale: 'device' });
// Full document capture; not a viewport-only test
await page.screenshot({ path: 'full-page.png', fullPage: true });
For a 1280 × 800 CSS viewport at CSS scale, expect a 1280 × 800 image. At device scale factor 2 with device scale, the raster dimensions are larger because each CSS pixel maps to multiple device pixels. Consult the Page screenshot API for supported screenshot options.
4. Make the baseline comparison meaningful
A visual assertion is useful when it catches the changes you care about without failing on irrelevant movement. Playwright Test’s toHaveScreenshot() creates a snapshot on its first run and compares later captures. Review and commit reference images so a change can be inspected in code review.
- Set a tolerance deliberately.
maxDiffPixelRatiolimits the share of pixels allowed to differ. A stricter threshold catches small changes but may be sensitive to rendering noise. There is no universally correct value; choose one based on what the test must detect. - Stabilize page state. Wait for the relevant content, fonts, and images to load. Avoid capturing while asynchronous content is still changing.
- Hide or normalize volatile content. Timestamps, rotating banners, randomized data, and live counters can make a page differ on every run. Remove or normalize them in the test when they are outside its purpose.
- Disable animation when appropriate. Playwright’s screenshot assertion options can disable animations and hide the caret. Use these only when motion and caret state are not what the test is meant to verify.
- Use a stylesheet for repeatability. The screenshot assertion supports a stylesheet for hiding or normalizing dynamic elements. Keep such overrides narrow so they do not conceal real regressions.
- Keep the environment consistent. Generate and compare baselines using the same browser version and environment where possible. Operating system, browser settings, hardware, power source, and headless mode can affect rendering.
Playwright’s visual comparison process takes repeated screenshots until two consecutive captures match when creating a snapshot. This helps avoid capturing an unstable frame, but it does not remove the need to stabilize the page and environment. See the official visual comparison guidance.
5. Test screenshot API output with a fixed viewport
If the screenshot service accepts viewport parameters, send explicit width and height along with the URL, and request viewport capture rather than full-page output. Keep output scale and other emulation settings fixed across baseline and candidate captures. A generic API’s parameter names and authentication are service-specific, so use its documentation for the actual request syntax.
For a repeatable API test, save the returned image, verify that the request succeeded, and compare it to a reviewed baseline with your visual comparison tool. Record the dimensions and relevant request settings alongside the snapshot so a later difference can be traced to a changed viewport, scale, or browser environment. Fixed dimensions improve repeatability; they do not prove that the service’s browser or host environment stayed identical over time.
Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server. A single GET request can return a PNG, JPEG, WebP, or PDF. Set the viewport with width and height; see the ScreenshotNeo API documentation for the complete options.
cURL
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=800 \
-d format=webp \
-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",
"width": 1280,
"height": 800,
"format": "webp",
},
timeout=90,
)
r.raise_for_status()
with open("shot.webp", "wb") as image:
image.write(r.content)
Node.js
const q = new URLSearchParams({
access_key: 'YOUR_API_KEY',
url: 'https://stripe.com',
width: '1280',
height: '800',
format: 'webp',
});
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());
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', image));
In addition to fixed dimensions, ScreenshotNeo supports device presets and custom viewport sizes, retina scale, full-page capture, selector capture, dark mode, custom CSS and JavaScript, wait conditions, and request blocking. Cookie banners, newsletter popups, and chat widgets are removed before the shot, with each cleanup step switchable. Bot checks, blank pages, timeouts, failed loads, and cache hits cost nothing; response headers report the page verdict and billing status. Its MCP server provides take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients.
The free plan includes 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 screenshots; every feature is on every plan. Create a free account and get 1,000 screenshots a month, with no card.
6. Troubleshooting common differences
| Symptom | Likely cause | Fix |
|---|---|---|
| Layout wraps differently | Viewport width differs, or mobile emulation/user agent is inconsistent | Set width and relevant emulation context explicitly before navigation; verify the test is not relying on a preset that changes those values. |
| Image dimensions are unexpectedly large | Device-pixel scale is used with a device scale factor above 1 | Use scale: 'css' for one output pixel per CSS pixel, or keep device scale fixed and expect larger raster dimensions. |
| Screenshot includes content below the fold | Full-page capture is enabled | Use fullPage: false for viewport-only comparison. |
| Snapshot changes between runs | Dynamic page content, unfinished loading, animation, or environment variation | Wait for stable content, normalize only irrelevant dynamic regions, disable irrelevant motion, and use the same browser and host environment. |
| Baseline fails on another machine | OS, browser version, settings, hardware, or headless mode differs | Run comparison in the baseline environment or regenerate and review baselines for the intended environment. |
| First test run creates a snapshot instead of detecting a regression | No reviewed baseline exists yet | Inspect the generated image, then commit it as the reference before relying on later comparisons. |
| API capture shows a blank or challenge page | The destination did not produce the expected page during capture | Inspect the response status and service-specific page verdict; verify the URL and whether the site requires interaction or blocks automated requests. |
7. Performance, reliability, and cost
Visual comparisons add browser rendering and image comparison work to a test run. Keep the test focused on the viewport and states that matter; full-page and device-pixel captures produce larger images and can take more storage and comparison work. Reuse a stable browser setup and avoid repeatedly capturing a page before it reaches the state under test.
For reliable results, pin the browser version and run baseline generation and comparison in the same environment. A fixed viewport controls the layout dimensions, but not fonts, browser rendering, operating system, hardware, or live content. Treat a mismatch as a signal to inspect both the page change and the capture environment before updating a baseline.
With a self-hosted Playwright workflow, account for the compute and maintenance of the browser environment and snapshot storage. With ScreenshotNeo, usage is plan-based: free includes 1,000 shots per month, and paid plans begin at $5 for 3,000. Only clean shots are billed; bot checks, blank pages, timeouts, failed loads, and cache hits are not billed. Check the API documentation for parameters and response headers before integrating billing-sensitive workflows.
FAQ
Does a fixed viewport guarantee identical screenshots?
No. It fixes the visible layout dimensions, but browser version, operating system, settings, hardware, headless mode, and page state can still affect rendering.
Should the baseline be generated in CI?
Generate it in the same environment used for comparisons, review the resulting image, and commit the approved baseline. A baseline should represent an accepted appearance, not merely the first output produced by automation.
When should I use full-page screenshots?
Use them when the requirement concerns the complete scrollable document. For a fixed viewport rendering check, compare the visible viewport instead.
Can I use a device preset and still set a custom viewport?
Yes. Playwright documents that the viewport can be overridden even when a device preset is used. Set and record the final viewport and other emulation values your test depends on.


