Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.
Use Playwright’s Page.ScreenshotAsync method to capture a browser page in .NET. Add a Path to save the image, or keep the returned byte array for further processing. Set FullPage for the entire scrollable document, or call ScreenshotAsync on a locator to capture one element.
This guide covers setup, complete C# examples, formats, visual controls, reliable test captures, troubleshooting, and a hosted alternative when you do not want to maintain a browser.
Minimal Playwright .NET screenshot
Install the Playwright .NET package, install a browser, then navigate a page and call ScreenshotAsync. The following console example uses Chromium and saves a PNG in the process’s current working directory.
dotnet add package Microsoft.Playwright
pwsh bin/Debug/net8.0/playwright.ps1 install chromium
using Microsoft.Playwright;
using var playwright = await Playwright.CreateAsync();
await using var browser = await playwright.Chromium.LaunchAsync(new BrowserTypeLaunchOptions
{
Headless = true
});
var context = await browser.NewContextAsync();
var page = await context.NewPageAsync();
await page.GotoAsync("https://example.com");
await page.ScreenshotAsync(new PageScreenshotOptions
{
Path = "screenshot.png"
});
await context.CloseAsync();
The official Playwright .NET screenshots guide shows the same core call. A relative path is resolved from the process’s current working directory. Use an absolute path when a test runner or CI job may start in a different directory.
#1 Best Overall
Save the file or use screenshot bytes
Page.ScreenshotAsync returns a byte[]. Supplying Path writes those bytes to disk; omitting it lets you upload, hash, resize, or attach the image without creating a temporary file.
byte[] image = await page.ScreenshotAsync();
await File.WriteAllBytesAsync("artifacts/home.webp", image);
The page API documents PNG, JPEG, and WebP output. Playwright can infer the format from the filename extension, or you can set Type explicitly.
Capture the viewport or the full page
Viewport screenshot
The default captures only the currently visible viewport. Set the viewport when reproducibility matters:
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
await page.SetViewportSizeAsync(1440, 900);
await page.ScreenshotAsync(new PageScreenshotOptions { Path = "viewport.png" });
Full-page screenshot
Set FullPage = true to capture the full scrollable page, as if it had a very tall screen. This is different from increasing the viewport: Playwright renders the document’s scrollable height into one image.
await page.GotoAsync("https://example.com/docs");
await page.ScreenshotAsync(new PageScreenshotOptions
{
Path = "docs-full.png",
FullPage = true
});
Very long pages can produce large images. If your consumer expects multiple pages, use PDF output or split the image after capture rather than assuming a browser viewport will paginate it.
Capture one element with a locator
Use Locator.ScreenshotAsync for a component, chart, header, or test fixture. Playwright performs actionability checks and scrolls the element into view before capturing it.
Rank #2
var header = page.Locator(".site-header");
await header.ScreenshotAsync(new LocatorScreenshotOptions
{
Path = "header.png"
});
If the target is covered by another element, the resulting pixels may not show it. For a scrollable container, the screenshot contains the container’s currently scrolled content, not every item hidden inside it. Scroll the container yourself or capture each state when that distinction matters.
Free tools Windows power users keep installed
One-click scans. No signup required.
Choose PNG, JPEG, or WebP
| Format | How to select it | Quality behavior | Typical use |
|---|---|---|---|
| PNG | Path = "shot.png" or Type = ScreenshotType.Png |
Lossless; the quality option does not apply. | Pixel comparisons, text, transparency. |
| JPEG | Path = "shot.jpg" or Type = ScreenshotType.Jpeg |
Quality defaults to 80; lower values reduce size with more loss. | Photographic pages and smaller files. |
| WebP | Path = "shot.webp" or Type = ScreenshotType.Webp |
Quality 100 is lossless; lower values are lossy. | Modern web delivery and compact archives. |
await page.ScreenshotAsync(new PageScreenshotOptions
{
Path = "hero.webp",
Type = ScreenshotType.Webp,
Quality = 85
});
WebP support and option names are release-sensitive; check the current Playwright .NET release notes when upgrading.
Control dimensions and visual output
Device scale versus CSS scale
The default scale follows the device pixel ratio, so a high-DPI context can create an image larger than its CSS dimensions. Set Scale = ScreenshotScale.Css for one image pixel per CSS pixel when predictable dimensions and smaller artifacts are more important than retina detail.
await page.ScreenshotAsync(new PageScreenshotOptions
{
Path = "css-scale.png",
Scale = ScreenshotScale.Css
});
Clip a rectangle
Use Clip to capture a specific rectangle in page coordinates:
await page.ScreenshotAsync(new PageScreenshotOptions
{
Path = "region.png",
Clip = new Clip { X = 0, Y = 120, Width = 800, Height = 500 }
});
Disable animation and hide the caret
For visual tests, set Animations = ScreenshotAnimations.Disabled. Finite animations are fast-forwarded; infinite animations are canceled for the capture and resumed afterward. Hiding the text caret avoids a blinking pixel changing between runs.
Quick wins for a faster PC:
Scan for outdated or missing drivers - takes under a minuteDriver Scan →Clear out junk files and repair common Windows errorsFree Scan →await page.ScreenshotAsync(new PageScreenshotOptions
{
Path = "stable.png",
Animations = ScreenshotAnimations.Disabled,
Caret = ScreenshotCaret.Hide
});
Mask dynamic content
Mask locators whose values change, such as timestamps or user names. Masks cover the target bounding box; the documented default mask color is pink, and you can choose another color.
await page.ScreenshotAsync(new PageScreenshotOptions
{
Path = "masked.png",
Mask = new[] { page.Locator(".last-updated"), page.Locator(".avatar") },
MaskColor = "#777777"
});
Apply screenshot-only CSS
The Style option injects CSS for this capture only. Hide a cookie banner, force a state, or remove a blinking cursor without changing the page under test.
await page.ScreenshotAsync(new PageScreenshotOptions
{
Path = "print-state.png",
Style = ".debug-panel, .cookie-banner { display: none !important; }"
});
Wait for the page you actually want to capture
A screenshot taken immediately after navigation may contain loading skeletons or unloaded images. Navigate with an appropriate wait condition, then wait for a meaningful selector.
await page.GotoAsync("https://example.com/dashboard", new PageGotoOptions
{
WaitUntil = WaitUntilState.NetworkIdle
});
await page.Locator("main.dashboard").WaitForAsync();
await page.ScreenshotAsync(new PageScreenshotOptions { Path = "dashboard.png" });
Network idle is not suitable for every application because analytics, polling, or WebSockets may keep requests active. In those cases, wait for a stable UI locator, a known response, or a deliberate short delay. Full-page capture may trigger lazy-loaded images as Playwright scrolls the document, but application-specific lazy loading can still require an explicit wait.
Recommended Free Tools
Production lifecycle and repeatability
Short scripts can use convenience APIs, but production tests should explicitly manage browser, context, and page lifetimes. A context isolates cookies, storage, viewport, locale, and permissions; closing it releases pages and context resources.
await using var browser = await playwright.Chromium.LaunchAsync();
await using var context = await browser.NewContextAsync(new BrowserNewContextOptions
{
ViewportSize = new() { Width = 1280, Height = 800 },
DeviceScaleFactor = 1
});
var page = await context.NewPageAsync();
Keep the browser process alive for a batch of captures and create a fresh context when isolation is required. Pin your Playwright package and browser binaries in CI, use the same operating-system fonts, and fix viewport, locale, timezone, and color scheme. These controls reduce differences but cannot guarantee byte-identical output across different rendering environments.
Timeouts, errors, and fixes
“Executable doesn’t exist” or browser launch failure
Install the browser binaries for the package version used by your project. In a typical build output, run the generated playwright.ps1 install chromium script, or install the browsers during CI setup.
Screenshot timeout
The documented screenshot timeout is 30 seconds by default. Increase it for slow pages or set a project-wide default:
Rank #4
await page.ScreenshotAsync(new PageScreenshotOptions
{
Path = "slow.png",
Timeout = 60_000
});
A timeout can also indicate that an element never became actionable, a navigation is still pending, or a page is blocked by authentication or a bot challenge. Inspect the page state and wait for the correct selector instead of only increasing the number.
Element is not visible or is covered
Confirm the locator matches one element, scroll it into view, and remove overlays or wait for them to disappear. A locator screenshot cannot reveal pixels hidden behind a modal, sticky layer, or consent dialog.
Blank or incomplete image
Check the URL response, authentication, redirects, and console errors. Wait for the application’s ready marker and for important images to finish loading. For a virtualized list, scroll through the list or capture the visible state intentionally.
Unexpected dimensions or file size
Check device scale, full-page mode, and CSS transforms. Use CSS scale for CSS-sized output, JPEG/WebP for smaller files, and Clip or locator capture to avoid recording unused page areas.
Crashes, No Sound, or Screen Glitches?
Random freezes, missing sound and display glitches usually trace back to one bad driver. Find and replace yours safely.Free scan · under a minutePC Slower Than It Used to Be?
A free scan shows the junk files, broken settings and background clutter dragging Windows down - then fixes them in one click.Free scan · Windows 10 & 11Or skip the browser setup
ScreenshotNeo provides a website screenshot API and MCP server. One GET request returns PNG, JPEG, WebP, or PDF, while its capture pipeline accepts cookie and consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets before the shot. Each option can be disabled.
Only clean shots are billed: bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and the response reports the result 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.
For the complete parameter list and authentication details, see the ScreenshotNeo documentation.
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)
r.raise_for_status()
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}`);
if (!res.ok) throw new Error(`${res.status} ${res.statusText}`);
require('fs').writeFileSync('shot.webp', Buffer.from(await res.arrayBuffer()));
ScreenshotNeo also supports full-page and selector captures, dark mode, device presets, retina scale, custom CSS and JavaScript, clicks, waits, request blocking, headers, cookies, user agents, authorization, timezone and geolocation, transparent backgrounds, resizing, configurable-TTL caching, signed image links, asynchronous jobs with signed webhooks, bulk capture of up to 100 URLs per call, usage reporting, and an OpenAPI specification. Existing parameter names used by other screenshot APIs are accepted to ease migration.
Best Value
| Plan | Allowance | Price |
|---|---|---|
| Free | 1,000 shots/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 |
Yearly billing gives two months free, and every feature is included on every plan. Create a free ScreenshotNeo account to get 1,000 screenshots a month without a card.
FAQ
Does Playwright screenshot return bytes?
Yes. ScreenshotAsync returns a byte array; add Path when you want Playwright to save it.
Can I capture an element instead of a page?
Yes. Call ScreenshotAsync on a locator. It scrolls the element into view and captures its currently visible contents.
What is the default screenshot format?
PNG is the default. JPEG and WebP are available through the screenshot options or filename extension.
Frequently Asked Questions
Can Playwright capture a full scrollable page?
Yes. Set FullPage = true on PageScreenshotOptions.
How do I avoid changing the live page when styling a screenshot?
Use the screenshot Style option to inject CSS only for that capture.
Quick Recap
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.

