How to Generate Responsive Website Screenshots for Every CSS Breakpoint
Use Playwright to capture a page at every CSS breakpoint, with consistent viewport dimensions, image scale, and filenames.
Use Playwright to set the browser viewport to each CSS width you want to inspect, take a screenshot, and save it with a filename that records the dimensions. Get the widths from the site’s actual responsive CSS; there is no universal set of device widths that covers every site’s breakpoints. Test just below and above each breakpoint when you need to inspect the transition.
This guide uses Playwright with JavaScript. It also shows how to choose viewport-only versus full-page captures, when device emulation matters, how to keep image scale consistent, and how to troubleshoot the capture loop.
1. Find the breakpoints and choose the widths
Start by inspecting the stylesheets and identifying media queries or other responsive conditions that affect the page. Make an explicit list of widths to test. For a breakpoint at 768 CSS pixels, for example, test widths on both sides such as 767 and 768, or 768 and 769, depending on the CSS condition. Include a representative width within each range as well as transition edges if layout behavior at the boundary matters.
Do not assume a standard phone, tablet, and desktop set will cover the site. A preset can represent a device, but it does not discover the breakpoints in your CSS. Playwright supports viewport configuration and device emulation, including user agent, screen size, viewport, and touch capability. Playwright’s emulation guide documents presets and viewport overrides.
2. Install Playwright
Create a project and install Playwright’s test package. The following commands work in a new Node.js project:
mkdir responsive-captures
cd responsive-captures
npm init -y
npm install --save-dev @playwright/test
npx playwright install chromium
Use the browser your team needs to review. The example below launches Chromium; you can use another Playwright browser when cross-browser rendering is part of the task.
3. Capture a page at every CSS breakpoint
Save this as capture-breakpoints.mjs. It accepts a target URL as its first argument, creates an output directory, sets each viewport, waits for the page to load, and writes a viewport screenshot with dimensions in the filename.
import { chromium } from '@playwright/test';
import { mkdir } from 'node:fs/promises';
const url = process.argv[2];
if (!url) {
throw new Error('Usage: node capture-breakpoints.mjs https://example.com');
}
// Replace these with widths derived from the site's responsive CSS.
const widths = [375, 767, 768, 1024, 1280];
const height = 900;
const outputDir = 'screenshots';
await mkdir(outputDir, { recursive: true });
const browser = await chromium.launch();
try {
for (const width of widths) {
const page = await browser.newPage({
viewport: { width, height },
deviceScaleFactor: 1,
});
try {
await page.goto(url, { waitUntil: 'load', timeout: 30000 });
await page.screenshot({
path: `${outputDir}/page-${width}x${height}.png`,
fullPage: false,
animations: 'disabled',
scale: 'css',
});
console.log(`Saved ${outputDir}/page-${width}x${height}.png`);
} finally {
await page.close();
}
}
} finally {
await browser.close();
}
Run it with:
node capture-breakpoints.mjs https://example.com
The output names make the tested viewport dimensions visible to reviewers. The script uses one page per width so each capture has an explicit viewport and is closed even if navigation or capture fails. For a production workflow, consider writing a small manifest containing the URL, widths, timestamp, browser version, and capture settings alongside the files.
4. Choose viewport or full-page capture
The example sets fullPage: false, which captures the visible viewport. Use this when checking how the layout looks in the initial screen at each width. Set fullPage: true when reviewers need the entire scrollable page in each image. A full-page screenshot is a different capture scope, not simply a taller viewport. See the Playwright Page API for the screenshot options.
For very long pages, full-page images can be large and harder to compare. If the problem is a particular section, capture that section separately or use a locator screenshot instead of creating enormous files.
5. Keep pixel scale and device context consistent
Playwright’s screenshot API supports CSS-pixel and device-pixel scaling. With scale: 'css', output has one image pixel per CSS pixel. With device scaling, output uses device pixels and can be larger on a high-density display. Use the same scale for every image in a comparison set so differences reflect responsive layout rather than output resolution.
The script sets deviceScaleFactor: 1 and scale: 'css' for a straightforward CSS-width comparison. If the page’s behavior depends on touch, user agent, or other device characteristics, create a context with an appropriate Playwright device preset, then override its viewport for the exact breakpoint width. Explicit viewport settings take precedence when configured after a device preset. Device emulation and a width sweep answer different questions: the sweep checks your chosen CSS widths; emulation adds device characteristics.
6. Make captures useful for review
- Capture widths immediately around each breakpoint when you need to inspect the transition.
- Use stable filenames that include page identity and dimensions, such as
home-375x900.png. - Keep the height, browser, scale, and navigation conditions consistent across the set.
- Review for horizontal overflow, clipped content, unexpected wrapping, and elements that change position or visibility.
- If content changes between runs, control test data and any page state that affects rendering before comparing images.
These are review practices; Playwright provides the viewport and screenshot controls, while deciding which widths matter remains a project-specific task.
7. Variations for common needs
Capture multiple URLs
Wrap the width loop in a URL loop and include a sanitized page identifier in the output path. Avoid using the raw URL as a filename; query strings and path characters can create invalid or confusing filenames.
Capture a single element
When the review is about one component, locate it and take a locator screenshot rather than capturing the entire page. Keep the same viewport setup so responsive changes around that component still appear correctly.
Use a test project configuration
If captures are part of automated tests, define viewport dimensions in Playwright test projects or override the page viewport for a particular test. This keeps the responsive matrix in the test configuration and makes it easier to run the same checks repeatedly.
Use device presets when device behavior matters
Choose a preset if the behavior depends on parameters such as user agent or touch support, not only CSS width. You can still override its viewport to hit the precise breakpoint under review. The emulation documentation explains device parameters and viewport configuration.
8. cURL, Python, and Node.js alternatives
Playwright is the browser automation method used above. These other snippets are useful when your workflow already uses a browser automation library or when you want a hosted screenshot API instead of managing a browser locally. The API example below is ScreenshotNeo; parameter names used by other screenshot APIs may differ.
cURL with ScreenshotNeo
curl -G "https://api.screenshotneo.com/v1/shot" \
-d access_key=YOUR_API_KEY \
--data-urlencode url=https://example.com \
-d width=375 \
-d height=900 \
-o page-375x900.webp
Python with ScreenshotNeo
import requests
url = "https://example.com"
widths = [375, 767, 768, 1024, 1280]
height = 900
for width in widths:
response = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={
"access_key": "YOUR_API_KEY",
"url": url,
"width": width,
"height": height,
},
timeout=90,
)
response.raise_for_status()
filename = f"page-{width}x{height}.webp"
with open(filename, "wb") as image_file:
image_file.write(response.content)
print(f"Saved {filename}")
Node.js with ScreenshotNeo
const url = 'https://example.com';
const widths = [375, 767, 768, 1024, 1280];
const height = 900;
for (const width of widths) {
const q = new URLSearchParams({
access_key: 'YOUR_API_KEY',
url,
width: String(width),
height: String(height),
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
const bytes = Buffer.from(await res.arrayBuffer());
const filename = `page-${width}x${height}.webp`;
await import('node:fs/promises').then(({ writeFile }) => writeFile(filename, bytes));
console.log(`Saved ${filename}`);
}
See the ScreenshotNeo API documentation for its request options, authentication, and response details. The response can be PNG, JPEG, WebP, or PDF; choose the requested output format to match your workflow.
Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server. Send a request for each width in your breakpoint list. For example:
curl -G "https://api.screenshotneo.com/v1/shot" \
-d access_key=YOUR_API_KEY \
--data-urlencode url=https://example.com \
-d width=375 \
-d height=900 \
-o page-375x900.webp
Repeat the request with each width and use a matching output filename. The API docs describe the supported parameters.
- Cookie 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 not billed. Responses identify the page verdict and billing status in headers.
- An MCP server lets AI agents use
take_screenshot,get_page_info, andcapture_pdf. - The free plan includes 1,000 screenshots a month with no card. Paid plans start at $5 for 3,000 screenshots.
Sign up for 1,000 free screenshots a month with no card.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Every image looks the same size | The viewport was not changed between captures, or dimensions in filenames do not match the configured values. | Set viewport for each width before navigation and verify the loop’s width value is used in the screenshot name. |
| A breakpoint transition is missing | The chosen widths sample only one side of a media-query boundary. | Inspect the CSS and add widths immediately below and above the condition. |
| Screenshot is blank or incomplete | Navigation may have failed, timed out, or required more time for client-side rendering. | Check the target URL and browser logs, set an appropriate navigation wait condition, and wait for a relevant selector when the page renders asynchronously. |
| Images or content appear late | They load after the initial page load event or only when scrolled into view. | Wait for the content needed for the capture. For full-page or lazy-loaded content, use a workflow that scrolls through the page before capturing, and verify the resulting image. |
| Files are hard to compare | Viewport height, scale, browser, or page state differs between runs. | Keep those settings consistent and use dimension-based filenames. |
| Output is unexpectedly large | Device-pixel scale or full-page capture creates more pixels than expected. | Use CSS scale for one pixel per CSS pixel, or use viewport-only capture when a full-page image is unnecessary. |
| Device preset misses the intended breakpoint | The preset viewport width differs from the CSS width you meant to test. | Override the preset viewport explicitly with the target width. |
| ScreenshotNeo returns an HTTP error | The key, URL, or request parameters may be invalid, or the request may have timed out. | Check the API docs, URL-encode the target URL, confirm the access key, and handle non-success HTTP responses before saving the response body as an image. |
Performance, reliability, and cost
A local sweep performs one navigation and screenshot per width, so runtime grows with the number of widths and page load time. Reuse a browser process, close each page after capture, and avoid launching many captures at once if the site or machine cannot handle the load. Parallel capture can reduce elapsed time, but it also increases browser and target-site load; choose concurrency deliberately.
For repeatable visual review, keep the browser version, viewport height, scale, and page state stable. Network-dependent pages can vary between runs, so use controlled content where possible and wait for the specific page state needed rather than relying on an arbitrary short delay.
With a local Playwright setup, account for the compute and maintenance needed to install and run browsers. With ScreenshotNeo, pricing is based on its published monthly plans: Free has 1,000 shots; Starter is $5 for 3,000; Growth is $15 for 15,000; Pro is $39 for 60,000; Scale is $99 for 250,000; and Business is $249 for 1,000,000. Yearly billing gives two months free, and every feature is on every plan. Clean screenshots are billed; bot checks and other failed or empty results, plus cache hits, are not billed. Consult the documentation for response headers and request behavior.
FAQ
How do I take screenshots at every CSS breakpoint?
Read the site’s responsive CSS, list the widths that matter, then set the browser viewport and capture once for each width. Add widths on both sides of boundaries when reviewing transitions.
How can I capture a website at multiple viewport sizes?
Use a loop over explicit width and height pairs, set the viewport for each capture, and include the dimensions in each output filename.
Do I need a device preset to test responsive CSS?
No. Explicit viewport widths are enough for a width-based CSS sweep. Use a device preset when user agent, touch, or other emulated device settings affect the behavior under review.
Should I use full-page screenshots?
Use them when the review needs all scrollable content. Use viewport captures when the question is what fits on screen at a given width.


