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

The most reliable way to reach the bottom of a Playwright page is to scroll a stable bottom element into view:

await page.getByText('Footer text').scrollIntoViewIfNeeded();

Use a footer, end-of-results marker, or explicit test ID when one exists. For pages without a reliable marker, scroll the document with evaluate(). Infinite-scroll pages need a loop that waits for new content and stops when an application-specific condition is met. If the content lives in an overflow container, scroll that container instead of the window.

Why explicit scrolling is sometimes necessary

Playwright automatically scrolls most elements into view before actions such as clicking or filling a field. Explicit scrolling is still useful when you need to trigger lazy loading or infinite scrolling, position the page for a screenshot, verify an end state, or reach content that is not the target of an immediate action.

A scroll command changes the viewport; it does not prove that loading has finished. Always assert the resulting state: a visible footer, a final item, an expected item count, a disappeared spinner, or a known end marker.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
#1 Best Overall
Sale
Logitech M185 Compact Ambidextrous 2.4 GHz Wireless Mouse - Swift Grey
  • Compact Mouse: With a comfortable and contoured shape, this Logitech ambidextrous wireless mouse feels great in either right or left hand and is far superior to a touchpad
  • Durable and Reliable: This USB wireless mouse features a line-by-line scroll wheel, up to 1 year of battery life (2) thanks to a smart sleep mode function, and comes with the included AA battery
  • Universal Compatibility: Your Logitech mouse works with your Windows PC, Mac, or laptop, so no matter what type of computer you own today or buy tomorrow your mouse will be compatible
  • Plug and Play Simplicity: Just plug in the tiny nano USB receiver and start working in seconds with a strong, reliable connection to your wireless computer mouse up to 33 feet / 10 m (5)
  • Better than touchpad: Get more done by adding M185 to your laptop; according to a recent study, laptop users who chose this mouse over a touchpad were 50% more productive (3) and worked 30% faster (4)

Scroll a stable bottom marker into view

When the page has a footer or end marker, locate it with a durable user-facing or test-contract locator:

import { test, expect } from '@playwright/test';

test('reaches the footer', async ({ page }) => {
  await page.goto('https://example.com');

  const footer = page.getByRole('contentinfo');
  await footer.scrollIntoViewIfNeeded();
  await expect(footer).toBeVisible();
});

If the footer has no useful ARIA role, use distinctive text or a test ID:

await page.getByText('Footer text', { exact: true }).scrollIntoViewIfNeeded();
await expect(page.getByText('Footer text', { exact: true })).toBeVisible();

// An explicit contract is often even more durable:
await page.getByTestId('end-of-results').scrollIntoViewIfNeeded();

scrollIntoViewIfNeeded() performs Playwright’s actionability checks and scrolls only when the element is not already completely visible. It is preferable to guessing a document height because it identifies the actual end state you care about.

Choosing a locator

  • Role: getByRole('contentinfo') for a semantic footer.
  • Text: distinctive end-of-list wording visible to users.
  • Test ID: an agreed attribute such as data-testid="end-of-results".
  • Avoid: long CSS chains and XPath expressions tied to layout details.

Scroll the whole document when no marker exists

For a finite page with no dependable bottom element, execute browser-side JavaScript:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
await page.evaluate(() => {
  window.scrollTo(0, document.documentElement.scrollHeight);
});

This moves the top-level document to its current maximum height. Verify the position or a visible element afterward:

Rank #2
Sale
Logitech G305 Lightspeed Wireless Gaming Mouse - Black
  • The next-generation optical HERO sensor delivers incredible performance and up to 10x the power efficiency over previous generations, with 400 IPS precision and up to 12,000 DPI sensitivity
  • Ultra-fast LIGHTSPEED wireless technology gives you a lag-free gaming experience, delivering incredible responsiveness and reliability with 1 ms report rate for competition-level performance
  • G305 wireless mouse boasts an incredible 250 hours of continuous gameplay on just 1 AA battery; switch to Endurance mode via Logitech G HUB software and extend battery life up to 9 months
  • Wireless does not have to mean heavy, G305 lightweight mouse provides high maneuverability coming in at only 3.4 oz thanks to efficient lightweight mechanical design and ultra-efficient battery usage
  • The durable, compact design with built-in nano receiver storage makes G305 not just a great portable desktop mouse, but also a great laptop travel companion, use with a gaming laptop and play anywhere
