Skip to main content
SeleniumDecoded

Scrolling

Scroll elements into view, scroll by an amount, use the W3C wheel actions, handle infinite scroll and sticky headers, and know when Selenium scrolls for you.

Selenium 4 Stable Updated 9 Sept 2026 · Verified against Selenium 4.48.0

Selenium scrolls an element into view automatically before clicking or typing into it. That covers most cases, so the first rule of scrolling is: do not scroll unless a command failed because of it. The exceptions are lazy-loaded content, sticky headers that cover the target after the automatic scroll, infinite feeds, and screenshots of a specific region. This lesson covers each.

Option 1: scrollIntoView via JavaScript

The most common approach, and the one that works in every browser and Selenium version.

Scroll an element into view
Selenium 3 & 4 Stable
WebElement footer = driver.findElement(By.id("footer"));
JavascriptExecutor js = (JavascriptExecutor) driver;
// Align to top of viewport
js.executeScript("arguments[0].scrollIntoView(true);", footer);
// Center it, which avoids sticky headers and footers
js.executeScript("arguments[0].scrollIntoView({block: 'center', inline: 'nearest'});", footer);
footer = driver.find_element(By.ID, "footer")
# Align to top of viewport
driver.execute_script("arguments[0].scrollIntoView(true);", footer)
# Center it
driver.execute_script("arguments[0].scrollIntoView({block: 'center', inline: 'nearest'});", footer)
const footer = await driver.findElement(By.id('footer'));
await driver.executeScript('arguments[0].scrollIntoView(true);', footer);
await driver.executeScript("arguments[0].scrollIntoView({block: 'center', inline: 'nearest'});", footer);
var footer = driver.FindElement(By.Id("footer"));
var js = (IJavaScriptExecutor)driver;
js.ExecuteScript("arguments[0].scrollIntoView(true);", footer);
js.ExecuteScript("arguments[0].scrollIntoView({block: 'center', inline: 'nearest'});", footer);

block: 'center' is the setting to remember. Aligning to the top puts the element under any fixed header, which then intercepts the click.

Option 2: The Actions API Wheel (Selenium 4.2+)

The W3C Actions API includes a wheel input device. Selenium 4.2 added scrollToElement, scrollByAmount and scrollFromOrigin so scrolling is a real user action rather than script injection. It fires the same events a trackpad would, which matters for infinite-scroll implementations that listen to wheel rather than scroll.

Actions-based scrolling
Selenium 4 Stable
import org.openqa.selenium.interactions.Actions;
import org.openqa.selenium.interactions.WheelInput;
Actions actions = new Actions(driver);
// Scroll until the element is in view
WebElement target = driver.findElement(By.id("load-more"));
actions.scrollToElement(target).perform();
// Scroll the page by a delta (positive y = down)
actions.scrollByAmount(0, 500).perform();
// Scroll from an origin: here, from the element's centre, 200px further down
WheelInput.ScrollOrigin origin = WheelInput.ScrollOrigin.fromElement(target);
actions.scrollFromOrigin(origin, 0, 200).perform();
// Scroll inside a scrollable container: origin at the container, delta applies to it
WebElement panel = driver.findElement(By.cssSelector(".sidebar-scroll"));
actions.scrollFromOrigin(WheelInput.ScrollOrigin.fromElement(panel), 0, 300).perform();
from selenium.webdriver import ActionChains
from selenium.webdriver.common.actions.wheel_input import ScrollOrigin
actions = ActionChains(driver)
# Scroll until the element is in view
target = driver.find_element(By.ID, "load-more")
actions.scroll_to_element(target).perform()
# Scroll the page by a delta (positive y = down)
actions.scroll_by_amount(0, 500).perform()
# Scroll from an origin
origin = ScrollOrigin.from_element(target)
actions.scroll_from_origin(origin, 0, 200).perform()
# Scroll inside a scrollable container
panel = driver.find_element(By.CSS_SELECTOR, ".sidebar-scroll")
actions.scroll_from_origin(ScrollOrigin.from_element(panel), 0, 300).perform()
const { Origin } = require('selenium-webdriver/lib/input');
const target = await driver.findElement(By.id('load-more'));
// Scroll until the element is in view
await driver.actions().scroll(0, 0, 0, 0, target).perform();
// Scroll the page by a delta from the viewport origin
await driver.actions().scroll(0, 0, 0, 500, Origin.VIEWPORT).perform();
// Scroll from an element origin, 200px further down
await driver.actions().scroll(0, 0, 0, 200, target).perform();
using OpenQA.Selenium.Interactions;
var actions = new Actions(driver);
var target = driver.FindElement(By.Id("load-more"));
// Scroll until the element is in view
actions.ScrollToElement(target).Perform();
// Scroll the page by a delta
actions.ScrollByAmount(0, 500).Perform();
// Scroll from an origin
var origin = new WheelInputDevice.ScrollOrigin { Element = target, XOffset = 0, YOffset = 0 };
actions.ScrollFromOrigin(origin, 0, 200).Perform();

