ScreenshotNeo

BlogHow-to

Why Cypress Full-Page Screenshots Are Not Working and How to Fix Them

Fix clipped, blurry, repeated, missing, or incorrect Cypress full-page screenshots with the right capture mode, layout checks, timing, and CI settings.

By the ScreenshotNeo team1 October 20267 min read

Cypress full-page screenshots usually fail for one of five reasons: the test captured the viewport instead of the full page, the page has sticky elements that repeat during stitching, the layout has no document-level scroll, the browser window is scaling the app, or the screenshot was taken before the UI was ready. Identify the symptom first, then apply the matching fix.

For a manual application screenshot, use cy.screenshot('name', { capture: 'fullPage' }). Cypress scrolls the application from top to bottom, captures each position, and stitches the images together. The runner mode includes the Cypress browser UI, while viewport captures only the current application viewport. See the Cypress screenshot API.

1. Confirm which screenshot Cypress actually took

Mode What is captured Typical use
viewport The visible application viewport A single screen or component state
fullPage The application from top to bottom, using scroll-and-stitch Long pages and documentation
runner The complete browser viewport, including the Cypress Command Log Debugging the test runner

Use an explicit manual capture when you need an application-only full-page image:

describe('full page capture', () => {
  it('captures the ready page', () => {
    cy.visit('/page')
    cy.get('[data-cy=page-ready]').should('be.visible')
    cy.screenshot('page-full', { capture: 'fullPage' })
  })
})

The assertion before the screenshot is retryable; cy.screenshot() itself does not retry chained assertions. Replace the readiness selector with one that represents your application.

Automatic failure screenshots are different

During cypress run, Cypress automatically takes failure screenshots when screenshotOnRunFailure is enabled (it defaults to true). Cypress coerces those failure captures to runner, so they can include the Command Log and do not honor an application-only fullPage request. No automatic failure screenshot is taken in cypress open; add a manual command there. Sources: Screenshot API and screenshots and videos guide.

2. Fix repeated sticky and fixed elements

Full-page capture scrolls through the page. A position: fixed or position: sticky header, cookie bar, or floating button can therefore appear in multiple stitched segments. Temporarily changing the element to absolute positioning is the workaround shown in Cypress documentation:

cy.get('.sticky-header').invoke('css', 'position', 'absolute')
cy.screenshot('page-full', { capture: 'fullPage' })
cy.get('.sticky-header').invoke('css', 'position', null)

Use an application-specific selector and restore the exact prior style. If the test can fail between the mutation and restoration, put cleanup in a strategy appropriate for your suite so later tests do not inherit the changed layout. Changing positioning can itself alter page flow, so verify that the header is not covering content.

3. Diagnose clipped or unexpectedly short images

Check which element actually scrolls

A full-page screenshot assumes a document that can be scrolled from top to bottom. Inspect the rendered document and the element with the scroll bar:

cy.window().then((win) => {
  cy.document().then((doc) => {
    cy.log(`document height: ${doc.documentElement.scrollHeight}`)
    cy.log(`body height: ${doc.body.scrollHeight}`)
    cy.log(`viewport: ${win.innerWidth}x${win.innerHeight}`)
  })
})

Layouts that set html and body to height: 100vh and width: 100vw, hide overflow, or put all content inside an inner scrolling panel may not produce a conventional document-length image. In that case, capture the scrolling element as an element-specific workflow or adjust the test fixture so the document can scroll. Cypress issue #25516 describes a version-specific clipping report involving a CSS-grid interface; treat it as a reproduction lead, not proof that every grid layout fails.

Verify the saved dimensions

Do not infer success from the configured viewport alone. Check the actual PNG dimensions and compare them with the document’s scroll height. A viewport-sized file usually means the capture mode was viewport, the page did not scroll, or the capture was made before content expanded.

4. Fix blurry or smaller-than-expected screenshots

A large viewportWidth and viewportHeight increase the app’s internal viewport, but the Cypress Test Runner can scale its app iframe down to fit the available browser window. The resulting image can look smaller or less detailed than expected. Cypress’s high-resolution guidance recommends increasing the total browser window size; in open mode, enlarge the window or narrow the Command Log. In CI, inspect the browser launch size and the available display/X server dimensions. Historical CI examples such as 1280×720 are examples, not universal current limits. See Cypress high-resolution guidance.

// cypress.config.js
const { defineConfig } = require('cypress')

module.exports = defineConfig({
  viewportWidth: 1440,
  viewportHeight: 900,
  e2e: {
    setupNodeEvents(on, config) {
      return config
    }
  }
})

Run the same test in the target CI display environment and inspect the output dimensions. Increasing CSS viewport values cannot compensate for a browser or display surface that is being scaled down.

5. Wait for the correct state without arbitrary sleeps

Screenshot capture is asynchronous and takes roughly 100 ms according to Cypress documentation. During that interval, timers, animations, lazy loading, or hydration can change the page. Prefer a deterministic readiness signal:

