How to Scroll an Element into View in Cypress
Use Cypress `.scrollIntoView()` to reveal a specific element, choose the right command for scrollable containers, and handle fixed headers and action scrolling.
To bring a specific element into view in Cypress, query it and chain .scrollIntoView():
cy.get('#footer').scrollIntoView()
cy.get('#footer').should('be.visible')
.scrollIntoView() is an element command: it needs a DOM element as its subject. It yields that subject, but Cypress marks it unsafe to chain later commands that rely on it. Query the element again before asserting or interacting with it. See the official Cypress API reference.
1. Scroll a specific element into view
Use a selector that identifies the element you want to reveal. The command scrolls the relevant page or scrollable ancestor so the target can be brought into view.
it('reveals the footer', () => {
cy.visit('/long-page')
cy.get('footer').scrollIntoView()
cy.get('footer').should('be.visible')
})
For a stable test, prefer an application selector intended for testing when available:
cy.get('[data-cy="checkout-submit"]').scrollIntoView()
cy.get('[data-cy="checkout-submit"]').should('be.visible')
The query itself does not scroll. Cypress automatically scrolls before actionable commands such as clicking, but an explicit .scrollIntoView() is useful when the test needs to assert visibility, inspect the revealed content, or make the scroll step clear.
2. Choose between scrollIntoView and scrollTo
| Command | Use it when | Example |
|---|---|---|
.scrollIntoView() |
You have a particular element and want to reveal it. | cy.get('#footer').scrollIntoView() |
.scrollTo() |
You want the window or a scrollable container at a named position or coordinate. | cy.scrollTo('bottom') |
Use scrollTo for a container’s position, for example:
cy.get('.sidebar').scrollTo('bottom')
cy.get('.sidebar').scrollTo(0, 500)
cy.get('.sidebar').scrollTo('50%', '100%')
cy.scrollTo() supports named positions such as top, center, and bottom, along with pixel and percentage coordinates. When chained to an element, that element must be scrollable. If it may or may not be scrollable, ensureScrollable: false suppresses the scrollability error:
cy.get('.maybe-scrollable').scrollTo('bottom', { ensureScrollable: false })
See the official Cypress scrollTo reference for the command’s complete options.
3. Handle fixed and sticky headers
A target can technically be in the viewport but covered by a fixed or sticky header. Set an offset to leave room when aligning the target:
cy.get('#main-content').scrollIntoView({
offset: { top: 150, left: 0 }
})
cy.get('#main-content').should('be.visible')
The offset is measured in pixels. Adjust it to your layout; a large offset can leave the target farther from the desired position. Cypress action commands also apply their own scrolling and may nudge an element clear of a fixed or sticky covering element.
4. Configure scrollIntoView
The documented .scrollIntoView() options are:
| Option | Default | Purpose |
|---|---|---|
duration |
0 |
Scroll animation duration in milliseconds. |
easing |
swing |
Animation easing when a duration is used. |
log |
true |
Show the command in the Cypress Command Log. |
offset |
{ top: 0, left: 0 } |
Offset the final alignment, useful around fixed headers. |
timeout |
defaultCommandTimeout |
Time allowed for the command to complete. |
cy.get('#results').scrollIntoView({
duration: 300,
easing: 'linear',
offset: { top: 80, left: 0 },
log: true,
timeout: 10000
})
Use a nonzero duration when the movement itself matters to the test or is useful for debugging. For ordinary visibility checks, the default avoids adding animation time.
5. Control automatic scrolling for actions
Commands such as .click() and .type() automatically scroll actionable elements into view. Configure alignment with scrollBehavior in Cypress configuration or for an individual action. Supported values include top, bottom, start, end, center, nearest, per-axis { block, inline } values, and false to skip action scrolling.
// cypress.config.js
const { defineConfig } = require('cypress')
module.exports = defineConfig({
e2e: {
scrollBehavior: 'center'
}
})
// Per-action setting
cy.get('[data-cy="save"]').click({ scrollBehavior: 'center' })
Use false only when the test specifically needs to exercise an element without Cypress scrolling it into place:
cy.get('[data-cy="save"]').click({ scrollBehavior: false })
{ force: true } is different: forced actions skip scrolling, visibility checks, and other actionability checks. That can conceal a genuine problem where a user cannot reach or interact with the element, so prefer normal actionability unless bypassing those checks is the test’s explicit purpose. Check the Cypress interaction guide and your installed Cypress version for version-sensitive behavior. Per-axis scrollBehavior and start/end alignments were added in Cypress 15.20.0; the current guide describes Cypress 16’s visibility algorithm as delegating to the browser’s Element.checkVisibility() API.
6. Assertions, retries, and debugging
Scroll commands run once; an assertion chained after the command is retried until it passes or times out. Re-querying makes the subject relationship explicit:
cy.get('#account-details').scrollIntoView()
cy.get('#account-details').should('be.visible').and('contain', 'Account details')
A visibility assertion confirms Cypress considers the element visible. It does not guarantee that every pixel is unobstructed or that the element is in the exact screen position your test expects. If placement matters, assert the relevant behavior or inspect the layout separately.
Cypress command snapshots do not accurately represent scroll positions. To watch movement, pause through the command sequence or inspect the test-run video. The scroll command’s own snapshot may not show where the page ended up.
7. Troubleshoot common failures
| Symptom | Likely cause | What to do |
|---|---|---|
scrollIntoView is not a function or a command error about the subject |
The command is being used as cy.scrollIntoView(selector), or the preceding chain did not yield a DOM element. |
Start with a query such as cy.get(selector).scrollIntoView(). It is an element command, not a top-level Cypress command. |
| Element not found | The selector is wrong, the page has not rendered it, or the element is conditional. | Check the selector and app state. Query after the page state that creates the element, and use a meaningful assertion rather than an arbitrary delay. |
| Scroll runs but the follow-up command behaves unexpectedly | A later command relies on the subject yielded by .scrollIntoView(), which Cypress marks unsafe for further subject-dependent chaining. |
Issue a fresh cy.get() or scoped query before the next assertion or action. |
| Target is hidden under the header | A fixed or sticky header covers the aligned target. | Set offset: { top: ... } on .scrollIntoView(), or tune the action’s scrollBehavior. |
scrollTo reports that an element is not scrollable |
The selected container has no scrollable overflow, or the wrong element was selected. | Target the actual scrolling container. If scrollability is legitimately optional, use ensureScrollable: false. |
| Forced click succeeds while a normal click fails | The element may be covered, hidden, disabled, or otherwise not actionable. | Inspect the page and fix the real visibility or layout issue when user interaction should work. Avoid using force merely to silence the failure. |
| Command Log snapshot appears not to move | Snapshots do not show accurate scroll positions. | Pause the run or inspect its video instead of treating the snapshot as evidence of scroll position. |
8. Performance and reliability notes
- Keep the default zero duration for routine test setup; animations add time without improving a visibility assertion.
- Prefer selectors that remain stable across styling changes, such as dedicated test attributes when your app provides them.
- Assert the state you need after scrolling. Do not use scroll movement alone as proof that asynchronous content has loaded.
- Do not add fixed waits to compensate for a missing element. First identify which application state or rendering condition should make it available.
- Use normal actionability checks for realistic clicks. A forced action can make a test pass while the user-facing interaction remains broken.
- For large test suites, keep scroll assertions focused on behavior that matters; repeated animation and redundant checks increase run time.
Cypress documents no special per-scroll charge or published scroll performance benchmark in the cited references. Runtime cost depends on the test run and any configured animation; the default duration is zero.
9. Or skip the browser setup
If the task is to capture a page image rather than test an interactive scroll, ScreenshotNeo provides a website screenshot API and MCP server. One GET request returns an image or PDF. See the API documentation.
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,
)
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}`);
ScreenshotNeo removes cookie banners, popups, and chat widgets before the shot. Bot checks, blank pages, and failed loads are never billed. Its MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000. Sign up for free.
10. FAQ
Does scrollIntoView scroll the whole page or a nested container?
It brings the selected element into view through the relevant scrolling context. If you need to control a specific container’s position, select that container and use .scrollTo().
Should I use scrollIntoView before every click?
No. Cypress automatically scrolls before actionable commands. Add an explicit scroll when the test needs to assert or inspect the revealed element, or when making that step explicit improves the test.
Why does Cypress say chaining after scrollIntoView is unsafe?
The command yields its original subject, but Cypress warns against relying on that subject in subsequent commands. A fresh query gives the next assertion or action a current subject.
Which Cypress version supports start and end scroll alignment?
The command reference identifies per-axis scrollBehavior and start/end action alignments as available from Cypress 15.20.0. Confirm the version installed in your project.


