Menu
csharpdotnetscreenshotsapiselenium

Website Screenshots in C# and .NET: Selenium vs Screenshot API

SnapSharp Team·April 15, 2026·7 min read

Capturing a website screenshot in C# typically means one of two paths: Selenium WebDriver (with ChromeDriver) or Puppeteer Sharp (a .NET port of Puppeteer). Both work. Both require you to ship a Chromium binary alongside your application, manage driver version matching, and handle browser process lifecycle in production.

This guide shows both approaches, explains where they break in production, and demonstrates how to replace them with a single HttpClient call to the SnapSharp screenshot API.


The Selenium WebDriver Approach

using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;

public static async Task<byte[]> ScreenshotWithSelenium(string url)
{
    var options = new ChromeOptions();
    options.AddArgument("--headless=new");
    options.AddArgument("--no-sandbox");
    options.AddArgument("--disable-dev-shm-usage");
    options.AddArgument("--window-size=1280,720");

    using var driver = new ChromeDriver(options);
    try
    {
        driver.Navigate().GoToUrl(url);
        await Task.Delay(2000); // Unreliable wait — replace with WebDriverWait in production

        var screenshot = ((ITakesScreenshot)driver).GetScreenshot();
        return screenshot.AsByteArray;
    }
    finally
    {
        driver.Quit(); // Must dispose or Chrome processes accumulate
    }
}

This requires the Selenium.WebDriver and Selenium.WebDriver.ChromeDriver NuGet packages, plus a matching Chrome installation.

What breaks in production

ChromeDriver version mismatch. Chrome updates automatically on most systems. ChromeDriver does not. After an OS update you will see SessionNotCreatedException: session not created: This version of ChromeDriver only supports Chrome version 114. The Selenium.WebDriver.ChromeDriver NuGet package pins to a specific Chrome version.

Azure App Service and containerized environments. Chrome requires a writable /dev/shm filesystem and specific kernel capabilities. In Azure App Service (Windows), Chrome does not run at all. In Linux containers you need --no-sandbox (a security trade-off) and a 2 GB+ memory allocation.

IDisposable management. Forgetting driver.Quit() leaks browser processes. In a web application under load, unclosed browser processes accumulate and exhaust server memory within hours.

Concurrent requests. Each new ChromeDriver() spawns a full browser process (~300–600 MB RAM). Building a thread-safe pool with ConcurrentQueue<IWebDriver> is not trivial and still requires capacity planning.


The Screenshot API Approach

The SnapSharp screenshot API handles the browser infrastructure on its side. Your C# code sends one HTTP request and receives a binary PNG or JPEG.

Using HttpClient (.NET 6+)

using System.Net.Http;
using System.Net.Http.Headers;

public class SnapSharpClient
{
    private readonly HttpClient _httpClient;
    private const string ApiBase = "https://api.snapsharp.dev/v1";

    public SnapSharpClient(HttpClient httpClient)
    {
        _httpClient = httpClient;
        var apiKey = Environment.GetEnvironmentVariable("SNAPSHARP_API_KEY")
            ?? throw new InvalidOperationException("SNAPSHARP_API_KEY is not set");
        _httpClient.DefaultRequestHeaders.Authorization =
            new AuthenticationHeaderValue("Bearer", apiKey);
    }

    public async Task<byte[]> ScreenshotAsync(string url,
        int width = 1280, int height = 720, string format = "png",
        CancellationToken cancellationToken = default)
    {
        var encodedUrl = Uri.EscapeDataString(url);
        var requestUrl = $"{ApiBase}/screenshot?url={encodedUrl}&width={width}&height={height}&format={format}";

        var response = await _httpClient.GetAsync(requestUrl, cancellationToken);
        response.EnsureSuccessStatusCode();

        return await response.Content.ReadAsByteArrayAsync(cancellationToken);
    }
}

Register as a singleton in Program.cs:

builder.Services.AddHttpClient<SnapSharpClient>();

Usage in a controller or service:

public class ScreenshotController : ControllerBase
{
    private readonly SnapSharpClient _snapSharp;

