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 →Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →iTechGuides is reader-supported. When you buy through links on our site, we may earn an affiliate commission. As an Amazon Associate I earn from qualifying purchases. Learn more
To use Puppeteer-style browser automation in C#, install PuppeteerSharp, download its compatible browser, launch it asynchronously, create a page, and then navigate, interact, capture, or generate a PDF. PuppeteerSharp is described by its project documentation as “a .NET port of the official Node.JS Puppeteer API.”
dotnet add package PuppeteerSharp
The complete workflow is asynchronous, and every browser and page should be disposed when the operation finishes.
Install PuppeteerSharp and a compatible browser
Create or open a .NET application, then add the NuGet package:
The Tool Desk
Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →dotnet add package PuppeteerSharp
The package version and supported target frameworks change over time. Check the exact NuGet version you select and the project README before committing to a target framework. The NuGet listing returned version 25.12.0 on the date of the referenced research, but that number is volatile.
#1 Best Overall
PuppeteerSharp’s README recommends its bundled Chromium for guaranteed compatibility. Using a different browser executable is possible, but the project describes that choice as being at the user’s risk. The README also lists an X server requirement for Linux; verify the current deployment prerequisites for your distribution and hosting environment.
Download the browser at startup
BrowserFetcher downloads the browser revision expected by the installed package:
using PuppeteerSharp;
var browserFetcher = new BrowserFetcher();
await browserFetcher.DownloadAsync();
In production, download the browser during image creation or deployment rather than on every request. Ensure the process can write to the browser cache directory and that the deployed account has permission to execute the browser.
Do these 3 things before closing this tab:
1Fix the driver behind crashes, sound loss and screen glitches2Repair Windows errors before they cause bigger problems3Scan for outdated or missing drivers - takes under a minuteMinimal C# example: navigate and take a screenshot
This is the smallest useful end-to-end program:
using PuppeteerSharp;
var browserFetcher = new BrowserFetcher();
await browserFetcher.DownloadAsync();
await using var browser = await Puppeteer.LaunchAsync(
new LaunchOptions { Headless = true });
await using var page = await browser.NewPageAsync();
await page.GoToAsync("https://example.com");
await page.ScreenshotAsync("screenshot.png");
LaunchAsync, NewPageAsync, navigation, and the capture are all asynchronous. The await using declarations make disposal visible: the page is closed first and the browser is then shut down even if a later operation throws.
Control the viewport
Set the viewport before navigation when layout, responsive breakpoints, or screenshot dimensions matter:
await page.SetViewportAsync(new ViewPortOptions
{
Width = 1440,
Height = 900,
DeviceScaleFactor = 1
});
await page.GoToAsync("https://example.com");
await page.ScreenshotAsync("desktop.png", new ScreenshotOptions
{
FullPage = true
});
A fixed viewport makes runs more reproducible. FullPage = true captures the complete document rather than only the visible viewport; pages that lazy-load content may need an additional scroll or readiness wait before capture.
Navigate reliably
Navigation can finish before an application has rendered its useful state. Choose a readiness condition that matches the site rather than assuming that the first response means the page is ready.
Outdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware matchPC 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 & 11await page.GoToAsync("https://example.com/dashboard", new NavigationOptions
{
WaitUntil = new[] { WaitUntilNavigation.Networkidle0 },
Timeout = 60_000
});
Single-page applications may continue making requests indefinitely, so waiting for a specific selector or application condition is often more reliable than waiting for network idle alone.
Rank #2
Select and interact with elements
Use locators for ordinary actions
PuppeteerSharp documents locators with built-in auto-retry and auto-wait. Use them for buttons, links, and form controls:
var email = page.Locator("input[name='email']");
await email.FillAsync("dev@example.com");
await page.Locator("button.submit").ClickAsync();
A locator is preferable to immediately querying a node when the element may appear after client-side rendering. Use stable attributes such as a data-test identifier when you control the page; long positional CSS selectors are more likely to break after a redesign.
Wait for a selector
When the next step depends on a particular element, wait for that element explicitly:
Recommended Free Tools
await page.WaitForSelectorAsync(".results");
var text = await page.Locator(".results").InnerTextAsync();
Set an appropriate timeout for the operation and handle timeout exceptions so a slow or changed page produces a useful diagnostic instead of an unexplained failure.
Wait for a browser-side condition
For state that cannot be represented by one selector, use a page function:
await page.WaitForFunctionAsync(
"() => document.querySelectorAll('.result').length > 0");
This executes the predicate in the page context. Keep the predicate inexpensive and narrowly scoped.
Run JavaScript in the page
Use EvaluateExpressionAsync<T> for an expression or EvaluateFunctionAsync<T> for a function. The return value is deserialized into the requested C# type:
var title = await page.EvaluateExpressionAsync<string>(
"document.title");
var links = await page.EvaluateFunctionAsync<string[]>(@"
() => Array.from(document.querySelectorAll('a'))
.map(a => a.href)");
foreach (var link in links)
{
Console.WriteLine(link);
}
Browser-side code can read the rendered DOM, but it cannot directly access C# variables unless you pass values as function arguments. Be careful with pages that use frames: an element inside an iframe belongs to that frame’s document and must be queried through the appropriate frame.
Connect to an existing or remote browser
Launching a local browser is not the only deployment model. PuppeteerSharp exposes Puppeteer.ConnectAsync with ConnectOptions for a browser that provides a WebSocket endpoint:
using PuppeteerSharp;
var browser = await Puppeteer.ConnectAsync(new ConnectOptions
{
BrowserWSEndpoint = Environment.GetEnvironmentVariable("BROWSER_WS")
?? throw new InvalidOperationException("BROWSER_WS is missing")
});
await using (browser)
await using (var page = await browser.NewPageAsync())
{
await page.GoToAsync("https://example.com");
Console.WriteLine(await page.TitleAsync());
}
Keep the endpoint secret and restrict network access to it. The project site advertises both Chrome DevTools Protocol (CDP) and WebDriver BiDi support; protocol details can change between releases, so consult the version-specific documentation when relying on protocol behavior.
Take screenshots beyond the basics
Capture an element
To capture only a component, locate it and use the element’s screenshot operation:
var card = page.Locator("article.product-card");
await card.ScreenshotAsync("card.png");
If the element is hidden, outside the rendered layout, or covered by another layer, make it visible first and wait for the final state.
Allow fonts and lazy content to settle
For a page that loads assets after navigation, wait for the relevant selector, a page function, or a short, justified delay before capturing. A full-page capture does not guarantee that an image loaded only after scrolling has already been fetched; trigger the page’s lazy-loading behavior when necessary.
Generate PDFs
PuppeteerSharp supports PDF generation through PdfAsync. Chrome headless is currently required for this operation:
await page.GoToAsync("https://example.com/invoice");
await page.PdfAsync("invoice.pdf", new PdfOptions
{
Format = PaperFormat.A4,
PrintBackground = true,
Landscape = false,
MarginOptions = new MarginOptions
{
Top = "16mm",
Right = "16mm",
Bottom = "16mm",
Left = "16mm"
}
});
The API reference distinguishes generating a PDF from a page from navigating to a PDF document: headless mode does not support navigating to a PDF document, while rendering the current HTML page to PDF is supported.
Wait for web fonts
If fonts are loaded from a CDN, the project README demonstrates waiting for document.fonts.ready before calling PdfAsync. Without that wait, the PDF may contain no rendered text:
Rank #4
await page.GoToAsync("https://example.com/report");
await page.EvaluateExpressionAsync("document.fonts.ready");
await page.PdfAsync("report.pdf", new PdfOptions
{
Format = PaperFormat.A4,
PrintBackground = true
});
Common failures and fixes
Browser executable not found
Cause: the compatible browser was never downloaded, the cache is unavailable, or the process lacks permission to execute it.
Fix: run BrowserFetcher.DownloadAsync() during deployment, preserve the cache in the runtime image, and verify executable permissions. If you provide an executable path manually, make sure it matches the PuppeteerSharp package’s supported browser expectations.
Navigation timeout
Cause: a slow server, a page that never becomes idle, a blocked network request, or an overly short timeout.
Fix: increase the timeout for the known workload, use a selector-based readiness check, and log the URL and the last operation. Do not hide persistent failures by setting an unlimited timeout.
Element cannot be clicked or filled
Cause: the selector matches nothing, the element is still rendering, it is covered by a modal, or it is inside an iframe.
Fix: wait for the selector, inspect the rendered DOM, dismiss the overlay, and query the correct frame. Prefer a locator with a stable selector.
Blank or incomplete screenshot
Cause: capture occurred before fonts, images, or client-side data finished loading.
Fix: wait for a meaningful selector or function, wait for document.fonts.ready when typography matters, and exercise lazy-loading paths before a full-page capture.
Best Value
PDF has missing text
Cause: web fonts were not ready when Chrome printed the page.
Fix: evaluate document.fonts.ready before PdfAsync. Also confirm that the operation is PDF generation from an HTML page, not navigation to a PDF URL.
Linux launch fails
Cause: missing system libraries, sandbox restrictions, or the X-server requirement documented by the project for Linux.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Fix: check the current PuppeteerSharp README for your distribution, install the listed dependencies, and validate the same container or VM image used in production.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Performance, reliability, and cost considerations
- Reuse deliberately: browser startup is expensive. A long-lived browser with isolated pages can reduce startup overhead, but close pages and limit concurrency to avoid memory growth.
- Bound every wait: navigation, selectors, functions, and external work should have timeouts. Record URL, selector, and exception details for diagnosis.
- Keep browser and package aligned: the bundled Chromium is the project’s guaranteed pairing; changing either side can introduce rendering or protocol differences.
- Separate jobs: use a fresh page or browser context for unrelated users and clear authentication state when data isolation matters.
- Make output deterministic: fix viewport, timezone, locale, fonts, and network-dependent data where reproducible screenshots or PDFs are required.
Or skip the browser setup
If your goal is simply a clean screenshot or PDF rather than maintaining Chromium in your C# service, ScreenshotNeo provides a website screenshot API and MCP server. One GET request returns PNG, JPEG, WebP, or PDF.
It removes cookie and consent banners, newsletter popups, and chat widgets before capture. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing status. Its MCP server includes take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients.
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(`HTTP ${res.status}`);
const bytes = new Uint8Array(await res.arrayBuffer());
See the complete parameter list and response behavior in the ScreenshotNeo documentation. Options include full-page and element capture, dark mode, device presets, retina scale, PDF paper and page ranges, custom CSS and JavaScript, clicks, waits, request blocking, headers, cookies, user agents, timezone, geolocation, transparent backgrounds, resizing, TTL caching, signed links, asynchronous webhooks, bulk capture of up to 100 URLs per call, usage reporting, and an OpenAPI specification.
Free tools Windows power users keep installed
One-click scans. No signup required.
The Free plan includes 1,000 screenshots per month without a card. Paid plans start at $5 for 3,000 screenshots; every feature is included on every plan. Create a free ScreenshotNeo account to try it.
Frequently Asked Questions
Is PuppeteerSharp the official Puppeteer package for .NET?
No. PuppeteerSharp is a community .NET port of the official Node.js Puppeteer API; install it as the PuppeteerSharp NuGet package.
Can PuppeteerSharp automate an already running browser?
Yes. Use Puppeteer.ConnectAsync with ConnectOptions and a browser WebSocket endpoint instead of LaunchAsync.
Can headless PuppeteerSharp open a PDF URL?
The API notes that headless mode does not support navigating to a PDF document. Generate a PDF from an HTML page with PdfAsync instead.
The Tool Desk
Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →Why does a PDF sometimes contain no text?
Web fonts may not have finished loading. Evaluate document.fonts.ready before calling PdfAsync.
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.