await page.evaluate(() => {
  window.scrollTo(0, document.documentElement.scrollHeight);
});

const atBottom = await page.evaluate(() => {
  const { scrollY, innerHeight } = window;
  return scrollY + innerHeight >= document.documentElement.scrollHeight;
});
expect(atBottom).toBe(true);

Some applications update the layout after images, ads, or client-side components render. In those cases, a single height read can become stale; use the infinite-scroll pattern below.

Handle infinite scrolling safely

Infinite lists require repeated movement and a condition that tells you when to stop. The loop limit and wait strategy are application-specific. This example uses the document height as a progress signal and stops after 20 iterations:

import { test, expect } from '@playwright/test';

test('loads an infinite list to its end', async ({ page }) => {
  await page.goto('https://example.com/feed');

  let previousHeight = 0;
  for (let i = 0; i < 20; i++) {
    const height = await page.evaluate(() => document.documentElement.scrollHeight);
    if (height === previousHeight) break;
    previousHeight = height;

    await page.evaluate(() => {
      window.scrollTo(0, document.documentElement.scrollHeight);
    });

    // Replace this delay with a DOM or network condition for your app.
    await page.waitForTimeout(500);
  }

  await expect(page.getByTestId('end-of-results')).toBeVisible();
});

A height that stops changing can mean the list is finished, but it can also mean a request failed or the application is still processing. Prefer a semantic condition whenever possible.

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

Better stop and wait conditions

  • End marker: wait for getByTestId('end-of-results') to be visible.
  • Loading indicator: wait for a spinner to disappear after each scroll.
  • Item count: record the current count, scroll, then wait for the count to increase.
  • Network response: wait for the specific request that supplies the next page.
  • Known target: stop when the final expected item becomes visible.
const items = page.getByRole('article');
let oldCount = await items.count();

for (let i = 0; i < 20; i++) {
  await items.last().scrollIntoViewIfNeeded();
  await expect.poll(() => items.count()).toBeGreaterThan(oldCount);
  const newCount = await items.count();
  if (newCount === oldCount) break;
  oldCount = newCount;

  if (await page.getByTestId('end-of-results').isVisible().catch(() => false)) break;
}

Do not rely on a fixed sleep when the page exposes a reliable DOM or network signal. A sleep can be too short on a slow run and unnecessarily long on a fast one.

Scroll a nested overflow container

A page can appear to scroll while the actual list is inside an element with overflow: auto or overflow: scroll. In that case, changing window scroll position will not move the list.

Rank #3
Sale
Logitech M185 Compact Ambidextrous Wireless Mouse with Rubber Grips - Blue
  • Compact Mouse: With a comfortable and contoured shape, this Logitech ambidextrous wireless mouse feels great in either right or left hand and is far superior to a touchpad
  • Durable and Reliable: This USB wireless mouse features a line-by-line scroll wheel, up to 1 year of battery life (2) thanks to a smart sleep mode function, and comes with the included AA battery
  • Universal Compatibility: Your Logitech mouse works with your Windows PC, Mac, or laptop, so no matter what type of computer you own today or buy tomorrow your mouse will be compatible
  • Plug and Play Simplicity: Just plug in the tiny nano USB receiver and start working in seconds with a strong, reliable connection to your wireless computer mouse up to 33 feet / 10 m (5)
  • Better than touchpad: Get more done by adding M185 to your laptop; according to a recent study, laptop users who chose this mouse over a touchpad were 50% more productive (3) and worked 30% faster (4)

Wheel input

Use a locator for the container, hover it so the wheel is directed there, and send incremental movement:

const list = page.getByTestId('scrolling-container');
await list.hover();
await page.mouse.wheel(0, 1000);