    public ScreenshotController(SnapSharpClient snapSharp)
    {
        _snapSharp = snapSharp;
    }

    [HttpGet("screenshot")]
    public async Task<IActionResult> Get([FromQuery] string url)
    {
        var bytes = await _snapSharp.ScreenshotAsync(url);
        return File(bytes, "image/png", "screenshot.png");
    }
}

No using blocks to manage. No browser process lifecycle. No package updates when Chrome increments its version.

Full-page screenshots

var requestUrl = $"{ApiBase}/screenshot?url={encodedUrl}&full_page=true&width=1280&format=png";

Requires Starter plan or higher.

Mobile device emulation

var device = Uri.EscapeDataString("iPhone 14");
var requestUrl = $"{ApiBase}/screenshot?url={encodedUrl}&device={device}&format=png";

Available devices: iPhone 14, Pixel 7, iPad Pro, Galaxy S21, MacBook Pro 13.

Dark mode screenshots

var requestUrl = $"{ApiBase}/screenshot?url={encodedUrl}&dark_mode=true&width=1280&height=720";

POST request with JSON body

For more complex parameters, the API also accepts POST with a JSON body:

var payload = new
{
    url = "https://example.com",
    width = 1280,
    height = 720,
    format = "png",
    full_page = true,
    block_ads = true,
    delay = 1000
};

var content = new StringContent(
    System.Text.Json.JsonSerializer.Serialize(payload),
    System.Text.Encoding.UTF8,
    "application/json");

var response = await _httpClient.PostAsync($"{ApiBase}/screenshot", content, cancellationToken);
response.EnsureSuccessStatusCode();
var bytes = await response.Content.ReadAsByteArrayAsync(cancellationToken);

Error handling

public async Task<byte[]?> ScreenshotSafeAsync(string url)
{
    try
    {
        var response = await _httpClient.GetAsync(
            $"{ApiBase}/screenshot?url={Uri.EscapeDataString(url)}&width=1280&height=720");

        if (response.StatusCode == System.Net.HttpStatusCode.TooManyRequests)
        {
            // Rate limit exceeded — back off and retry or return null
            var retryAfter = response.Headers.RetryAfter?.Delta ?? TimeSpan.FromSeconds(60);
            await Task.Delay(retryAfter);
            return null;
        }

        response.EnsureSuccessStatusCode();
        return await response.Content.ReadAsByteArrayAsync();
    }
    catch (HttpRequestException ex)
    {
        // Log and handle — do not crash the request pipeline
        Console.Error.WriteLine($"Screenshot API error: {ex.Message}");
        return null;
    }
}

Saving to a file

var bytes = await client.ScreenshotAsync("https://github.com");
await File.WriteAllBytesAsync("screenshot.png", bytes);

Comparison

Selenium WebDriverSnapSharp API
Binaries requiredChrome + ChromeDriverNone
Azure App ServiceDoes not run on Windows tierWorks on all tiers
Docker image overhead+300–500 MBNone
Concurrent requests~5–10 before OOMUnlimited (rate-limited by plan)
Version maintenanceChrome/driver sync requiredNone
Full-page captureIJavaScriptExecutor scroll hackfull_page=true
Mobile emulationChromeOptions per devicedevice=iPhone+14
CachingDIYBuilt-in Redis, cache hits free
Cold start3–8s (Chrome launch)0ms (warm pool)

NuGet

No additional packages are required. System.Net.Http.HttpClient is built into .NET 6+.

If you are on .NET Framework 4.x, use HttpWebRequest or install System.Net.Http from NuGet.


Getting started

  1. Create a free account — no credit card required
  2. Go to Dashboard → API Keys → Create key
  3. Set the environment variable: SNAPSHARP_API_KEY=sk_...
  4. Use the SnapSharpClient class above in your ASP.NET Core or .NET console project

The free tier includes 100 screenshots/month. The Starter plan provides 5,000/month with full-page capture, retina resolution, and device emulation.

Website Screenshots in C# and .NET: Selenium vs Screenshot API — SnapSharp Blog