How to capture responsive website screenshots with Cypress
Set Cypress viewport sizes, verify responsive behavior, and capture repeatable screenshots. Includes full-page options, troubleshooting, and an API alternative.
To capture responsive website screenshots with Cypress, set the viewport explicitly with cy.viewport(), visit the page, wait for the UI state you need, assert the expected layout, and call cy.screenshot(). Cypress defaults to a full-page capture; use { capture: 'viewport' } when you want an image of only the current viewport.
A screenshot records what the browser rendered. It does not check whether the result is correct or compare it with an earlier image. Add assertions for responsive behavior, and use a visual testing integration if you need image diffs. See the official viewport API, screenshot API, and visual testing guide.
1. Set up a responsive screenshot test
This example runs the same route at three illustrative widths. Replace the dimensions and selectors with values that match your app’s breakpoints and UI.
describe('responsive page screenshots', () => {
const sizes = [
{ name: 'mobile', width: 390, height: 844 },
{ name: 'tablet', width: 768, height: 1024 },
{ name: 'desktop', width: 1280, height: 900 },
]
sizes.forEach(({ name, width, height }) => {
it(`renders the home page at ${name} size`, () => {
cy.viewport(width, height)
cy.visit('/')
// Wait for a meaningful, app-specific ready state.
cy.get('[data-testid="site-nav"]').should('be.visible')
if (width < 600) {
cy.get('[data-testid="menu-button"]').should('be.visible')
cy.get('[data-testid="desktop-nav"]').should('not.be.visible')
} else {
cy.get('[data-testid="desktop-nav"]').should('be.visible')
}
cy.screenshot(`home-${name}`, { capture: 'viewport' })
})
})
})
The selectors and breakpoint in this example are illustrative; Cypress does not prescribe responsive breakpoints. Use the breakpoints and behaviors your CSS actually implements. Testing immediately on both sides of an important breakpoint can catch off-by-one layout bugs.
2. Choose viewport dimensions that cover real breakpoints
cy.viewport(width, height) sets the application viewport in CSS pixels. Cypress documents a default viewport of 1000×660 before a test changes it, and restores its default size between tests. Set dimensions explicitly in each test so screenshot size does not depend on a previous test.
Choose a compact set of widths based on actual layout transitions: for example, one narrow layout, one intermediate layout, and one wide layout. If navigation changes at a CSS breakpoint of 768 pixels, consider checking 767 and 768 pixels, or the exact adjacent values relevant to your media query. Include heights that expose the vertical behavior you care about.
You can also use documented device-size presets and specify portrait or landscape orientation:
cy.viewport('iphone-6', 'landscape')
cy.visit('/')
cy.screenshot('home-landscape', { capture: 'viewport' })
A named preset is a convenient viewport size, not complete phone emulation. Cypress documents that devicePixelRatio is not simulated. A viewport screenshot does not establish how touch input, physical-device rendering, or browser chrome behaves on a real device.
3. Configure a suite-wide default
For a suite with a shared starting size, set viewportWidth and viewportHeight in Cypress configuration. Use cy.viewport() inside tests for other sizes; current Cypress documentation says changing those two values with Cypress.config() does not change the running test’s viewport.
// cypress.config.js
const { defineConfig } = require('cypress')
module.exports = defineConfig({
e2e: {
viewportWidth: 1280,
viewportHeight: 900,
screenshotsFolder: 'cypress/screenshots',
},
})
The default screenshot directory is cypress/screenshots; screenshotsFolder changes it. In cypress run, Cypress captures screenshots of test failures by default. Cypress also clears asset folders before a run unless trashAssetsBeforeRuns is disabled, so do not assume screenshots from an earlier run remain in the folder.
4. Pick the right screenshot capture mode
| Option | What it captures | When to use it |
|---|---|---|
capture: 'viewport' |
The currently visible viewport | Responsive comparisons at explicit viewport sizes |
capture: 'fullPage' |
The page from top to bottom by scrolling and stitching; this is the default | Reviewing the whole page in one image |
capture: 'runner' |
The Cypress runner, including its Command Log | Debugging a test; failure screenshots are coerced to runner capture behavior |
For responsive testing, viewport capture usually gives the clearest comparable artifacts: every image represents the same kind of visible area at the dimensions named by the test. Full-page capture is useful for document coverage, but fixed or sticky elements can appear more than once because Cypress scrolls and stitches the page.
// Only the current visible area
cy.screenshot('checkout-mobile', { capture: 'viewport' })
// Full page; this is also the default
cy.screenshot('article-full-page', { capture: 'fullPage' })
// Crop a region of the capture
cy.screenshot('hero-crop', {
capture: 'viewport',
clip: { x: 0, y: 0, width: 390, height: 320 },
})
Other useful screenshot controls include blackout, which accepts selectors for elements to hide in application captures, and disableTimersAndAnimations, which defaults to true to reduce animation-related variation. Screenshot options also include callbacks before and after capture for controlled DOM changes and saved-image metadata. Check the screenshot command reference for the exact option types and behavior in your Cypress version.
cy.screenshot('account-page', {
capture: 'viewport',
blackout: ['[data-testid="private-account-number"]'],
})
5. Make the capture stable and meaningful
- Set the viewport before visiting. This ensures responsive CSS is evaluated at the intended dimensions as the page loads.
- Wait for application state. Prefer a visible element, network alias, or app-specific ready signal over a fixed delay. A screenshot command is asynchronous, so the page may change between the command and when the image is taken.
- Assert layout behavior. Check the menu, columns, visibility, or wrapping that should change at the viewport. The assertion explains what the test considers correct; the screenshot is supporting evidence.
- Control dynamic inputs. Use predictable test data and avoid time-dependent content where possible. The default timer and animation handling does not make changing backend data deterministic.
- Choose capture mode deliberately. Use viewport capture for a responsive state and full-page capture when the complete document is the subject.
it('shows the compact product grid on a narrow viewport', () => {
cy.viewport(375, 812)
cy.intercept('GET', '/api/products', { fixture: 'products.json' }).as('products')
cy.visit('/products')
cy.wait('@products')
cy.get('[data-testid="product-card"]').should('have.length.at.least', 1)
cy.get('[data-testid="product-grid"]').should('have.css', 'grid-template-columns')
cy.screenshot('products-narrow', { capture: 'viewport' })
})
The CSS assertion above confirms that a grid style exists, but a stronger app-specific test should check the actual expected column count or user-visible behavior. Avoid brittle assertions on computed style when a stable semantic condition is available.
6. Run the tests and find the artifacts
Run the suite with your project’s normal Cypress command, such as npx cypress run. Successful explicit screenshots and default failure screenshots are written under the configured screenshots folder. In interactive cypress open, do not expect automatic failure screenshots; that behavior applies to cypress run.
The Cypress app may scale the viewport preview to fit your display. That visual scaling does not change the application viewport dimensions set by cy.viewport().
7. Capture a responsive screenshot with the ScreenshotNeo API
If the goal is a screenshot artifact rather than a Cypress assertion inside an end-to-end suite, ScreenshotNeo can capture a URL with one API request. It is a website screenshot API and MCP server from Yorker Media. The examples below request a WebP screenshot. See the ScreenshotNeo API documentation for parameters, response headers, and formats.
cURL
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
Python
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)
Node.js
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 import('node:fs/promises').then(({ writeFile }) =>
writeFile('shot.webp', Buffer.from(await res.arrayBuffer()))
)
For responsive captures, make one request per viewport width and height you want to inspect. ScreenshotNeo also supports full-page capture, device presets and arbitrary viewports, retina scale, dark mode, element capture by CSS selector, image resizing, custom CSS and JavaScript, click-before-capture, selector hiding, waits for a selector, delay or network idle, request/resource blocking, custom headers, cookies, user agent and Authorization, timezone, geolocation, transparent backgrounds, caching with a chosen TTL, signed links for public image tags, asynchronous jobs with signed webhooks, bulk capture of up to 100 URLs per call, a usage API, and an OpenAPI spec. It can return PNG, JPEG, WebP, or PDF; PDF options include paper size, margins, landscape and page ranges. Its parameter names also work with those used by other screenshot APIs to ease migration. Refer to the docs for exact parameter names and supported values.
Or skip the browser setup
ScreenshotNeo accepts cookie or consent banners like a visitor and removes 60+ known consent platforms, newsletter popups, and chat widgets before the shot; each step can be turned off. Bot checks/CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing status. An MCP server gives AI agents tools to take screenshots, get page information, and capture PDFs. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 shots. Every feature is on every plan.
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
Sign up for 1,000 free screenshots a month, with no card required.
8. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Screenshot has the wrong dimensions | The viewport was not set in that test, was set after visiting, or the artifact is a full-page capture. | Call cy.viewport(width, height) before cy.visit(); use capture: 'viewport' for the visible viewport image. |
| Mobile navigation is missing or desktop navigation remains | The test width does not cross the app’s actual breakpoint, or the page has not reached its ready state. | Check the CSS breakpoint, test values on either side, and wait for a meaningful UI state before asserting and capturing. |
| Screenshot intermittently shows loading content | The page is still changing when the asynchronous screenshot is taken. | Wait on an app signal or network alias, stabilize test data, and control animations or clocks if the application requires it. |
| Sticky header appears multiple times | Full-page capture scrolls and stitches the document. | Use viewport capture for one responsive screen, or account for repeated fixed/sticky content when reviewing a full-page image. |
| Failure screenshot includes Cypress controls | Failure screenshots in run mode use runner capture behavior. | Distinguish runner diagnostics from explicit application screenshots; use explicit screenshots for the intended responsive artifact. |
| Old screenshot files disappear after a run | Cypress clears asset folders before runs by default. | Archive or copy artifacts after each run, or configure trashAssetsBeforeRuns when retaining them is appropriate. |
| Viewport screenshot does not match a physical phone | A viewport is not full device emulation; Cypress does not simulate devicePixelRatio. |
Use the viewport test for responsive CSS behavior and verify device-specific behavior in an appropriate real-device or browser testing setup. |
| Images differ but the Cypress test passes | Cypress captures screenshots but does not compare them. | Add a visual testing integration if you need baselines, image diffs, or review workflows. |
9. Performance, reliability, and cost considerations
Each viewport case loads and exercises the page, so keep the matrix focused on real layout transitions rather than taking many nearly identical screenshots. Parallelizing independent tests can help suite throughput, while deterministic fixtures and meaningful waits reduce flaky captures. Full-page screenshots involve scrolling and stitching and may take longer or produce less straightforward results for sticky layouts.
Cypress screenshot capture itself does not provide image comparison. Cypress documents integrations including Percy, LambdaTest SmartUI, Sauce Labs Visual, Chromatic, and Happo; compare them based on browser coverage, how and where rendering and diffs run, baseline approval, CI integration, and hosted review needs. The dossier does not establish current pricing or program terms for those services, so check their own current documentation before choosing one.
For API-based captures, ScreenshotNeo pricing is Free: 1,000 shots/month; Starter: $5 for 3,000; Growth: $15 for 15,000; Pro: $39 for 60,000; Scale: $99 for 250,000; Business: $249 for 1,000,000. Yearly billing gives two months free. Only clean shots are billed: bot checks/CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing. This is a separate capture workflow from Cypress tests; use Cypress when the screenshot belongs inside an asserted app test, and an API when a request-driven screenshot artifact fits the task.
10. FAQ
Does a Cypress screenshot test prove that a layout is responsive?
No. The viewport controls the tested size, but assertions must check the expected behavior at that size. A screenshot records the rendered state.
Can I use Cypress screenshots for visual regression testing?
You can save captures, but Cypress does not compare them to a baseline by itself. Add a visual testing integration for diffs and review.
Should I capture the viewport or the full page?
Use viewport capture to compare a single responsive screen. Use full-page capture to inspect the whole document, keeping in mind that sticky elements can repeat during stitching.
Does ScreenshotNeo replace Cypress?
No. Cypress runs browser tests and assertions in your app workflow. ScreenshotNeo provides request-based screenshots and related capture tools when you need an image from a URL without setting up that browser test.


