ScreenshotNeo

BlogHow-to

How to Capture a Cypress Screenshot After Scrolling to an Element

Scroll an element into view, then capture the element or the visible viewport with Cypress. Learn the right command sequence, options, and fixes for common issues.

By the ScreenshotNeo team4 October 20265 min read

To capture a Cypress screenshot after scrolling to an element, call .scrollIntoView(), then make a fresh query for the element and call .screenshot(). Re-querying matters because Cypress warns that chaining further commands that depend on the yielded subject after .scrollIntoView() is unsafe.

cy.get('[data-testid="target"]')
  .scrollIntoView()

cy.get('[data-testid="target"]')
  .should('be.visible')
  .screenshot('target-after-scroll')

This captures the selected element. To capture the visible application viewport after scrolling, use cy.screenshot({ capture: 'viewport' }) instead. See the official Cypress documentation for scrollIntoView and screenshot.

1. Choose what the screenshot should contain

Capture Command What it saves
Element cy.get(selector).screenshot() The selected element, optionally with padding.
Current viewport cy.screenshot({ capture: 'viewport' }) The visible browser application area. Scroll first if the desired content is lower on the page.
Full page cy.screenshot({ capture: 'fullPage' }) A top-to-bottom image captured by scrolling and stitching.
Runner cy.screenshot({ capture: 'runner' }) The browser viewport together with the Cypress Command Log.

The capture setting does not apply to element screenshots. Full-page capture scrolls and stitches the page; fixed and sticky elements can therefore appear more than once. Cypress also coerces failure screenshots to runner capture.

2. Capture the element after scrolling

Use a selector that identifies the intended element reliably, such as a test ID. Scroll it into view, then query it again for the visibility assertion and screenshot.

describe('captures a section after scrolling', () => {
  it('saves the target element', () => {
    cy.visit('/long-page')

    cy.get('[data-testid="pricing-section"]')
      .scrollIntoView()

    cy.get('[data-testid="pricing-section"]')
      .should('be.visible')
      .screenshot('pricing-section-after-scroll')
  })
})

Replace /long-page and the test ID with values from your application. The element screenshot contains that element rather than everything visible around it. Use the padding option if you need some space around its edges.

3. Capture the viewport after scrolling

When the goal is to show the target in its surrounding page context, scroll to it and then capture the viewport:

cy.visit('/long-page')

cy.get('[data-testid="pricing-section"]')
  .scrollIntoView()

cy.get('[data-testid="pricing-section"]')
  .should('be.visible')

cy.screenshot('pricing-section-in-viewport', { capture: 'viewport' })

The visibility assertion is a separate command after scrolling. Cypress retries assertions until they pass or time out, but .scrollIntoView() itself fires once and is not retried.

4. Configure scrolling and screenshot output

Scroll options

.scrollIntoView() accepts options that control the scroll behavior:

  • duration: time spent scrolling.
  • easing: the easing function used for the scroll.
  • offset: adjusts the final position after the element enters view. Use it when a fixed header covers the target.
  • timeout: how long Cypress waits for the command.
cy.get('[data-testid="target"]')
  .scrollIntoView({
    duration: 500,
    easing: 'swing',
    offset: { top: -80, left: 0 },
    timeout: 10000
  })

cy.get('[data-testid="target"]')
  .should('be.visible')
  .screenshot('target-below-header')

Choose an offset based on the height and position of your fixed header. The offset changes the scroll position; it does not alter the element screenshot’s captured bounds.

Element screenshot options

Pass a name to choose a readable filename and use padding when the element needs breathing room in the image:

cy.get('[data-testid="target"]')
  .scrollIntoView()

cy.get('[data-testid="target"]')
  .should('be.visible')
  .screenshot('target-with-context', { padding: 16 })

Element capture is distinct from viewport and full-page capture. Cypress documents screenshot options such as capture mode, padding, and screenshot naming in its screenshot API reference.

5. Find and inspect the saved image

Manually captured screenshots are saved under cypress/screenshots by default, using names based on the spec and test; a supplied name can be used as well. The screenshots folder is configurable in Cypress configuration.

For automation after a screenshot is written, Cypress provides the after:screenshot Node event. It runs in Node, so Cypress cy commands are unavailable there. The onAfterScreenshot callback provides details such as the image path and dimensions. See the official after:screenshot API.

6. Troubleshooting

Symptom Likely cause Fix
Element is clipped or hidden behind a header The element was scrolled into view, but a fixed header overlaps it. Adjust offset, then assert visibility and inspect the saved image.
Chain fails or acts on an unexpected element after scrolling A subject-dependent command was chained from .scrollIntoView(). Issue a fresh cy.get(selector) before the assertion or screenshot.
Screenshot is taken before the page is ready Scrolling does not guarantee that asynchronous content has rendered. Wait for an application-specific readiness condition, such as the target text or a loaded state, before scrolling and capturing.
Viewport image does not include the entire target The element is taller than the viewport, or the requested scope was element/full page rather than viewport. Use element capture for the target alone, or full-page capture for a page-length image; check whether stitching duplicates sticky content.
Screenshot differs from the state at command invocation Screenshot capture is asynchronous, so the application may change before the image is taken. Wait for animations or changing content to settle, then capture. Avoid triggering state changes during capture.
Scroll position is hard to diagnose from Cypress snapshots Cypress snapshots do not accurately show scroll positions. Inspect the actual run behavior or its video while debugging.

7. Performance, reliability, and cost

Cypress describes screenshot capture as asynchronous and says the action takes around 100 ms; this is documentation guidance, not a guaranteed time or an independent benchmark. Actual capture time depends on the page and environment. Keep screenshots focused on the needed element or viewport when full-page stitching is unnecessary.

For reliable results, wait for a meaningful application state rather than relying on a fixed delay alone. Remember that scrolling occurs once, while assertions after it are retried. When full-page output is required, account for repeated fixed and sticky elements. Cypress is software run within your test setup; the cited API documentation does not state a per-screenshot service charge.

Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server. For a URL-based page screenshot, make one GET request; it returns PNG, JPEG, WebP, or PDF. Read the ScreenshotNeo API documentation for available parameters.

curl -G "https://api.screenshotneo.com/v1/shot" \
  -d access_key=YOUR_API_KEY \
  --data-urlencode url=https://stripe.com \
  -o shot.webp
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)
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 request failed: ${res.status}`);
await Bun.write('shot.webp', res);
  • 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 cost nothing; response headers report the page verdict and billing status.
  • An MCP server lets AI agents use take_screenshot, get_page_info, and capture_pdf.
  • 1,000 screenshots a month are free with no card; paid plans start at $5 for 3,000 screenshots.

Create a free ScreenshotNeo account to get 1,000 screenshots a month with no card.

FAQ

Does an element screenshot include the rest of the viewport?

No. It captures the selected element. Use cy.screenshot({ capture: 'viewport' }) for the visible application area.

Can I screenshot an element inside a scrollable container?

Yes, provided the element is reachable and visible. Scroll the element into view, then re-query it for the assertion and screenshot.

Can I use the element screenshot API with full-page capture?

The element screenshot command captures the element; the capture scope options apply to cy.screenshot(). Choose the command that matches the output you need.