cy.visit('/dashboard')
cy.get('[data-cy=dashboard-ready]').should('be.visible')
cy.get('[data-cy=results]').should('have.length.greaterThan', 0)
cy.screenshot('dashboard-full', {
  capture: 'fullPage',
  disableTimersAndAnimations: true
})

disableTimersAndAnimations defaults to true. If the expected visual state depends on a running animation or timer, inspect that option and the app’s animation behavior before adding waits. Cypress also documents a timer-patch bypass: scripts that retain references to unpatched timer functions can prevent Cypress from pausing those tasks during capture. Scope investigation to cases showing that error or unpredictable timer behavior. For an SSR React 18+ hydration case described in the error reference, place the data-cy-bootstrap marker first in <head>, or ensure other scripts use defer or async. See Cypress error messages.

6. Make screenshots available after the run

The default screenshot directory is cypress/screenshots. Cypress clears asset folders before cypress run because trashAssetsBeforeRuns defaults to true. Preserve prior files when needed:

// cypress.config.js
const { defineConfig } = require('cypress')

module.exports = defineConfig({
  trashAssetsBeforeRuns: false,
  screenshotsFolder: 'cypress/screenshots'
})

In CI, publish that directory through your provider’s artifact mechanism or view captures in Cypress Cloud. Always read the terminal output for the exact path; a missing file is often a folder or cleanup issue rather than a capture failure. Configuration details are in the Cypress configuration reference.

7. A complete diagnostic checklist

  1. Confirm the command uses capture: 'fullPage', not viewport.
  2. Check whether the image is a failure screenshot; automatic failures use runner.
  3. Identify the real scrolling element and inspect scrollHeight.
  4. Temporarily neutralize sticky and fixed elements.
  5. Wait on a stable selector, data load, and image readiness signal.
  6. Compare configured viewport, browser window, display size, and saved dimensions.
  7. Inspect disableTimersAndAnimations when animations or timers matter.
  8. Check screenshotsFolder, cleanup settings, and CI artifact upload.

8. Common errors and fixes

Symptom Likely cause Fix
Only the visible screen is saved Viewport capture or no document scroll Use capture: 'fullPage'; inspect overflow and the scrolling element.
Header or chat button repeats Scroll-and-stitch captures fixed or sticky UI repeatedly Temporarily change positioning and restore it after capture.
Image is blurry Runner iframe or CI display is scaled Increase browser window/display size and compare actual file dimensions.
Failure image contains Cypress controls Automatic failure screenshots are coerced to runner mode Add an explicit application screenshot at the state you need.
Content is missing Capture occurred before hydration, lazy loading, or data rendering Assert a stable ready selector and required content before capture.
Screenshot differs from normal animation Timers and CSS animations are disabled during capture Review disableTimersAndAnimations and make the target state deterministic.
File disappears between runs trashAssetsBeforeRuns cleared the folder Set it to false or export artifacts after each run.

9. Performance, reliability, and visual comparison

Full-page capture costs more time than a viewport capture because Cypress scrolls and stitches multiple images. Keep the page deterministic, avoid unnecessary network requests, and capture only the pages needed for the test. A stable readiness selector is usually more reliable than a fixed delay. If the goal is visual regression, remember that cy.screenshot() creates an image but does not compare it; Cypress’s visual testing guide lists integration options for comparison workflows.

10. Or skip the browser setup

ScreenshotNeo provides a website screenshot API and MCP server. One GET request returns PNG, JPEG, WebP, or PDF, so you do not need to manage Cypress browser windows for a standalone capture. The API accepts options for full-page capture, element selectors, custom viewport and device presets, retina scale, waits, custom CSS and JavaScript, cookies and headers, blocking, caching, and more. Read the ScreenshotNeo API documentation.

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,
)
r.raise_for_status()
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}`);
if (!res.ok) throw new Error(`Screenshot failed: ${res.status}`);
const buffer = Buffer.from(await res.arrayBuffer());
require('fs').writeFileSync('shot.webp', buffer);

ScreenshotNeo accepts cookie and consent banners as a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each step can be turned off. Bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and the response reports the result through X-Page-Verdict and X-Billed headers. Its MCP server exposes 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 available on every plan, and yearly billing gives two months free. Create a free ScreenshotNeo account.

FAQ

Does cy.screenshot() wait for network idle?

No. Create an application-specific readiness assertion, such as a visible marker and rendered result count, before calling the screenshot command.

Why is my full-page image wider or narrower than the browser?

The stitched image follows the application viewport and document layout. Browser chrome, runner scaling, scrollbar behavior, and responsive breakpoints can change the resulting dimensions.

Can Cypress capture a page inside an iframe?

The full-page mode captures the application document. For iframe content, interact with the frame and use a capture strategy that matches where the scrollable document actually lives.

Should I use video instead of screenshots for failures?

Video can show the complete sequence when asynchronous state changes before capture. It is available during cypress run when enabled and is not recorded in cypress open.