How to Capture Full-Page Screenshots with Selenium in C#
Use FirefoxDriver’s native full-page API or Chromium DevTools to capture an entire webpage in Selenium C#, with setup, code, fixes, and alternatives.
Direct answer: In Selenium .NET, use FirefoxDriver.GetFullPageScreenshot() for Firefox. Selenium’s ordinary ITakesScreenshot.GetScreenshot() represents the page currently on screen, so it is a viewport capture unless the browser-specific implementation expands it. For Chrome and other Chromium browsers, use Selenium’s DevTools Page screenshot command with CaptureBeyondViewport = true, using the generated DevTools namespace that matches your installed Selenium package and browser.
Choose the capture method first
| Route | Best for | Important constraint |
|---|---|---|
| FirefoxDriver.GetFullPageScreenshot() | Native full-document screenshots in Firefox | The method is on FirefoxDriver, not the general IWebDriver interface. |
| Chromium DevTools Page.captureScreenshot | Chrome, Edge and other Chromium browsers | The generated OpenQA.Selenium.DevTools.V### binding must match your Selenium package and browser. |
| Scroll and stitch | Fallback when a native route is unsuitable | Sticky headers, animations, lazy loading and nested scroll areas can create seams or duplicated content. |
The Firefox method is implemented by Selenium’s Firefox driver through the browser endpoint /session/{sessionId}/moz/screenshot/full. Selenium’s .NET API describes GetScreenshot() as an image of the page on screen. Chromium’s generated DevTools settings expose CaptureBeyondViewport, whose documented default is false. See the FirefoxDriver source and the Selenium .NET API reference.
Prerequisites
- Install the .NET SDK.
- Create a console project:
dotnet new console -n FullPageShot. - Add Selenium:
dotnet add package Selenium.WebDriver. - Install the browser you intend to automate. Selenium Manager can resolve drivers for current Selenium versions, but verify that the browser starts in your environment.
- Use a URL that includes the scheme, such as
https://example.com.
Firefox: native full-page capture
This is the shortest source-backed implementation. The call returns a Selenium Screenshot, which you save with SaveAsFile.
using OpenQA.Selenium;
using OpenQA.Selenium.Firefox;
var options = new FirefoxOptions();
// options.AddArgument("-headless"); // Enable on CI or servers without a display.
using var driver = new FirefoxDriver(options);
driver.Manage().Window.Size = new System.Drawing.Size(1440, 900);
driver.Navigate().GoToUrl("https://example.com");
var screenshot = driver.GetFullPageScreenshot();
screenshot.SaveAsFile("full-page.png");
Console.WriteLine("Saved full-page.png");
GetFullPageScreenshot() is a FirefoxDriver method. Do not type the driver as only IWebDriver if you need to call it:
using OpenQA.Selenium;
using OpenQA.Selenium.Firefox;
using FirefoxDriver driver = new FirefoxDriver();
Screenshot image = driver.GetFullPageScreenshot();
Wait for the page before capturing
Navigation completion does not prove that application data, fonts or images are ready. Wait for a meaningful selector, then capture.
using OpenQA.Selenium;
using OpenQA.Selenium.Firefox;
using OpenQA.Selenium.Support.UI;
using var driver = new FirefoxDriver();
driver.Navigate().GoToUrl("https://example.com/catalog");
var wait = new WebDriverWait(driver, TimeSpan.FromSeconds(30));
wait.Until(d => d.FindElement(By.CssSelector("main")));
wait.Until(d => ((IJavaScriptExecutor)d).ExecuteScript("return document.fonts ? document.fonts.status : 'loaded';").ToString() == "loaded");
var screenshot = driver.GetFullPageScreenshot();
screenshot.SaveAsFile("catalog.png");
Trigger lazy-loaded content
A full-document API captures the document, but it does not promise that every off-screen application resource has loaded. If the page loads images only after scrolling, scroll through it first and wait for images to finish.
var js = (IJavaScriptExecutor)driver;
js.ExecuteScript(@"
window.scrollTo(0, document.body.scrollHeight);
window.scrollTo(0, 0);");
var wait = new WebDriverWait(driver, TimeSpan.FromSeconds(30));
wait.Until(d => (bool)((IJavaScriptExecutor)d).ExecuteScript(@"
return Array.from(document.images).every(img => img.complete);") );
var screenshot = ((FirefoxDriver)driver).GetFullPageScreenshot();
screenshot.SaveAsFile("lazy-loaded.png");
Chrome and Chromium: DevTools capture beyond the viewport
For Chromium, use Selenium’s generated DevTools Page API. The namespace contains a version number, such as OpenQA.Selenium.DevTools.V###; use the version exposed by the Selenium package you installed. The exact generated types change with Selenium releases, so inspect your package or IDE before copying the namespace.
using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
using OpenQA.Selenium.DevTools;
using System.Text.Json;
var options = new ChromeOptions();
// options.AddArgument("--headless=new");
using var driver = new ChromeDriver(options);
driver.Navigate().GoToUrl("https://example.com");
if (driver is not IDevTools devTools)
throw new NotSupportedException("This driver does not expose Selenium DevTools.");
var session = devTools.GetDevToolsSession();
// Replace V### with the matching generated namespace in your Selenium package.
// Example shape for Selenium releases that generate a Page domain:
// var domains = session.GetVersionSpecificDomains();
// await domains.Page.Enable();
// var settings = new OpenQA.Selenium.DevTools.V###.Page.CaptureScreenshotCommandSettings
// {
// CaptureBeyondViewport = true,
// Format = OpenQA.Selenium.DevTools.V###.Page.CaptureScreenshotCommandSettings.FormatValue.Png
// };
// var result = await domains.Page.CaptureScreenshot(settings);
// File.WriteAllBytes("full-page.png", result.Data);
Console.WriteLine("Use the generated V### Page binding that matches your Selenium package.");
The command shape above is intentionally version-qualified: Selenium generates DevTools bindings from a browser protocol version. Check the installed package’s OpenQA.Selenium.DevTools.V### namespace and its CaptureScreenshotCommandSettings type, then set CaptureBeyondViewport to true. If your binding uses a different enum or return type, follow that generated API rather than mixing versions.
When Chromium code fails to compile
- Namespace not found: your package does not contain the version you typed. Browse the installed assembly or upgrade Selenium, then replace
V###. - Missing Page method: the generated API shape differs in your Selenium release. Open the Page domain in IntelliSense and use its generated command and settings names.
- Browser session errors: update Selenium Manager, the browser, or the driver so their versions are compatible.
Viewport screenshots versus full-page screenshots
This common call is useful when you deliberately want only the visible viewport:
using OpenQA.Selenium;
using OpenQA.Selenium.Firefox;
using var driver = new FirefoxDriver();
driver.Navigate().GoToUrl("https://example.com");
((ITakesScreenshot)driver).GetScreenshot().SaveAsFile("viewport.png");
Do not label that result as full-page solely because the page has a long document. The documented interface describes an image of the page on screen. Use Firefox’s browser-specific method or the Chromium DevTools route for beyond-viewport capture.
Fallback: scroll and stitch
Stitching takes several viewport images and combines them. It can work when a native full-page API is unavailable, but it needs page-specific handling. Fixed headers may appear in every tile, animations can shift content between shots, lazy loading can change page height, and nested scroll containers may never be captured by scrolling the document.
- Disable animations with temporary CSS where the site permits it.
- Record the viewport width and height.
- Scroll by a measured amount with a small overlap.
- Capture each viewport.
- Crop repeated fixed elements and combine the tiles with an image library.
- Inspect seams on the actual target pages and browser versions.
The available research includes a historical tutorial showing this approach, but it does not verify a currently maintained .NET stitching package. Treat stitching as an implementation to evaluate, not as a package recommendation.
Capture options and page preparation
| Need | What to do |
|---|---|
| Desktop layout | Set a deterministic window size before navigation. |
| Mobile layout | Use a mobile emulation or a narrow window and verify breakpoints. |
| Dark mode | Set the browser preference or page state before waiting for content. |
| Authentication | Log in first, or load the required cookies and headers in the browser session. |
| Cookie banners | Accept or dismiss the banner before capture; otherwise it may cover content. |
| Ads and chat widgets | Hide or disable them with test-environment configuration or page JavaScript. |
| Animations | Inject a temporary stylesheet that sets transition and animation durations to zero, then wait for layout stabilization. |
| Long documents | Check the output for clipping, blank image regions and memory pressure. |
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Only the visible area is saved | GetScreenshot() was used. |
Use FirefoxDriver.GetFullPageScreenshot() or Chromium DevTools with CaptureBeyondViewport = true. |
GetFullPageScreenshot is missing |
The variable is typed as IWebDriver, or the driver is not Firefox. |
Keep a FirefoxDriver reference and import OpenQA.Selenium.Firefox. |
| Images are blank or missing | Lazy loading or failed network requests. | Scroll to trigger loading, wait for document.images, and inspect network and console errors. |
| Cookie dialog covers the page | The consent state was not set. | Accept or dismiss it before capture, or use a test profile with the consent cookie already present. |
| Sticky header repeats in stitched output | The header is fixed while the document scrolls. | Hide it during capture or crop repeated regions during assembly. |
| Content shifts between tiles | Animations, ads or live data change layout. | Freeze animations, wait for stable content, and use deterministic test data. |
| DevTools type or enum errors | Generated binding version does not match the sample. | Use the V### namespace installed with your Selenium package and check its generated signatures. |
| Timeout during navigation | The page or a third-party resource never finishes. | Set a sensible page-load timeout, wait for the selector your capture needs, and diagnose the slow request instead of waiting indefinitely. |
| Headless output differs from headed output | Different viewport, fonts, GPU behavior or media queries. | Set the same window size, install required fonts, and compare headed and headless runs on representative pages. |
Performance and reliability guidance
- Reuse a browser session when capturing several pages, but clear cookies and local storage when isolation matters.
- Set explicit viewport dimensions, timezone and locale so responsive layouts are repeatable.
- Wait for the smallest reliable readiness signal, such as a content selector and completed images, instead of an arbitrary long delay.
- Capture after fonts load; late font swaps can change line wrapping and document height.
- Limit concurrency to what the machine can render without exhausting CPU or memory. Measure your own pages because the sources provide no universal speed or height benchmark.
- Save diagnostic metadata with each image: URL, browser version, viewport, timestamp and readiness condition.
- Retry transient browser startup or network failures, but do not blindly retry deterministic bot checks or application errors.
Or skip the browser setup
ScreenshotNeo provides a website screenshot API and MCP server. One request returns a PNG, JPEG, WebP or PDF. It removes cookie and consent banners, newsletter popups and chat widgets before the capture; bot checks, blank pages, timeouts, failed loads and cache hits are not billed, and the response reports the page verdict and billing status in X-Page-Verdict and X-Billed headers. Its MCP server exposes take_screenshot, get_page_info and capture_pdf for Claude, Cursor and other MCP clients.
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}`);
See the ScreenshotNeo API documentation for the full parameter set. Options include full-page capture, CSS-selector element capture, device presets, custom viewports, retina scale, dark mode, PDF paper and margin settings, custom CSS and JavaScript, clicks, selector or network-idle waits, request blocking, headers, cookies, user agents, authorization, timezone, geolocation, transparent backgrounds, resizing, cache TTLs, signed image links, asynchronous webhooks, bulk capture and usage data.
There is a free plan with 1,000 screenshots per month and no card. Paid plans start at $5 for 3,000 screenshots, and every feature is available on every plan. Create a free ScreenshotNeo account.
Cost and operational notes
Selenium itself runs on infrastructure you manage, so your cost depends on browser hosts, parallelism, storage and maintenance. A hosted API shifts that browser setup to the service. With ScreenshotNeo, only clean shots are billed; failed loads, bot checks, blank pages, timeouts and cache hits are free, and each response identifies the result with headers. Choose based on whether you need a local browser session with application-level control or a repeatable capture endpoint.
FAQ
Can I call full-page capture through IWebDriver?
Not for Firefox’s native method. Keep a FirefoxDriver reference because GetFullPageScreenshot() is browser-specific.
Does full-page capture load every lazy image?
No. Prepare the page by scrolling or triggering the application’s loading behavior, then wait for the images or selectors required by your capture.
Is Chromium’s DevTools route cross-browser?
It is Chromium-specific and coupled to generated DevTools bindings. Firefox has its own native full-page method.
Why are screenshots different in CI?
Different fonts, viewport dimensions, browser flags, locale, timezone and animation timing can change layout. Make those inputs explicit.
When should I use stitching?
Use it only when a native browser route is unsuitable and after testing sticky elements, lazy loading, nested scroll containers and animations on the pages you capture.


