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

In Playwright for .NET, take a page screenshot with Page.ScreenshotAsync. Give it a Path to save a file, or omit Path and use the returned byte[]. Set FullPage = true for the entire scrollable page, or call ScreenshotAsync on a locator to capture one element.

This guide covers installation, complete C# examples, image formats, full-page and element captures, deterministic visual settings, troubleshooting, and a browser-free API option.

Minimal working example

Install the Playwright .NET package, install a browser, then launch Chromium, create a context and page, navigate, and capture.

Install Playwright and Chromium

dotnet add package Microsoft.Playwright
dotnet build
pwsh bin/Debug/net8.0/playwright.ps1 install chromium

Replace net8.0 with your target framework directory if your project uses another .NET version. The browser-install script is generated by the package build.

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

Save a PNG to disk

using Microsoft.Playwright;

public class Program
{
    public static async Task Main()
    {
        using var playwright = await Playwright.CreateAsync();
        await using var browser = await playwright.Chromium.LaunchAsync(new()
        {
            Headless = true
        });

        await using var context = await browser.NewContextAsync(new()
        {
            ViewportSize = new() { Width = 1440, Height = 900 }
        });

        var page = await context.NewPageAsync();
        await page.GotoAsync("https://example.com");
        await page.ScreenshotAsync(new()
        {
            Path = "screenshot.png"
        });
    }
}

Path is resolved relative to the process working directory. The default output is PNG, inferred from the extension when you provide one.

Choose what to capture

Goal API or option Result
Visible viewport page.ScreenshotAsync() The currently visible browser viewport.
Entire page FullPage = true The complete scrollable page, as if it were displayed on a very tall screen.
One element page.Locator(selector).ScreenshotAsync() The selected element after Playwright scrolls it into view.
Image bytes Omit Path A byte[] for processing, uploading, or storing yourself.

Capture the full scrollable page

await page.GotoAsync("https://example.com/docs");
await page.ScreenshotAsync(new()
{
    Path = "docs-full.png",
    FullPage = true
});

Full-page capture includes the page’s scrollable content rather than only the current viewport. Very long pages can create large images and consume more memory; use an element capture or a clip rectangle when you need only a section.

Capture one element

var header = page.Locator("header.site-header");
await header.ScreenshotAsync(new()
{
    Path = "header.png"
});

A locator screenshot performs actionability checks and scrolls the element into view. If another element covers it, the covered pixels will not appear as the element you expect. For a scrollable container, Playwright captures only the content currently visible inside that container, not every item hidden behind its own scrollbar.

Use screenshot bytes instead of a file

ScreenshotAsync always returns image bytes. When no path is supplied, keep the result in memory and pass it to an object store, HTTP response, image pipeline, or test assertion.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
var imageBytes = await page.ScreenshotAsync(new()
{
    FullPage = true
});

await File.WriteAllBytesAsync("latest.png", imageBytes);

This pattern avoids a temporary file when your application immediately uploads or transforms the image. Add a path when a local artifact is more convenient for debugging or CI reports.

Control format, quality, and dimensions

PNG, JPEG, and WebP

Format How to select it Quality behavior
PNG Use a .png path or Type = ScreenshotType.Png. Lossless; the quality option does not apply.
JPEG Use a .jpg path or Type = ScreenshotType.Jpeg. Defaults to quality 80; set a value when size and visual fidelity need balancing.
WebP Use a .webp path or Type = ScreenshotType.Webp. Quality 100 is lossless; lower values are lossy. WebP support is release-sensitive, so check the current .NET release notes when upgrading.
await page.ScreenshotAsync(new()
{
    Path = "hero.webp",
    Type = ScreenshotType.Webp,
    Quality = 85
});

Quality is meaningful for JPEG and WebP, not PNG.

Device pixels versus CSS pixels

Screenshot dimensions use device-pixel scaling by default. Set Scale = ScreenshotScale.Css to produce one image pixel per CSS pixel, which can reduce dimensions on high-DPI environments.

await page.ScreenshotAsync(new()
{
    Path = "css-sized.png",
    Scale = ScreenshotScale.Css
});

Clip a rectangle

Use Clip when a fixed rectangle is more precise than a locator. Coordinates and dimensions are CSS pixels relative to the page.

await page.ScreenshotAsync(new()
{
    Path = "top-right.png",
    Clip = new ScreenshotClip
    {
        X = 900,
        Y = 0,
        Width = 500,
        Height = 300
    }
});

Make captures repeatable

Disable animations

Set Animations = ScreenshotAnimations.Disabled to disable CSS transitions, CSS animations, and Web Animations during capture. Finite animations are fast-forwarded to completion; infinite animations are canceled for the shot and then resume afterward.

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.
await page.ScreenshotAsync(new()
{
    Path = "stable.png",
    Animations = ScreenshotAnimations.Disabled,
    Caret = ScreenshotCaret.Hide
});

Hiding the caret prevents a blinking text cursor from changing visual diffs.

Mask dynamic regions

Mask timestamps, avatars, ads, or other changing regions with locators. Masks cover the locator bounding box; the documented default color is pink.

await page.ScreenshotAsync(new()
{
    Path = "masked.png",
    Mask = new[]
    {
        page.Locator(".last-updated"),
        page.Locator(".user-avatar")
    }
});

Apply CSS only for the screenshot

The Style option injects screenshot-only CSS, useful for hiding a chat launcher or normalizing a visual detail without changing application code.

await page.ScreenshotAsync(new()
{
    Path = "without-overlays.png",
    Style = ".chat-launcher, .newsletter-modal { display: none !important; }"
});

