Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

Use Selenium 4’s wheel actions when you need browser-like scrolling, and use JavaScript when you need direct DOM control. For a known element, call ActionChains(driver).scroll_to_element(element).perform(). For a fixed distance, use scroll_by_amount(0, pixels). For a particular scroll origin, use scroll_from_origin. JavaScript’s scrollIntoView() is a practical alternative when your test needs page-context scrolling rather than a wheel gesture.

The examples below use Python because its Selenium API documents all three wheel patterns clearly. The wheel reference is labeled Chromium Only, so verify support for your exact browser and language binding before depending on it outside Chromium.

Choose the scrolling method by intent

What you need Recommended API What it does
Reveal a known element scroll_to_element(element) Uses wheel input and positions the page with the element’s bottom at the bottom of the screen.
Move a defined distance scroll_by_amount(delta_x, delta_y) Scrolls from the upper-left of the viewport; negative values move left or up.
Scroll from an element or viewport point scroll_from_origin(origin, delta_x, delta_y) Applies deltas relative to an element (optionally offset) or a viewport coordinate.
Invoke page scrolling behavior execute_script() Runs synchronous JavaScript in the current window or frame, such as scrollIntoView().

Selenium’s wheel actions were introduced in Selenium 4.2. Actions do not automatically scroll a target into view before another action, so explicitly reveal an off-screen element before clicking, typing, or reading it.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Prerequisites and a reliable starting point

  • Install Selenium 4 for Python: python -m pip install -U selenium.
  • Use a WebDriver for the browser under test and navigate to the page before locating the target.
  • Wait for the element you intend to scroll to be present and, when necessary, visible. Scrolling does not repair a stale reference or a page that has not finished rendering.
  • For wheel methods, check the current Selenium documentation for your browser. The official wheel guide is explicitly marked Chromium Only.
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

options = webdriver.ChromeOptions()
driver = webdriver.Chrome(options=options)
wait = WebDriverWait(driver, 20)

driver.get("https://example.com")
target = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, "footer")))

Scroll a page to a specific element

Wheel action: the browser-like approach

from selenium.webdriver.common.action_chains import ActionChains

ActionChains(driver).scroll_to_element(target).perform()

This is the documented common case for wheel input. The element is brought into view, with its bottom positioned at the bottom of the screen. It is a scroll operation, not a click: follow it with your intended interaction.

ActionChains(driver).scroll_to_element(target).perform()
wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, "footer a"))).click()

If a sticky header covers the element after scrolling, use JavaScript with an offset or scroll again after measuring the layout. Do not assume that an element being in the viewport means every pixel of it is unobstructed.

JavaScript: direct DOM scrolling

driver.execute_script(
    "arguments[0].scrollIntoView({block: 'center', inline: 'nearest'});",
    target,
)

execute_script runs synchronously in the current window or frame. The script above centers the element vertically, which is often more useful than aligning it to an edge when a fixed navigation bar is present. The simpler documented pattern is:

driver.execute_script("arguments[0].scrollIntoView();", target)

Scroll down or up by a fixed amount

Wheel distance

# Down 500 CSS pixels
ActionChains(driver).scroll_by_amount(0, 500).perform()

# Up 300 CSS pixels
ActionChains(driver).scroll_by_amount(0, -300).perform()

# Horizontal movement is the first argument
ActionChains(driver).scroll_by_amount(250, 0).perform()

delta_x and delta_y are measured from the upper-left of the viewport. A negative vertical value moves upward. A single large movement can skip lazy-loading triggers or intermediate content; several smaller movements are safer when the page loads content as it becomes visible.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

JavaScript distance and absolute positions

# Relative movement
 driver.execute_script("window.scrollBy(0, arguments[0]);", 500)

# Absolute vertical position
 driver.execute_script("window.scrollTo(0, arguments[0]);", 1200)

Remove the leading space before driver if you paste this into a Python file. JavaScript scrolling is useful when you need an exact document coordinate, but it does not emulate a physical wheel gesture.

Scroll relative to an element or a viewport point

Element origin

An element origin lets you move relative to a scrollable area or a meaningful control. If the origin is outside the viewport, Selenium first brings it into view. An offset that lands outside the viewport raises an exception.

from selenium.webdriver.common.action_chains import ActionChains

origin = ActionChains(driver).scroll_from_origin

# Scroll 200 pixels down from the element's center
from selenium.webdriver.common.actions.wheel_input import ScrollOrigin
scroll_origin = ScrollOrigin.from_element(target)
ActionChains(driver).scroll_from_origin(scroll_origin, 0, 200).perform()

For an offset origin, construct the origin with the element and its horizontal and vertical offsets according to your installed Python binding’s API. Keep the offset inside the viewport after Selenium reveals the element.

Viewport origin

from selenium.webdriver.common.actions.wheel_input import ScrollOrigin

# Start at a viewport coordinate, then move down
viewport_origin = ScrollOrigin.from_viewport(400, 300)
ActionChains(driver).scroll_from_origin(viewport_origin, 0, 250).perform()

Origin-based scrolling is particularly useful when a page contains nested scroll regions. The origin identifies where the wheel input starts; it does not guarantee that an arbitrary container will scroll unless that container is the active scrollable context.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Scroll a nested, scrollable container