await expect(list.getByRole('listitem').last()).toBeVisible();

Several smaller wheel events may be necessary when the application loads more rows after each threshold:

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.
for (let i = 0; i < 20; i++) {
  await list.hover();
  await page.mouse.wheel(0, 800);
  await page.waitForTimeout(300);
  if (await page.getByTestId('end-of-results').isVisible().catch(() => false)) break;
}

Deterministic scrollTop control

For a deterministic test, set the element’s scroll position directly:

await list.evaluate((element) => {
  element.scrollTop = element.scrollHeight;
});

You can also move by a fixed amount, matching Playwright’s documented locator.evaluate() technique:

await list.evaluate((element) => {
  element.scrollTop += 100;
});

Inspect the container, not the document, when diagnosing progress:

Rank #4
Amazon Basics 3-Button USB Wired Mouse with Responsive Tracking, Plug & Play, Compatible with Windows and Mac, Black
  • Computer mouse for easily navigating a computer interface; click, scroll, and more
  • USB-A wired connection; if existing device only supports USB-C, an additional adapter will be required
  • High-definition (1000 dpi) optical tracking ensures responsive cursor control for precise tracking and easy text selection
  • 3 buttons offer effortless fingertip control
  • Plug-and-go ready for instant use
const metrics = await list.evaluate((element) => ({
  scrollTop: element.scrollTop,
  clientHeight: element.clientHeight,
  scrollHeight: element.scrollHeight
}));
console.log(metrics);

If scrollHeight is greater than clientHeight but scrollTop never changes, check whether another element overlays the container, whether it is enabled, and whether the selector identifies the scrollable element rather than a wrapper.

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.

Verify that scrolling achieved the intended result

Choose an assertion that represents the behavior under test:

  • await expect(footer).toBeVisible() for a finite document.
  • An expected final item or end marker for an infinite list.
  • A count increase after each load cycle.
  • A hidden loading indicator after the final request.
  • Container metrics showing that scrollTop + clientHeight is near scrollHeight.

A successful scroll call alone is not a useful assertion: it says only that an input or script ran, not that the application rendered the desired content.

Common failures and fixes

The window moves, but the list does not

Cause: the content is in a nested overflow container. Fix: locate that element, then use hover() plus mouse.wheel(), or set its scrollTop with evaluate().

One large wheel event loads nothing

Cause: the application listens for incremental thresholds or the pointer is over the wrong element. Fix: hover the container and send smaller events, waiting for new items between them.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Best Value
Sale
Acer Wireless Mouse for Laptop, 2.4GHz Computer Mouse 3 Adjustable 1600 DPI
  • 【Plug and Play for Home/Office/School】The wireless computer mouse features 2.4GHz connectivity, delivering a stable, interference-free connection up to 32ft. Designed for 𝐦𝐞𝐝𝐢𝐮𝐦 𝐭𝐨 𝐥𝐚𝐫𝐠𝐞 𝐬𝐢𝐳𝐞𝐝 𝐡𝐚𝐧𝐝𝐬, it ensures comfortable use all day. Simply plug in the USB-A receiver for instant pairing—no drivers needed. 📌📌 If the mouse isn’t suitable, place the USB receiver in the battery compartment and return both.
  • 【3 Levels Adjustable DPI】This travel USB mouse offers 3 adjustable DPI settings (800, 1200, 1600), allowing you to customize sensitivity for precise design work. Effortlessly switch to match your task and elevate your productivity. 📌 Please remove the film at the bottom of the mouse before use.
  • 【Effortless Browsing】Equipped with forward and backward buttons, this computer mice streamlines your workflow, making it easy to navigate through web pages and files with a simple click. 📌Side button does not work on Mac.
  • 【Visible Indicator Light】 The pc mouse features a visual indicator for DPI levels and low battery alerts. The red light flashes once for 800 DPI, twice for 1200 DPI, and three times for 1600 DPI. When the battery level is below 10%, the light flashes red until the mouse is completely out of power.
  • 【Click to Wake】With smart sleep mode, it saves power by standby after 10 inactive minutes, just 2-3 clicks to wake. This efficient design delivers 3x longer battery life than motion-wake mice. Engineered for durability, its buttons and scroll wheel are tested for 10 million clicks, ensuring long-term reliability and consistent performance.

