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 an active RSelenium remoteDriver to run JavaScript in the current browsing context. To move once to the document’s current bottom, call remDr$executeScript("window.scrollTo(0, document.body.scrollHeight);"). For infinite-scroll or lazy-loaded pages, scroll in increments, wait for a page-specific change, and repeat; a single jump does not guarantee that more content has been fetched.

What scrolling can—and cannot—do

Scrolling changes the viewport. It may also trigger an application’s lazy-loading or infinite-scroll handler, but RSelenium cannot assume that new elements appeared just because the command returned. A browser navigation can reach its selected readyState while JavaScript continues to add, remove, or reveal content. Treat navigation completion and a fixed sleep as setup steps, not proof that the result you need exists.

First decide which situation you have:

Situation Best first action Important limitation
The page already contains all content and you need the bottom window.scrollTo(0, document.body.scrollHeight) It reaches the current bottom only; later content can extend the page.
Content is added as the user approaches the bottom Scroll by a measured amount, wait for a result signal, and loop You need a page-specific condition and stopping rule.
A known element is already in the DOM Locate it and scroll it into view It cannot scroll an element that has not yet been created.
The relevant area is inside a nested scroll container Scroll that container rather than window Moving the top-level window may leave the target area unchanged.

Prerequisites and a minimal RSelenium session

You need R, the RSelenium package, a running Selenium server (or a service that exposes WebDriver), and a browser driver compatible with that server and browser. The examples assume remDr is an already connected remoteDriver.

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

# Adjust port, browser, and server details for your environment.
remDr <- remoteDriver(
  remoteServerAddr = "localhost",
  port = 4444L,
  browserName = "chrome"
)
remDr$open()
remDr$navigate("https://example.com/page")

Keep the same driver session while you scroll. If the page opens an iframe, select the frame containing the content before executing page JavaScript; scripts run in the currently selected browsing context.

Scroll to the current document bottom

This is the concise one-time operation:

remDr$executeScript(
  "window.scrollTo(0, document.body.scrollHeight);"
)

The first argument is the horizontal coordinate and the second is the vertical coordinate. The call returns the JavaScript result (which is NULL here), while the browser viewport moves immediately. If you need to inspect the new position, return it explicitly:

remDr$executeScript(
  "window.scrollTo(0, document.body.scrollHeight);
   return { y: window.scrollY, height: document.body.scrollHeight };"
)

Use this method for a conventional long page or when one movement is known to trigger everything you need. On an infinite-scroll page, the bottom at the moment the script runs may be only the first batch’s bottom.

Scroll in increments for lazy loading

Incremental scrolling gives the page time to react and gives your script an opportunity to observe a meaningful change:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
remDr$executeScript("window.scrollBy(0, 800);")

The value is pixels in the current page coordinate system. Choose an increment that reaches the loading threshold without racing past it; 800 is an example, not a universal requirement.

A result-count loop

When each loaded item has a stable selector, count those elements before and after each movement. The loop below stops after a configurable number of checks with no increase, or when a desired count is reached.

Rank #2
Sale
HTML and CSS: Design and Build Websites
  • HTML CSS Design and Build Web Sites
  • Comes with secure packaging
  • It can be a gift option
count_items <- function() {
  out <- remDr$executeScript(
    "return document.querySelectorAll('.result-card').length;"
  )[[1]]
  as.integer(out %||% 0L)
}

`%||%` <- function(x, y) if (is.null(x)) y else x

wanted <- 100L
max_no_change <- 3L
no_change <- 0L
last_count <- count_items()

for (i in seq_len(40L)) {
  remDr$executeScript("window.scrollBy(0, 800);")
  Sys.sleep(1)
  current_count <- count_items()

  if (current_count >= wanted) break
  if (current_count > last_count) {
    no_change <- 0L
  } else {
    no_change <- no_change + 1L
  }
  last_count <- current_count
  if (no_change >= max_no_change) break
}

last_count

Replace .result-card with a selector that represents the page’s actual loaded records. “No increase three times” is merely a deliberate guard against an endless loop; a page-specific end marker, disabled “Load more” button, or API state is stronger when available.

Wait for a condition instead of guessing with sleep

A fixed delay can be too short on a busy page and wasteful on a fast one. Poll a condition that matters to your next action, such as an element becoming present or a count increasing.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
wait_for_count_increase <- function(selector, previous, timeout = 20, interval = 0.25) {
  deadline <- Sys.time() + timeout
  repeat {
    n <- as.integer(remDr$executeScript(
      sprintf("return document.querySelectorAll(%s).length;",
              jsonlite::toJSON(selector, auto_unbox = TRUE))
    )[[1]] %||% 0L)
    if (n > previous) return(n)
    if (Sys.time() >= deadline) return(n)
    Sys.sleep(interval)
  }
}

before <- count_items()
remDr$executeScript("window.scrollBy(0, 800);")
after <- wait_for_count_increase(".result-card", before)

If the page intentionally has no new item at a particular step, make timeout a normal branch and inspect an end-of-results indicator rather than treating it as a driver failure.

Scroll a known element into view

When the target is already in the DOM, find it and pass the WebElement as an argument to JavaScript. This avoids interpolating an untrusted selector into a script string.

target <- remDr$findElement(using = "css selector", value = "#pricing")
remDr$executeScript(
  "arguments[0].scrollIntoView({block: 'center', inline: 'nearest'});",
  args = list(target)
)

