ScreenshotNeo

BlogHow-to

How to Fix Blank White Selenium Screenshots in C#

Diagnose blank white Selenium screenshots in C# with explicit waits, headless comparisons, version checks, viewport controls, and reliable capture code.

By the ScreenshotNeo team1 October 20267 min read

A blank white PNG does not identify the failure by itself. First determine whether the browser page is empty in the Selenium session or whether only the saved screenshot is blank. Then verify the application is rendered, compare headed and headless runs, check browser and driver versions, and confirm that the screenshot file is freshly written.

This guide gives a repeatable C# diagnostic flow, complete capture code, fixes for common causes, and a browser-free option with ScreenshotNeo.

1. Confirm what is actually blank

Selenium captures the current browsing context. If the page itself contains no visible application content, a white screenshot can be a valid representation of that state. If the page looks correct when inspected but the PNG is blank, investigate the file path, timing, viewport, browser mode, and browser/driver compatibility.

  1. Save to a known absolute path.
  2. Check the file timestamp and dimensions after the test runs.
  3. Immediately before capture, log the current URL, title, and a page-specific element or text.
  4. Record document.readyState, while remembering that complete does not prove a JavaScript application has finished rendering.
  5. Save diagnostics before quitting or disposing the driver.

2. Use an application-specific wait

Navigation completion and document.readyState only describe browser loading milestones. Single-page applications may still be fetching data, replacing a loading shell, or painting the main component. Wait for the condition that proves the content you need is present.

using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
using OpenQA.Selenium.Support.UI;
using System;
using System.IO;

var options = new ChromeOptions();
// Remove this argument temporarily for a headed comparison.
options.AddArgument("--headless");
options.AddArgument("--window-size=1920,1080");

using IWebDriver driver = new ChromeDriver(options);
driver.Navigate().GoToUrl("https://example.com/dashboard");

var wait = new WebDriverWait(driver, TimeSpan.FromSeconds(30));
wait.Until(d =>
{
    var element = d.FindElement(By.CssSelector("main"));
    return element.Displayed && !string.IsNullOrWhiteSpace(element.Text);
});

string readyState = ((IJavaScriptExecutor)driver)
    .ExecuteScript("return document.readyState;")?.ToString() ?? "unknown";

Console.WriteLine($"URL: {driver.Url}");
Console.WriteLine($"Title: {driver.Title}");
Console.WriteLine($"readyState: {readyState}");
Console.WriteLine($"Viewport: {driver.Manage().Window.Size}");

string outputPath = Path.GetFullPath("selenium-capture.png");
var screenshot = ((ITakesScreenshot)driver).GetScreenshot();
screenshot.SaveAsFile(outputPath);
Console.WriteLine($"Saved: {outputPath}");

Replace main with a selector that represents the rendered state of your own page. For a loading indicator, wait until it disappears; for a result list, wait until at least one result exists. Avoid using an arbitrary short Thread.Sleep as the primary fix because slower CI hosts can reproduce the race.

3. Capture diagnostics at the failure point

static void WriteDiagnostics(IWebDriver driver, string directory)
{
    Directory.CreateDirectory(directory);

    File.WriteAllText(
        Path.Combine(directory, "page.txt"),
        $"URL: {driver.Url}{Environment.NewLine}" +
        $"Title: {driver.Title}{Environment.NewLine}" +
        $"Window: {driver.Manage().Window.Size}{Environment.NewLine}" +
        $"ReadyState: {((IJavaScriptExecutor)driver)
            .ExecuteScript("return document.readyState;")}");

    var pageSource = driver.PageSource;
    File.WriteAllText(Path.Combine(directory, "page-source.html"), pageSource);

    ((ITakesScreenshot)driver)
        .GetScreenshot()
        .SaveAsFile(Path.Combine(directory, "failure.png"));
}

// Call WriteDiagnostics(driver, "diagnostics") inside your catch block,
// before driver.Dispose() or driver.Quit().

Check that the path you open is the path printed by the test. Selenium .NET writes the captured image bytes to the target PNG path and overwrites an existing file, so a stale file or a different working directory can make a successful capture look unchanged.

4. Compare headed and headless execution

Run the same URL, browser and driver versions, viewport, profile, and test data twice. Change only whether --headless is present.

Comparison What it tells you
Page content versus PNG Separates an application or navigation problem from an image-file problem.
Headed versus headless Shows whether the behavior is mode-specific.
Local versus CI/container/Grid Exposes environment, profile, display, networking, and resource differences.
Navigation-ready versus application-ready Shows whether the capture races asynchronous rendering.
Expected versus reported viewport Finds responsive layouts or off-screen content caused by an unexpected size.

Current Chrome documentation describes headless and headful as using the unified Chrome implementation. Treat a headless-only failure as evidence to investigate, not proof that C# PNG serialization is broken: Chrome Headless mode documentation.

5. Check browser, driver, and Selenium versions

Log the exact Selenium .NET package, Chrome version, ChromeDriver version, operating system, container or CI image, and every Chrome argument. Browser auto-updates can change the version pair without a code change.