Safari does not implement the wheel action; fall back to JavaScript there.

Scrolling the Window by Pixels or to the Bottom

Window-level scrolling with JavaScript
Selenium 3 & 4 Stable
JavascriptExecutor js = (JavascriptExecutor) driver;
js.executeScript("window.scrollBy(0, 800);"); // relative
js.executeScript("window.scrollTo(0, document.body.scrollHeight);"); // to bottom
js.executeScript("window.scrollTo({top: 0, behavior: 'instant'});"); // to top
Long y = (Long) js.executeScript("return window.scrollY;"); // current offset
driver.execute_script("window.scrollBy(0, 800);")
driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
driver.execute_script("window.scrollTo({top: 0, behavior: 'instant'});")
y = driver.execute_script("return window.scrollY;")
await driver.executeScript('window.scrollBy(0, 800);');
await driver.executeScript('window.scrollTo(0, document.body.scrollHeight);');
await driver.executeScript("window.scrollTo({top: 0, behavior: 'instant'});");
const y = await driver.executeScript('return window.scrollY;');
var js = (IJavaScriptExecutor)driver;
js.ExecuteScript("window.scrollBy(0, 800);");
js.ExecuteScript("window.scrollTo(0, document.body.scrollHeight);");
js.ExecuteScript("window.scrollTo({top: 0, behavior: 'instant'});");
var y = (long)js.ExecuteScript("return window.scrollY;");

Use behavior: 'instant' in tests. Smooth scrolling animates over several frames, and a screenshot or click taken mid-animation lands in the wrong place.

Infinite Scroll

Feeds that load more items as you approach the bottom need a loop: scroll, wait for new items, repeat until the count stops changing or you have what you need. Waiting on the item count is the reliable signal; waiting a fixed time is not.

Load an infinite feed until an item appears or it stops growing
Selenium 4 Medium
By items = By.cssSelector("[data-testid='feed-item']");
JavascriptExecutor js = (JavascriptExecutor) driver;
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
int previous = -1;
for (int page = 0; page < 20; page++) {
int count = driver.findElements(items).size();
if (count == previous) break; // feed stopped growing
previous = count;
js.executeScript("window.scrollTo(0, document.body.scrollHeight);");
final int seen = count;
try {
wait.until(d -> d.findElements(items).size() > seen);
} catch (TimeoutException e) {
break; // nothing more loaded
}
}
System.out.println("Loaded " + driver.findElements(items).size() + " items");
items = (By.CSS_SELECTOR, "[data-testid='feed-item']")
wait = WebDriverWait(driver, 10)
previous = -1
for _ in range(20):
count = len(driver.find_elements(*items))
if count == previous:
break # feed stopped growing
previous = count
driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
try:
wait.until(lambda d: len(d.find_elements(*items)) > count)
except TimeoutException:
break # nothing more loaded
print("Loaded", len(driver.find_elements(*items)), "items")
const items = By.css("[data-testid='feed-item']");
let previous = -1;
for (let page = 0; page < 20; page++) {
const count = (await driver.findElements(items)).length;
if (count === previous) break;
previous = count;
await driver.executeScript('window.scrollTo(0, document.body.scrollHeight);');
try {
await driver.wait(async () => (await driver.findElements(items)).length > count, 10000);
} catch {
break;
}
}
var items = By.CssSelector("[data-testid='feed-item']");
var js = (IJavaScriptExecutor)driver;
var wait = new WebDriverWait(driver, TimeSpan.FromSeconds(10));
int previous = -1;
for (int page = 0; page < 20; page++)
{
int count = driver.FindElements(items).Count;
if (count == previous) break;
previous = count;
js.ExecuteScript("window.scrollTo(0, document.body.scrollHeight);");
try { wait.Until(d => d.FindElements(items).Count > count); }
catch (WebDriverTimeoutException) { break; }
}