For a target that appears only after lazy loading, first scroll the relevant page or container, wait for the element, then call scrollIntoView. Finding it before that trigger correctly produces a “no such element” result.

Scrolling a nested container

Many feeds scroll inside a panel while the document itself stays still. Set that element’s scroll position or dispatch a wheel-style movement inside it:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
feed <- remDr$findElement(using = "css selector", value = ".feed")
remDr$executeScript(
  "arguments[0].scrollTop = arguments[0].scrollTop + 800;",
  args = list(feed)
)

Measure the container’s scrollTop, clientHeight, and scrollHeight to verify that you are moving the intended region. If the top-level page appears frozen, this is the first check to make.

Using asynchronous JavaScript

Use RSelenium’s executeAsyncScript when the browser-side operation itself is asynchronous. The injected script receives a callback as its final argument and must call it; the operation cannot span a page load.

value <- remDr$executeAsyncScript(
  "var done = arguments[arguments.length - 1];
   setTimeout(function () {
     window.scrollBy(0, 800);
     done({ y: window.scrollY });
   }, 250);"
)
value

For ordinary scrolling, synchronous executeScript plus an explicit wait for the application condition is simpler. Async execution is useful when you need to coordinate a browser-side timer, observer, or callback.

Frames, readiness, and reliable stopping

Select the correct frame

JavaScript executes in the currently selected frame. If the feed is inside an iframe, switch to that frame before finding elements or scrolling, then switch back when you need the top-level document. A script that succeeds in the wrong frame can appear to do nothing because it changed a different document.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Rank #4
Sale
Web Design with HTML, CSS, JavaScript and jQuery Set
  • Brand: Wiley
  • Set of 2 Volumes
  • A handy two-book set that uniquely combines related technologies Highly visual format and accessible language makes these books highly effective learning tools Perfect for beginning web designers and front-end developers

Do not equate navigation readiness with application readiness

Page-load strategy controls when navigation returns based on document readiness. Single-page applications can continue network requests and DOM updates afterward. Wait for the result element, a count change, a loading indicator to disappear, or another state your task actually requires.

Choose a defensible stop rule

  • Stop when the target element exists and is usable.
  • Stop when the result count reaches the required number.
  • Stop when an explicit end marker or disabled load control appears.
  • As a fallback, stop after several observations with no change and record that the page did not expose a stronger signal.

Set a maximum number of iterations and a per-iteration timeout so a broken endpoint cannot run indefinitely.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Troubleshooting common failures

Nothing new appears after scrolling

Confirm that the site really loads on scroll. It may use numbered pagination, a “Load more” button, or a nested container. Inspect which element’s scroll height changes and trigger that element instead of window.

The page looks loaded, but results are missing

Replace a fixed sleep with a wait for the result selector or another application-specific state. Also check whether the page displays an error, consent gate, or bot check that prevents the feed from running.

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.

The target cannot be found

If it is not yet in the DOM, scrolling it into view cannot create it. Trigger the earlier loading step, wait, and search again. Re-locate elements after large DOM updates because a previously captured WebElement can become stale.

The wrong area moves

Check for an iframe and nested overflow containers. Select the proper frame and set the container’s scrollTop. Verify movement by returning scrollY or the container’s scroll metrics.

Async execution never returns

Ensure the injected script calls the supplied callback on every success and failure path. Keep the operation within the current page; navigation during an async script invalidates the operation.

Or skip the browser setup

If your goal is a clean image or PDF rather than interaction with a live Selenium session, ScreenshotNeo provides a single screenshot request. Its capture can accept cookie and consent banners and remove more than 60 known consent platforms, newsletter popups, and chat widgets before the shot; each cleanup step can be disabled. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies the outcome with X-Page-Verdict and X-Billed headers.

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

See the ScreenshotNeo API documentation for options such as full-page lazy-image loading, selector capture, custom waits, blocking requests, device and viewport settings, PDFs, signed links, asynchronous jobs, and bulk capture. An MCP server exposes take_screenshot, get_page_info, and capture_pdf to Claude, Cursor, and other MCP clients. The Free plan includes 1,000 shots per month without a card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.

Complete examples in other languages

Python

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)

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}`);

These ScreenshotNeo calls are for capture, not for driving an interactive scroll loop. Use RSelenium when you must click, inspect, or react to a live application; use the API when a rendered artifact is the deliverable.

Operational checklist

  • Identify whether content is in the DOM, scroll-triggered, paginated, or inside a nested container.
  • Select the correct iframe and scroll region.
  • Use scrollTo for a one-time move or scrollBy in a bounded loop.
  • Wait for a meaningful application condition, not only navigation readiness.
  • Re-find elements after DOM replacement and cap iterations and timeouts.
  • Log counts, scroll positions, and the stop reason so a changed page is diagnosable.

Frequently Asked Questions

Can RSelenium scroll by an exact number of pixels?

Yes. Execute window.scrollBy(0, N) with the pixel amount you require, then verify the resulting position if precision matters.

Why does scrolling to document.body.scrollHeight stop too early?

That value is evaluated before newly requested content is appended. On an infinite-scroll page, repeat the movement after waiting for the application to extend the document.

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

Is Selenium wheel input required for lazy loading?

No. JavaScript scrolling through executeScript is usually sufficient. Wheel actions are another option, but their documented support and viewport behavior depend on the browser and binding.

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.