Two SeleniumHQ reports are useful comparison points: issue #14544 describes a C# white-screen report with Selenium 4.25.0 and Chrome/ChromeDriver 129.0.6668.70; issue #14514 reports Chrome 129 with Selenium .NET 4.24.0 and ChromeDriver 129.0.6668.58. These are version-specific reports, not proof of a universal defect or a confirmed fix.

Do not add --headless=old as a current workaround. Chrome 132 removed the old implementation from the Chrome binary. Users who specifically need that behavior must use the separate chrome-headless-shell binary described by Chrome’s documentation.

6. Set and verify the viewport

A small or unexpected viewport can activate a mobile layout, hide the main content, or change lazy-loading behavior. Set the size deliberately and print the size Selenium reports.

var options = new ChromeOptions();
options.AddArgument("--headless");
options.AddArgument("--window-size=1920,1080");

using var driver = new ChromeDriver(options);
driver.Navigate().GoToUrl("https://example.com");
Console.WriteLine(driver.Manage().Window.Size);

The window-size argument appearing in a reported issue does not establish that viewport size caused the white page. Verify the actual result in your run and change one variable at a time.

7. Check navigation, redirects, authentication, and scripts

  • Print driver.Url after navigation; redirects may send the session to a login, error, or consent page.
  • Print driver.Title and inspect PageSource when the expected element is missing.
  • Confirm cookies, authentication state, proxy settings, and test data are available in headless and CI runs.
  • Look for a JavaScript error, blocked request, failed API call, or certificate problem in browser logs where your setup exposes them.
  • Wait for the application element rather than assuming a successful navigation call means the UI is ready.

8. Use Chrome’s CLI as an independent comparison

Chrome’s headless command-line mode supports --screenshot and --window-size. Its --timeout sets a maximum wait before the CLI capture even if loading continues. This can tell you whether the browser can render the URL outside Selenium, but it does not replace an application-specific wait in your test.

chrome --headless --disable-gpu \
  --window-size=1920,1080 \
  --timeout=30000 \
  --screenshot=cli.png \
  https://example.com

9. Common errors and fixes

Symptom Likely cause Fix
PNG is white and the expected element is absent Redirect, authentication failure, blocked request, script error, or application state is empty Log URL, title, page source, browser logs, and authentication state; fix navigation or application readiness first.
Headed works, headless is white Mode-specific rendering, version interaction, viewport difference, or CI environment Keep all other inputs identical, record versions, test a deliberate viewport, and compare logs.
Screenshot sometimes contains a loading shell Capture races asynchronous rendering Wait for the main content or for the loading indicator to disappear.
Old image keeps appearing Relative path, stale artifact, or file overwrite confusion Use an absolute path, print it, check its timestamp and dimensions, and save before quitting.
Content changes at different sizes Responsive layout or unexpected window size Set --window-size and print driver.Manage().Window.Size.
Failure begins after a browser update Browser and driver/Selenium version combination changed Record exact versions, reproduce with a controlled pair, and update or roll back according to your support policy.
Capture fails only in CI Different OS image, proxy, network, profile, permissions, or resource limits Compare local and CI diagnostics and preserve the complete failure artifact set.

10. Reliability and performance checklist

  • Use an explicit timeout appropriate for the application and environment.
  • Wait for a meaningful selector or state, not a fixed sleep.
  • Keep browser and driver versions compatible and log them per run.
  • Use a known viewport and verify it.
  • Save the screenshot, URL, title, page source, and readiness evidence together.
  • Change one variable per experiment so each result remains interpretable.
  • Reuse a driver only when test isolation is safe; otherwise a fresh profile can prevent state leakage.
  • Do not treat a valid blank page as an encoding failure until page content has been checked.

Or skip the browser setup

ScreenshotNeo returns a screenshot or PDF from one GET request. Before capture it accepts cookie and consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each step can be turned off. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and the response reports the result with X-Page-Verdict and X-Billed headers. It also provides an MCP server with take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients.

See the ScreenshotNeo API documentation for all options.

cURL

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

The free plan includes 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 shots, and every feature is available on every plan. Create a free ScreenshotNeo account.

FAQ

Does a blank screenshot prove Chrome is broken?

No. Check the page DOM and expected content immediately before capture. The screenshot may accurately represent an empty or unauthenticated page.

Should I increase the sleep duration?

Use a wait for the application state you need. A longer arbitrary sleep can reduce the symptom while remaining unreliable on slower runs.

Is document.readyState == "complete" enough?

No. It is useful diagnostic evidence, but client-side rendering and data requests can continue afterward.

Should I use --headless=old?

No. Chrome 132 removed old headless from the Chrome binary. Use current headless mode, or the separate chrome-headless-shell binary only when you specifically require the old implementation.

What evidence should I attach to a bug report?

Include the screenshot, URL, title, page-specific element check, page source, console or browser logs, Selenium version, Chrome and ChromeDriver versions, operating system, CI/container details, headless arguments, viewport, and whether headed mode works.