Cap the loop. A feed that never ends will otherwise run until the test timeout.

Sticky Headers That Intercept Clicks

The symptom is ElementClickInterceptedException with a message naming the header element. Selenium scrolled the target to the top edge and the header covers it. Fixes, in order of preference:

  1. Scroll with block: 'center' before clicking (above).
  2. Use Actions.scrollToElement, which respects scroll-margin CSS if the app sets it.
  3. As a last resort, hide the header with JavaScript in the test environment.
Click under a sticky header
Selenium 3 & 4 Medium
WebElement link = driver.findElement(By.linkText("Terms"));
((JavascriptExecutor) driver).executeScript(
"arguments[0].scrollIntoView({block: 'center'});", link);
link.click();
link = driver.find_element(By.LINK_TEXT, "Terms")
driver.execute_script("arguments[0].scrollIntoView({block: 'center'});", link)
link.click()
const link = await driver.findElement(By.linkText('Terms'));
await driver.executeScript("arguments[0].scrollIntoView({block: 'center'});", link);
await link.click();
var link = driver.FindElement(By.LinkText("Terms"));
((IJavaScriptExecutor)driver).ExecuteScript("arguments[0].scrollIntoView({block: 'center'});", link);
link.Click();

Full-Page Screenshots

Selenium’s getScreenshotAs captures the viewport in most browsers (Firefox captures the full page). To capture a long page elsewhere, either resize the window to the document height before the screenshot, or use CDP’s Page.captureScreenshot with captureBeyondViewport on Chromium. The resize approach works everywhere:

Resize to document height, then capture
Selenium 4 Medium
JavascriptExecutor js = (JavascriptExecutor) driver;
long height = (Long) js.executeScript("return document.documentElement.scrollHeight;");
driver.manage().window().setSize(new Dimension(1366, (int) height));
File shot = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE);
height = driver.execute_script("return document.documentElement.scrollHeight;")
driver.set_window_size(1366, height)
driver.save_screenshot("full-page.png")
const height = await driver.executeScript('return document.documentElement.scrollHeight;');
await driver.manage().window().setRect({ width: 1366, height });
fs.writeFileSync('full-page.png', await driver.takeScreenshot(), 'base64');
var height = (long)((IJavaScriptExecutor)driver).ExecuteScript("return document.documentElement.scrollHeight;");
driver.Manage().Window.Size = new System.Drawing.Size(1366, (int)height);
((ITakesScreenshot)driver).GetScreenshot().SaveAsFile("full-page.png");

Headless browsers accept very tall windows; headed ones are capped by the screen.

Summary

  • Selenium scrolls automatically before interacting; scroll manually only for lazy content, sticky headers, infinite feeds or screenshots.
  • scrollIntoView({block: 'center'}) avoids fixed headers; the Actions wheel API produces real user events.
  • Drive infinite scroll loops by element count with a cap, never by sleeping.
  • Use instant scrolling in tests; smooth scrolling causes mid-animation clicks and screenshots.

Related lessons