How to Fix a Horizontal Scrolling Table Cut Off in a Selenium Screenshot
A Selenium screenshot may capture only the visible part of a horizontally scrolling table. Identify the scroller, then choose a capture method that fits your browser and goal.
If a horizontally scrolling table is cut off in a Selenium screenshot, first identify which element actually scrolls, then check what area your screenshot method captures. A normal viewport screenshot shows the current view; an element screenshot captures the element’s visible bounding area. Neither should be assumed to include every horizontally hidden column.
This applies to Indian web apps as it does to other web apps. Without the page URL, DOM, Selenium binding and version, browser, and screenshot code, there is no way to know the cause on your particular page. The steps below help you diagnose it and choose a suitable fix.
1. Identify the horizontal scroller
A table may scroll inside a wrapper, or the document itself may scroll. Find the node whose scrollable width exceeds its visible width, then check which node’s scrollLeft changes when you scroll horizontally. These browser DOM checks are diagnostic; they do not assume a particular app’s CSS.
const table = document.querySelector('table');
let node = table;
const candidates = [];
while (node) {
const style = getComputedStyle(node);
candidates.push({
node,
tag: node.tagName,
className: node.className,
clientWidth: node.clientWidth,
scrollWidth: node.scrollWidth,
scrollLeft: node.scrollLeft,
overflowX: style.overflowX,
hasHorizontalOverflow: node.scrollWidth > node.clientWidth
});
node = node.parentElement;
}
console.table(candidates);
Run this in the page’s developer console after replacing the selector if needed. If the table is inside a frame, inspect the relevant frame’s document. During Selenium automation, you can evaluate the same check in the active browsing context with your binding’s JavaScript execution API.
- Compare
scrollWidthandclientWidthon the table and its ancestors. - Scroll horizontally and observe which node’s
scrollLeftchanges. - Record the screenshot’s viewport or element bounds and the scroller’s position at capture time.
- If you capture multiple sections, use overlap and inspect for duplicated sticky headers or fixed columns.
A page can contain more than one horizontal scroller. Confirm the one that contains the clipped table rather than changing the document scroll position by habit.
2. Choose the capture scope that matches the goal
| Method | What it captures | Use it when | Limit |
|---|---|---|---|
| Driver or current-context screenshot | The current browsing context, with behavior depending on binding and browser | You need a screenshot of the current view | Do not assume it includes all horizontally hidden content |
| Selenium WebElement screenshot | The visible region within the element’s bounding rectangle | You need one visible control or table region | It is not a guarantee of the element’s full scrollable width |
| WebDriver BiDi screenshot | Viewport or element, depending on the API used | Your binding and browser support the BiDi workflow | Availability depends on Selenium version, binding, and browser |
| Chrome DevTools Protocol screenshot | A specified region; Chrome also exposes an option to capture beyond the viewport | Your screenshot pipeline is Chrome-specific and you need control over capture bounds | CDP is Chrome-specific; captureBeyondViewport is marked experimental |
| Multiple scroll-position captures | Separate visible sections of the table | You need the full width and one capture is unsuitable | You must manage overlap, sticky elements, and stitching |
Selenium documents driver screenshots as current browsing-context captures; its JavaScript API describes a best-effort page-to-window-to-visible-frame sequence. Treat behavior as binding and browser dependent, not as a universal full-page promise. The Selenium element screenshot API captures the visible area of the element’s bounding rectangle. See the Selenium documentation and Selenium JavaScript API.
3. Capture the current view or a visible element
Use a driver screenshot when the current viewport is the intended output. If the table has a nested scroller, scroll that node to the desired position first. Use an element screenshot when the visible element rectangle is the intended output, while remembering it does not automatically expand to the element’s full scrollable content.
Python: viewport and visible element
from selenium import webdriver
from selenium.webdriver.common.by import By
options = webdriver.ChromeOptions()
driver = webdriver.Chrome(options=options)
try:
driver.get("https://example.com/report")
# Capture the current browsing context.
driver.save_screenshot("viewport.png")
# Capture only the visible bounding area of this element.
table = driver.find_element(By.CSS_SELECTOR, "table")
table.screenshot("table-visible.png")
finally:
driver.quit()
Replace the example URL and selector with the page under test. If the screenshot is taken before the table is ready, wait for the relevant page state before capturing; a fixed delay is not a reliable substitute for a condition that represents readiness.
Java: viewport and visible element
import java.io.File;
import org.openqa.selenium.By;
import org.openqa.selenium.OutputType;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
WebDriver driver = new ChromeDriver();
try {
driver.get("https://example.com/report");
File viewport = ((org.openqa.selenium.TakesScreenshot) driver)
.getScreenshotAs(OutputType.FILE);
WebElement table = driver.findElement(By.cssSelector("table"));
File visibleTable = table.getScreenshotAs(OutputType.FILE);
// Copy the files to your desired output paths using your project’s file utilities.
} finally {
driver.quit();
}
The Java example obtains screenshot files through Selenium’s screenshot API. Add your project’s preferred file-copy code if you need named output files.
4. Capture multiple horizontal sections
If the required output is the entire table width and the browser’s single-capture route does not fit, capture successive positions of the actual scroller. Save each position with its screenshot so the output is reproducible. The following Python example assumes the table wrapper is the scroller and saves each viewport-width slice; change the selector to the node found during diagnosis.
from pathlib import Path
from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
options = webdriver.ChromeOptions()
driver = webdriver.Chrome(options=options)
try:
driver.get("https://example.com/report")
selector = ".table-wrapper"
wait = WebDriverWait(driver, 20)
wait.until(lambda d: d.execute_script("""
const el = document.querySelector(arguments[0]);
return el && el.clientWidth > 0 && el.scrollWidth >= el.clientWidth;
""", selector))
metrics = driver.execute_script("""
const el = document.querySelector(arguments[0]);
return { clientWidth: el.clientWidth, scrollWidth: el.scrollWidth };
""", selector)
max_left = metrics["scrollWidth"] - metrics["clientWidth"]
step = max(1, metrics["clientWidth"] - 40) # 40 px overlap
positions = list(range(0, max_left + 1, step))
if not positions or positions[-1] != max_left:
positions.append(max_left)
output_dir = Path("table-parts")
output_dir.mkdir(exist_ok=True)
for index, left in enumerate(positions):
driver.execute_script("""
const el = document.querySelector(arguments[0]);
el.scrollLeft = arguments[1];
""", selector, left)
actual_left = driver.execute_script(
"return document.querySelector(arguments[0]).scrollLeft", selector
)
driver.save_screenshot(str(output_dir / f"part-{index:03d}.png"))
(output_dir / f"part-{index:03d}.txt").write_text(
f"requested_scrollLeft={left} actual_scrollLeft={actual_left}\n"
)
finally:
driver.quit()
This example captures the viewport at each wrapper position, not a cropped image of the wrapper. Crop or stitch only after checking the page layout and overlap. If the document is the scroller, update the script to scroll the document instead. Some content loads as it becomes visible; allow the page to settle at each position and verify that the final slice contains the expected last column. Sticky headers and pinned columns may appear in every slice, so remove duplicates deliberately if you stitch.
5. Use WebDriver BiDi when your stack supports it
Selenium’s WebDriver BiDi documentation lists viewport and element screenshot capabilities. BiDi is the W3C standard bidirectional browser automation protocol developed with the Selenium project and browser vendors. Check the API exposed by your language binding, Selenium version, and target browser before adopting it. The documentation does not make every binding and browser combination interchangeable. See Selenium WebDriver BiDi.
Choose the viewport or element operation based on the desired scope. BiDi’s existence does not mean that a nested horizontal scroller’s hidden columns are automatically included; the page layout and capture bounds still matter.
6. Use Chrome CDP for a specified region or beyond-viewport capture
Chrome DevTools Protocol’s Page.captureScreenshot accepts a clip region and has a captureBeyondViewport option. Page.getLayoutMetrics exposes content and viewport metrics that can help determine bounds. This is a Chrome-specific route, and captureBeyondViewport is marked experimental; validate behavior against your Chrome and Selenium versions before depending on it. Consult the Chrome DevTools Protocol Page reference.
// Protocol-level example (pseudocode): exact command plumbing depends on
// the Selenium binding and its CDP integration.
const metrics = await cdp.send("Page.getLayoutMetrics");
const result = await cdp.send("Page.captureScreenshot", {
format: "png",
captureBeyondViewport: true,
clip: {
x: 0,
y: 0,
width: metrics.cssContentSize.width,
height: metrics.cssContentSize.height,
scale: 1
}
});
// Decode result.data from base64 and write the bytes to a PNG file.
This shows the protocol parameters, not a drop-in Selenium snippet: CDP session setup and command dispatch differ by binding and Selenium release. Confirm that the measured content bounds include the table area you need. A page-wide content clip may also capture unrelated content; use a region appropriate to the output you want.
7. Decide whether the page layout also needs a change
If the goal is a single image of a wide report, a capture workflow may be the right fix. If people need to read the table on a narrow screen, consider a UI change as well: make horizontal scrolling discoverable, prioritize the most important columns, or provide another way to inspect less-used columns. These are design options, not a universal CSS fix. Without the page DOM and styles, no one rule can be prescribed for every app.
8. Troubleshoot common failures
| Symptom | Likely cause | What to do |
|---|---|---|
| Rightmost columns are missing | The capture shows the viewport or visible element bounds, while the table scrolls inside a wrapper | Find the actual scroller; capture successive positions or use a browser-specific capture route with verified bounds |
| Scrolling the page does not move the table | The horizontal scroll belongs to a nested wrapper | Set and inspect scrollLeft on the wrapper found through the ancestor check |
| Element screenshot is still narrow | The element’s visible bounding rectangle is narrower than its scrollable content | Treat element screenshot as visible-region capture; capture multiple positions or use another supported method |
| Only part of the page appears in a driver screenshot | Screenshot scope can depend on binding, browser, frame, and implementation | Check the API behavior for your exact stack; capture the needed viewport or region explicitly |
| CDP command or parameter is rejected | The browser, protocol version, or Selenium CDP integration may not support that command or option | Check the matching protocol reference and versions; avoid relying on the experimental option without validation |
| Some columns are blank in later captures | Content may load lazily or update after scrolling | Wait for the relevant content at each position and verify the last slice before stitching |
| Stitched image repeats headers or columns | Sticky headers, pinned columns, or insufficient overlap handling | Keep overlap metadata, identify repeated regions, and remove duplicates in a deliberate post-processing step |
| Screenshot is blank or shows a loading state | Capture happened before the relevant page state was ready, or the page did not render successfully | Wait for a meaningful selector or state and inspect the browser’s page and network errors |
9. Performance, reliability, and cost
Each extra scroll position adds a browser capture and file handling. More slices also mean more overlap to inspect and more opportunity for asynchronous content or sticky elements to differ between captures. Keep the viewport and device scale consistent across slices, record positions, and make the page state stable before capturing. A CDP capture may avoid manual stitching when its bounds work for the page, but it ties the workflow to Chrome and protocol behavior.
For repeatable output, record the browser and Selenium versions, binding, viewport dimensions, device scale, frame context, scroller selector, scroll positions, and capture method. Recheck the final edge of the table, since a last slice can be missed if positions are generated only by fixed steps.
Costs for a self-managed Selenium workflow depend on the machines and browser infrastructure you run; the research sources provide no benchmark or pricing figure. A hosted screenshot API has a per-plan cost, so compare its supported capture options and billing rules with your workload.
Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server from Yorker Media. One GET request returns a PNG, JPEG, WebP, or PDF. For a public page, this Python example captures the URL; see the ScreenshotNeo API documentation for options and authentication details.
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)
ScreenshotNeo removes cookie banners, newsletter popups, and chat widgets before capture, and each step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed; response headers report the page verdict and billing status. Its MCP server gives AI agents tools for screenshots, page information, and PDF capture. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 screenshots. Those cleanup and billing features can help when a clean page capture is the goal, though they do not replace diagnosing a table’s nested horizontal scroller.
Learn about ScreenshotNeo, or sign up for 1,000 free screenshots a month with no card.
FAQ
Does a Selenium full-page screenshot always include a wide table?
No universal guarantee applies across bindings and browsers. A full-page capture and a table’s nested horizontal scrolling are different dimensions; verify the actual capture bounds and scroller.
Should I resize the browser until the whole table fits?
That can be useful for a controlled capture if the page responds as expected, but it may change the layout and does not prove the original viewport behavior is correct. Check the table and wrapper after resizing.
What details help diagnose a clipping issue?
Share the browser and version, Selenium version and language binding, screenshot method, viewport size, relevant table-wrapper markup and styles, and which element’s scrollLeft changes.


