How to Test Responsive Layouts with Percy Across Viewport Sizes
Choose Percy widths around real layout breakpoints, capture the right DOM state at each size, and keep visual regression coverage useful.
Percy tests responsive layouts by capturing a page’s DOM in your test browser and rendering that snapshot at the viewport widths you configure. Choose widths based on your application’s actual breakpoints and layout changes. If the DOM itself changes with viewport size, capture a separate DOM state at each relevant width using Percy’s responsive DOM workflow.
Percy’s documented default widths are 375 and 1280 pixels, with a default minimum height of 1024 pixels. They are starting points, not a complete breakpoint plan. Each width creates a separate screenshot and counts toward monthly screenshot usage. BrowserStack’s Percy guide describes the standard workflow: “You provide a list of responsive breakpoint widths and we take care of the rest.”
1. Choose viewport widths that exercise real layout changes
Start with the CSS breakpoints and component transitions in the application. A useful width set covers representative narrow and wide layouts, plus the boundary around a breakpoint when the layout could fail as it changes.
| Width choice | What it helps catch |
|---|---|
| Just below a breakpoint | Overflow or cramped content before a layout switches. |
| At or just above a breakpoint | Problems in the newly activated layout, such as a navigation change. |
| Representative narrow width | Small-screen behavior such as stacked controls and mobile navigation. |
| Representative wide width | Desktop composition, maximum widths, and spacing. |
Do not add widths merely because they correspond to named devices. Add them when they exercise a meaningful layout state or a risky transition. Too few widths can miss a breakpoint regression; too many increase visual review work and screenshot usage.
2. Configure Percy responsive widths
Set project-wide widths in .percy.yml. Replace the example list with widths that represent your app’s breakpoints. The configuration reference documents widths in pixels and a default minimum height of 1024 pixels. Snapshot-level widths can override the global list in supported SDKs; check your SDK’s current documentation for its exact call syntax and merge behavior.
version: 2
snapshot:
widths: [360, 390, 767, 768, 1024, 1280, 1440]
min-height: 1024
For this example, 767 and 768 probe the transition at 768 pixels. Use boundary pairs only where the interface has a meaningful breakpoint or where testing has shown a risk. Percy configuration also supports per-snapshot options such as widths, min-height, and percy-css; supported options depend on the SDK.
3. Capture a stable application state
Call the Percy snapshot function from the browser automation that already navigates to and prepares the page. Wait for the relevant content and interactions to settle first. Use a consistent snapshot name for the same UI state across builds so Percy can compare it with its baseline.
The exact method signature varies across Percy SDKs and versions. For example, JavaScript SDK workflows commonly call percySnapshot(page, name, options) after navigating a Playwright page. Confirm the supported method and options in the documentation for your installed SDK. Percy’s SDK workflow documentation explains that the SDK captures DOM state and assets, then Percy renders screenshots at configured widths.
// Illustrative Playwright flow; install and import the Percy SDK
// according to the documentation for your project and SDK version.
await page.setViewportSize({ width: 1280, height: 900 });
await page.goto('http://localhost:3000/products');
await page.getByRole('heading', { name: 'Products' }).waitFor();
await percySnapshot(page, 'Products page');
With project widths configured, a standard DOM snapshot can be rendered at all configured widths. The code above illustrates the sequencing; use the exact import and invocation documented for your SDK. If a test uses dynamic data, stabilize it so that unrelated content changes do not obscure layout differences.
4. Decide whether one DOM snapshot represents every width
Standard responsive rendering works when the captured DOM represents the UI at all target widths. CSS-only changes such as flex wrapping, grid columns, and media-query styling often fit this model.
Capture viewport-specific DOM when the application changes its DOM based on viewport size—for example, it conditionally mounts different navigation components or replaces a component with a structurally different version. A DOM captured at desktop width may not contain the mobile-only state, even if Percy renders that snapshot at a narrow width.
Percy documents a responsiveSnapshotCapture workflow for supported SDKs. The documented feature applies to Percy SDK projects and is implemented for Selenium and Playwright; the documentation lists a Percy CLI prerequisite of version 1.31.10 or higher and SDK-specific requirements. Check the current guide for your framework, SDK version, and browser before enabling it. The flag’s availability and behavior are not universal across every Percy integration.
version: 2
snapshot:
responsiveSnapshotCapture: true
widths: [375, 768, 1280]
With responsive DOM capture, Percy can capture the DOM at each configured viewport instead of reusing only the original DOM. The browser must be resized for those captures. Some workflows require deferred uploads and matching snapshot names to group responsive variants; follow the responsive DOM guide for your SDK’s setup. Browser and SDK constraints matter: the feature documentation calls out browser resizing caveats and width limitations for Firefox and older Selenium setups. Those are implementation constraints, not design breakpoint recommendations.
5. Review diffs and manage screenshot usage
Review the rendered widths against approved visual baselines in Percy. Check both the changed region and the viewport: a change that looks correct on a desktop width can still break a mobile state.
Every responsive width is counted as a separate screenshot. BrowserStack’s example says a snapshot rendered at 375 and 1280 pixels counts as two screenshots. Estimate usage by multiplying the number of snapshot states by the number of widths rendered, then include any additional states or captures in your suite. Reduce redundant widths if review volume or usage grows, while keeping widths that cover distinct layout behavior.
Common problems and fixes
| Problem | Likely cause | What to do |
|---|---|---|
| Mobile rendering shows desktop structure | The captured DOM was taken at desktop width, but the application changes DOM structure by viewport. | Use the supported responsive DOM capture workflow and capture at each relevant viewport. Confirm SDK, CLI, and browser prerequisites. |
| A breakpoint regression is missed | The width list skips the transition or only uses generic device widths. | Add widths on both sides of the actual breakpoint where the transition is risky. |
| Snapshot option appears ignored | The installed SDK may not support the option, or snapshot-level/global precedence differs from expectation. | Check the current SDK documentation and its version. Confirm whether snapshot settings override or merge with project settings. |
| Responsive DOM capture fails or produces ungrouped snapshots | Prerequisites, deferred upload behavior, or snapshot naming requirements are not met. | Follow the responsive DOM guide for the integration, enable the required upload behavior, and use the documented naming pattern. |
| Narrow widths render incorrectly in a particular browser | Browser or driver resizing limits can constrain supported capture widths. | Review Percy’s current browser and SDK caveats; verify the chosen width with the supported setup. |
| Snapshots differ because content moves or loads late | The page state is not stable when the snapshot is captured. | Wait for a reliable page or component condition, and control dynamic data and animations in the test setup. |
| Usage is higher than expected | Each configured responsive width counts as a screenshot. | Count snapshots multiplied by widths, then remove widths that do not cover distinct behavior. |
Performance, reliability, and cost considerations
- Capture and rendering: Percy’s standard workflow captures a DOM and assets, then renders at multiple widths on Percy infrastructure. It does not require manually resizing the test browser for every width when the same DOM is valid across them.
- Responsive DOM capture: Re-capturing at each viewport adds viewport-specific setup and may require deferred uploads. Use it when structural changes make one DOM insufficient.
- Stable diffs: Capture the same application state on every run. Wait for content that affects layout, and avoid unnecessary volatility that creates visual noise.
- Usage: Budget one screenshot per snapshot-width combination. More widths can improve coverage but increase screenshot consumption and the number of diffs to review.
- Configuration: Keep common widths in project configuration and reserve snapshot-level settings for pages that need a different set. Verify precedence in the SDK you use.
Or skip the browser setup
If you need screenshots of deployed pages without wiring up a browser test, ScreenshotNeo is a website screenshot API and MCP server. One GET request returns an image or PDF. It is useful for capturing page output at specified viewport dimensions, but it does not replace Percy’s baseline comparison workflow for responsive visual regression testing.
Example using cURL; set the viewport options for the width you want to capture. See the ScreenshotNeo API documentation for request options and parameter names.
curl -G "https://api.screenshotneo.com/v1/shot" \
-d access_key=YOUR_API_KEY \
--data-urlencode url=https://stripe.com \
-d width=390 \
-d height=844 \
-o shot.webp
The same endpoint can be called from Python or Node.js:
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={
"access_key": "YOUR_API_KEY",
"url": "https://stripe.com",
"width": 390,
"height": 844,
},
timeout=90,
)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({
access_key: 'YOUR_API_KEY',
url: 'https://stripe.com',
width: '390',
height: '844',
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await Bun.write('shot.webp', new Uint8Array(await res.arrayBuffer()));
- Cookie and consent banners, newsletter popups, and chat widgets are handled before the shot: ScreenshotNeo accepts consent and removes more than 60 known consent platforms and these overlays, with each step configurable.
- Bot checks, blank pages, timeouts, and failed loads are not billed; response headers identify the page verdict and billing state. Cache hits are also not billed.
- An MCP server provides
take_screenshot,get_page_info, andcapture_pdftools for AI agents, including Claude, Cursor, and other MCP clients. - 1,000 screenshots per month are free with no card. Paid plans start at $5 for 3,000 screenshots.
Create a free ScreenshotNeo account for 1,000 screenshots a month, with no card required.
FAQ
Does Percy resize my test browser for every width?
In the standard workflow, Percy renders a captured DOM at configured widths on its infrastructure. Viewport-specific DOM capture is a separate workflow that resizes the test browser for each capture.
Are Percy’s default widths enough?
Not necessarily. The documented defaults are 375 and 1280 pixels. Add widths that exercise your application’s real breakpoints and structural states.
Does each width use screenshot quota?
Yes. Each rendered responsive width counts as a separate screenshot.
Can I use ScreenshotNeo to compare responsive baselines?
ScreenshotNeo captures image or PDF output from a URL. Percy’s documented workflow is the one described here for comparing responsive snapshots against visual baselines.