Many applications keep the document fixed while a panel, modal, table, or chat history scrolls. First locate the container, then use JavaScript to set its scrollTop or bring a child into view inside that container.

panel = wait.until(EC.presence_of_element_located(
    (By.CSS_SELECTOR, ".results-panel")
))

# Move the panel down by 400 pixels
driver.execute_script(
    "arguments[0].scrollTop += arguments[1];", panel, 400
)

# Reveal a row within that panel
row = panel.find_element(By.CSS_SELECTOR, "tr[data-id='42']")
driver.execute_script(
    "arguments[0].scrollIntoView({block: 'nearest'});", row
)

Using block: 'nearest' avoids needlessly moving the whole page when the child is already partly visible. If the panel uses virtualization, wait for the row to be rendered before locating it; a row that is not in the DOM cannot be scrolled into view.

Wait for content that appears after scrolling

Scrolling and waiting are separate operations. Infinite-scroll pages often append content asynchronously. After each movement, wait for a concrete condition rather than sleeping for an arbitrary time.

from selenium.common.exceptions import TimeoutException

old_count = len(driver.find_elements(By.CSS_SELECTOR, ".card"))
ActionChains(driver).scroll_by_amount(0, 700).perform()

try:
    wait.until(lambda d: len(d.find_elements(By.CSS_SELECTOR, ".card")) > old_count)
except TimeoutException:
    # The page may have reached its end, failed to load, or require a different trigger.
    pass

Other useful conditions include the presence of a “load more” control, disappearance of a spinner, or a footer becoming visible. For a page that loads images lazily, verify the image’s complete property or wait for an application-specific readiness marker.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Common failures and fixes

Element is outside the viewport

Cause: an action such as click or send keys was attempted without first scrolling. Fix: call scroll_to_element or scrollIntoView(), then wait for the element to be interactable.

Wheel action is unsupported or has no effect

Cause: the wheel reference is Chromium Only, or the installed binding/browser combination does not implement the method consistently. Fix: verify current browser support and Selenium version; use JavaScript scrolling when you need a portable fallback.

Wrong region scrolls

Cause: the page has a nested scroll container, but the command targets the window. Fix: identify the container with a selector and change its scrollTop, or use an element-origin action after confirming which region receives wheel input.

Sticky header hides the target

Cause: the target is technically visible but covered. Fix: center it with scrollIntoView({block: 'center'}), apply a controlled offset, or hide the header only in a test-specific style.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Stale element reference

Cause: infinite scrolling or a framework rerender replaced the DOM node. Fix: locate the element again after the scroll and wait for the replacement.

Timeout after scrolling

Cause: the page reached its end, a request failed, a bot check appeared, or the application needs a click rather than a wheel event. Fix: inspect the DOM and network-visible state, add an explicit end-of-content check, and handle the application’s actual loading control.

Performance and reliability practices

  • Prefer a target-based scroll over repeated blind distances when the test has a semantic destination.
  • Use one explicit wait tied to the state you need; excessive fixed sleeps make suites slow and still fail under variable latency.
  • Break very long pages into moderate movements when lazy loading or intersection observers are involved.
  • Re-find elements after operations that can rerender the page.
  • Record the browser, Selenium version, binding, and scroll method in failure logs so a browser-support issue is distinguishable from an application bug.
  • Keep wheel and JavaScript paths as separate helper functions. That makes it straightforward to switch when a browser or component behaves differently.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Or skip the browser setup

If your real goal is a clean image or PDF of a URL rather than interactive browser automation, ScreenshotNeo provides a single screenshot request. It accepts consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be disabled. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing result. It also offers an MCP server for AI clients, with take_screenshot, get_page_info, and capture_pdf tools.

One-call examples

See the parameter details in the ScreenshotNeo documentation.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
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 supports full-page captures with lazy images loaded, element selectors, dark mode, device presets and custom viewports, retina scale, PDFs with paper and page options, custom CSS and JavaScript, clicks, waits, request blocking, headers, cookies, user agents, authorization, timezone, geolocation, transparent backgrounds, resizing, chosen cache TTLs, signed image links, asynchronous webhooks, bulk capture of up to 100 URLs per call, a usage API, and an OpenAPI specification. Existing parameter names used by other screenshot APIs also work, easing migration.

The free plan includes 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 shots; every feature is available on every plan, and yearly billing gives two months free. Create a free ScreenshotNeo account to start.

FAQ

Which method should a new Selenium test use?

Use scroll_to_element when the test has a known target, and JavaScript when you need DOM-specific positioning or a fallback for an unsupported wheel environment.

Can Selenium scroll an iframe?

Switch into the frame first, then locate and scroll elements in that document. Switch back to the default content when the frame interaction is complete.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Does scrolling guarantee that an image has loaded?

No. Scrolling may trigger lazy loading, but wait for the image or application state that proves loading finished.

Frequently Asked Questions

Can I use negative values to scroll upward?

Yes. Pass a negative vertical delta to scroll_by_amount, such as scroll_by_amount(0, -300).

Why does my scroll target move but remain unclickable?

A fixed header, overlay, disabled state, or rerender may still block it. Center the element, wait for clickability, and locate it again after dynamic updates.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.