The loop exits too early

Cause: the height was sampled before asynchronous content arrived, or a request failed. Fix: wait for a spinner, response, item-count increase, or end marker; treat an unchanged height as a diagnostic signal rather than proof of completion.

The test times out waiting for the footer

Cause: the footer is not rendered, the locator is ambiguous, or an infinite list never reaches its end. Fix: inspect the locator, confirm the page state, add a bounded loop, and assert the application’s actual completion signal.

Content is present but not visible

Cause: a fixed header, modal, cookie banner, or virtualized list is covering or recycling elements. Fix: dismiss overlays when appropriate, use a stable end marker, and assert the rendered state rather than relying only on coordinates.

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

Performance and reliability choices

Approach Best for Trade-off
scrollIntoViewIfNeeded() on a marker Known footer or end marker Most semantic and usually the least code; requires a stable locator.
window.scrollTo() Finite pages without a marker Deterministic for the document, but does not identify whether loading completed.
Incremental wheel events Realistic interaction and nested lists Can be slower and sensitive to pointer position.
Direct scrollTop Deterministic container tests Bypasses real input behavior; pair it with application-state assertions.

Keep an explicit iteration cap to prevent a broken feed from hanging a test. Capture diagnostics such as item counts, scroll metrics, and the last observed network or loading state when a test fails.

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

Or skip the browser setup

If your goal is a clean page image or PDF rather than testing interactive scrolling, ScreenshotNeo provides a website screenshot API. Its full-page capture loads lazy images, and you can choose waits, custom JavaScript, selectors to hide, device settings, PDF options, caching, and other capture controls. The API also accepts the parameter names used by other screenshot services, which can simplify a migration.

A single request is enough:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

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

See the ScreenshotNeo documentation for all options and response headers. Before capture, cookie and consent banners, newsletter popups, and chat widgets are removed. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed as clean shots; response headers identify the page verdict and billing result. An MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. The Free plan includes 1,000 screenshots per month without a card; paid plans start at $5 for 3,000 shots. Sign up free.

Playwright scrolling checklist

  1. Identify whether the document or a nested element owns the scroll.
  2. Prefer a role, text, or test-ID bottom marker.
  3. Use scrollIntoViewIfNeeded() when a marker exists.
  4. For infinite lists, wait for a semantic loading or completion condition.
  5. Bound every loop and record useful diagnostics.
  6. Assert the final UI state, not merely that a scroll command returned.

Frequently Asked Questions

Does Playwright scroll automatically before every action?

Most actions automatically scroll their target into view when actionability checks require it. Explicit scrolling is still needed to trigger infinite loading, position a page for a screenshot, or verify an end state.

How do I know whether I should scroll the window or an element?

Inspect the layout and scroll metrics. If the list element has its own scrollable height and its scrollTop changes, scroll that element; otherwise scroll the document.

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

Is a fixed timeout a reliable way to wait after scrolling?

No. A fixed delay is only a fallback. A loading indicator, item-count change, network response, or end marker adapts better to the page’s actual completion time.

Quick Recap

SaleBestseller No. 1
Logitech M185 Compact Ambidextrous 2.4 GHz Wireless Mouse - Swift Grey
Logitech M185 Compact Ambidextrous 2.4 GHz Wireless Mouse - Swift Grey
Product carbon footprint: 3.97 kg CO2e; Contoured shape: Gives you more comfort and control
$14.64
SaleBestseller No. 3
Bestseller No. 4
Amazon Basics 3-Button USB Wired Mouse with Responsive Tracking, Plug & Play, Compatible with Windows and Mac, Black
Amazon Basics 3-Button USB Wired Mouse with Responsive Tracking, Plug & Play, Compatible with Windows and Mac, Black
Computer mouse for easily navigating a computer interface; click, scroll, and more; 3 buttons offer effortless fingertip control
$9.70

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.