For deterministic results, also control the context viewport, timezone, locale, and test data in the same way on every run. Playwright gives you the controls, but identical pixels still depend on the browser version, fonts, network responses, and application state.

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

Manage browser, context, and page lifetimes

A short script can create one page directly, but production tests should explicitly own the browser context and page. A context isolates cookies, storage, permissions, and viewport settings; disposing it releases those resources before the browser process exits.

using var playwright = await Playwright.CreateAsync();
await using var browser = await playwright.Chromium.LaunchAsync(new()
{
    Headless = true
});
await using var context = await browser.NewContextAsync(new()
{
    ViewportSize = new() { Width = 1280, Height = 800 }
});
var page = await context.NewPageAsync();

Reuse a browser for a batch of pages, but create separate contexts when tests must not share state. Close pages and contexts in finally blocks if your surrounding code can throw before normal disposal.

Wait for the page you actually want to capture

GotoAsync returning does not guarantee that late images, fonts, or application data have finished rendering. Wait for a meaningful selector before the screenshot.

await page.GotoAsync("https://example.com/dashboard");
await page.Locator("main.dashboard").WaitForAsync();
await page.ScreenshotAsync(new()
{
    Path = "dashboard.png",
    FullPage = true
});

If content is driven by a known API call, wait for the resulting UI state rather than adding an arbitrary delay. A fixed delay can be useful for a page with an unavoidable animation, but it is slower and less reliable than waiting for a selector that proves the page is ready.

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.

Troubleshoot common failures

The browser executable is missing

Symptom: launch fails with an executable-not-found error. Cause: the NuGet package is installed but its browser binaries are not. Fix: run the generated Playwright install script for the target framework, for example pwsh bin/Debug/net8.0/playwright.ps1 install chromium, then rerun the program.

The screenshot is blank or incomplete

Cause: capture happened before the application rendered, or the page redirected to a login, bot check, or error state. Fix: wait for a selector that identifies the intended page, verify the final URL, and inspect the page text or HTML before capturing.

An element screenshot contains the wrong pixels

Cause: the locator matched multiple nodes, the target was covered, or a scrollable container hid part of its content. Fix: make the locator specific, wait for it to be visible, check overlays, and capture the container’s current viewport or the individual child elements you need.

Navigation or screenshot times out

The documented default screenshot timeout is 30 seconds. Increase it for a known slow page or set a page/context default timeout, while still fixing the underlying load problem.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
await page.ScreenshotAsync(new()
{
    Path = "slow-page.png",
    Timeout = 60_000
});

Visual tests differ between machines

Pin the browser version used by CI, set an explicit viewport, use a consistent scale, disable animations, hide the caret, mask dynamic locators, and ensure the same fonts and test data are installed. A screenshot setting cannot compensate for different rendered inputs.

The output file is unexpectedly large

Use JPEG or lossy WebP when lossless pixels are unnecessary, reduce the viewport or clip to the relevant region, use CSS scale on high-DPI runners, and avoid full-page capture for pages that contain extensive off-screen content.

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

Performance, reliability, and cost considerations

  • Browser startup: launching Chromium is expensive compared with taking another screenshot in an existing browser. Keep one browser alive for a batch and isolate state with contexts.
  • Memory: full-page images and high device scale increase memory use. Capture only the required element or clip when possible.
  • Network: screenshots reflect the response, fonts, and third-party resources available at capture time. Wait for a page-specific readiness signal and make external dependencies deterministic in tests.
  • Artifacts: save PNG for pixel-accurate diffs; choose JPEG or WebP when transfer size matters more than lossless comparison.
  • Timeouts: set an explicit screenshot timeout for slow environments, but keep a finite limit so a broken page does not stall a test indefinitely.

Or skip the browser setup

If you only need a URL converted to an image or PDF, ScreenshotNeo provides a hosted screenshot API and MCP server. It accepts consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be disabled. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and every response identifies the result with X-Page-Verdict and X-Billed headers.

One-call examples

See the ScreenshotNeo API documentation for parameter details.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
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}`);

Replace the URL with your target page and supply your API key. ScreenshotNeo supports full-page capture, CSS-selector element capture, dark mode, 12 device presets plus custom viewports, retina scale, PNG/JPEG/WebP, PDF paper and page-range controls, custom CSS and JavaScript, pre-capture clicks, hide selectors, selector or network-idle waits, ad and tracker blocking, custom headers, cookies, user agents, authorization, timezone and geolocation, transparent backgrounds, resizing, configurable-TTL caching, signed image links, asynchronous jobs with signed webhooks, bulk capture for up to 100 URLs per call, a usage API, and an OpenAPI specification. Existing parameter names used by other screenshot APIs also work.

Plans

Plan Allowance Price
Free 1,000 shots per month $0, no card
Starter 3,000 shots $5
Growth 15,000 shots $15
Pro 60,000 shots $39
Scale 250,000 shots $99
Business 1,000,000 shots $249

Every feature is included on every plan, and yearly billing provides two months free. The MCP server exposes take_screenshot, get_page_info, and capture_pdf to Claude, Cursor, and other MCP clients, so an AI agent can request captures without your code managing a browser.

Create a free ScreenshotNeo account to get 1,000 screenshots each month without a card.

Frequently Asked Questions

Can I use the same capture code for a test and a production worker?

Yes. Keep the page and screenshot options in a shared method, but let each environment create and dispose its own browser context so cookies, viewport settings, and failures remain isolated.

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

When should I prefer a locator over a clip rectangle?

Use a locator when the element’s layout can move and you want Playwright to find and scroll to it. Use a clip when you need a fixed geometric region independent of DOM selectors.

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.