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

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

Call GetContentAsync() only after the page reaches a condition that represents the content you need. A reliable Puppeteer Sharp sequence is: navigate with GoToAsync, wait for a required selector or a truthy application-state expression, then retrieve the current document HTML.

await page.GoToAsync(url);
await page.WaitForSelectorAsync("#results");
var html = await page.GetContentAsync();

GetContentAsync() returns the full HTML, including the doctype. Navigation finishing is not the same as a JavaScript application finishing its render, so the wait condition is the important part.

What “rendered HTML” means in Puppeteer Sharp

A JavaScript-heavy site may send a nearly empty document, then add headings, prices, rows, or other content after scripts execute. Puppeteer Sharp controls a Chromium page, so you can read the DOM after those scripts modify it. GetContentAsync() serializes the page’s current HTML; it does not fetch a separate server-side version.

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

The result includes the document type declaration and the markup currently present in the page. If you need only one value, querying that element and reading its innerText or another property can be more efficient than serializing the whole document.

A complete extraction pattern

1. Create a browser and page

The browser-launch details depend on how your project installs or locates Chromium. The extraction logic begins once you have an IBrowser and IPage. Dispose both when the job ends.

using PuppeteerSharp;

await new BrowserFetcher().DownloadAsync();
await using var browser = await Puppeteer.LaunchAsync(new LaunchOptions
{
    Headless = true
});

await using var page = await browser.NewPageAsync();

Use the browser/runtime and Puppeteer Sharp package versions that your project supports, and verify signatures against the version you install.

2. Navigate to the URL

var url = "https://example.com/products";
await page.GoToAsync(url);

The documented default navigation success condition is Load. You can provide one or more WaitUntilNavigation events when navigation semantics matter, but a lifecycle event alone does not prove that your application’s data is on screen.

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

3. Wait for the content-specific signal

await page.WaitForSelectorAsync("#results");

WaitForSelectorAsync waits for a matching element to be added to the DOM. Choose a selector that means “the result I need exists,” rather than a generic wrapper that appears before data is inserted.

4. Read the current document

var html = await page.GetContentAsync();
await File.WriteAllTextAsync("rendered.html", html);

At this point, html is the serialized, post-JavaScript document. It is suitable for parsing, archiving, or passing to another processing step.

Rank #2
Sale
HTML and CSS: Design and Build Websites
  • HTML CSS Design and Build Web Sites
  • Comes with secure packaging
  • It can be a gift option

Waiting strategies and when to use each

Strategy Best use Risk or limitation
WaitForSelectorAsync A stable element appears when the required content is ready. The element may exist before its children or text are populated.
WaitForFunctionAsync or WaitForExpressionAsync Readiness depends on a state flag, row count, text, or another custom condition. The expression must match the site’s real state and remain valid as the UI changes.
WaitForNetworkIdleAsync A page has a bounded burst of requests and no better signal is available. Background polling can prevent completion; requests ending does not guarantee the desired DOM exists.
Fixed delay A last-resort workaround for a site with no observable readiness signal. It is either wasteful on fast runs or too short on slow runs, so it is less reliable than a content-based wait.

Selector wait

await page.WaitForSelectorAsync("article[data-loaded='true']");
var html = await page.GetContentAsync();

Use a selector tied to the outcome: a results table, a populated article, or a loading marker that has been replaced. If the selector can exist in an empty shell, add a stronger condition.

Custom JavaScript condition

await page.WaitForFunctionAsync(
    "() => document.querySelector('#results')?.children.length > 0");
var html = await page.GetContentAsync();

This expression is illustrative. Adapt it to the target DOM and application state. You can test a global readiness flag, a minimum number of rows, or non-empty text.

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.

Network idle

await page.GoToAsync(url);
await page.WaitForNetworkIdleAsync();
var html = await page.GetContentAsync();

Network idle can help when rendering follows a finite request sequence. Treat it as a candidate signal, not proof of content readiness: analytics, polling, sockets, or delayed rendering can make it misleading. The API documentation also notes that Networkidle0 and Networkidle2 are not supported for SetContentAsync; use a supported setting or a separate selector/expression wait for that method.

Timeouts, navigation, and slow pages

Puppeteer Sharp’s DefaultTimeout applies to waits such as selector, function, and expression waits, as well as navigation methods. The documented default for GoToAsync is 30 seconds; setting it to zero disables that timeout. Make the choice deliberate: a longer limit can accommodate slow pages, while an unlimited wait can leave workers stuck indefinitely.

page.DefaultTimeout = 45_000;
await page.GoToAsync(url);
await page.WaitForSelectorAsync("#results");
var html = await page.GetContentAsync();

For production jobs, combine a realistic timeout with cancellation at the worker or request level, log the URL and failed readiness condition, and close the page after an exception.

Full reusable C# example

using PuppeteerSharp;

static async Task<string> GetRenderedHtmlAsync(string url)
{
    await new BrowserFetcher().DownloadAsync();
    await using var browser = await Puppeteer.LaunchAsync(new LaunchOptions
    {
        Headless = true
    });
    await using var page = await browser.NewPageAsync();

    page.DefaultTimeout = 45_000;
    await page.GoToAsync(url);
    await page.WaitForFunctionAsync(
        "() => document.querySelector('#results')?.children.length > 0");

    return await page.GetContentAsync();
}

var html = await GetRenderedHtmlAsync("https://example.com/products");
await File.WriteAllTextAsync("rendered.html", html);

Replace the URL and readiness expression with conditions specific to the page. If an element’s presence is sufficient, use WaitForSelectorAsync instead.

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

Extracting one element instead of the whole document

Full HTML is useful when you need the complete post-render DOM. For a single value, query the element after the same readiness wait:

await page.GoToAsync(url);
await page.WaitForSelectorAsync(".price");
var price = await page.QuerySelectorAsync(".price");
var text = price is null ? null : await price.EvaluateFunctionAsync<string>(
    "el => el.innerText");

This avoids treating the entire document as necessary and makes the intended extraction explicit.

Common failures and fixes

HTML contains only an app shell

Cause: navigation completed before the data-rendering code finished. Fix: wait for a result selector or a truthy function that checks populated content, then call GetContentAsync().

WaitForSelectorAsync times out

Cause: the selector is wrong, the page failed, content is inside a frame, or the application never reaches that state. Fix: inspect the selector in the rendered page, verify the URL and response, check for frames, and select a stable readiness marker. Increase the timeout only when the page is legitimately slow.

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.
Rank #4
Sale
Web Design with HTML, CSS, JavaScript and jQuery Set
  • Brand: Wiley
  • Set of 2 Volumes
  • A handy two-book set that uniquely combines related technologies Highly visual format and accessible language makes these books highly effective learning tools Perfect for beginning web designers and front-end developers

Network-idle waiting never completes

Cause: background requests, polling, or long-lived connections. Fix: replace network idle with a selector or custom expression tied to the required content.

The selector appears but text is empty

Cause: a container is inserted before its children or data. Fix: wait for a non-empty text condition, a minimum child count, or the site’s loaded-state attribute.

Navigation times out

Cause: a slow server, blocked resource, redirect loop, or unsuitable timeout. Fix: inspect the target and redirects, set a bounded timeout appropriate for the workload, and handle the failure rather than disabling timeouts globally.

Returned markup differs from what you see

Cause: viewport, cookies, locale, authentication, timing, or client-side conditions differ. Fix: reproduce the required page state in Puppeteer Sharp and wait for the same application signal. A screenshot is visual evidence; GetContentAsync() is the DOM serialization.

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

Performance and reliability practices

  • Reuse a browser process for multiple pages when isolation requirements allow it, but create and close pages per job.
  • Prefer the narrowest readiness condition that proves your output is ready.
  • Avoid arbitrary long sleeps; they increase latency without guaranteeing correctness.
  • Keep timeouts finite and record which phase failed: navigation, readiness, or extraction.
  • Use element-level extraction when full markup is unnecessary.
  • Expect pages to change their selectors and loading states; keep readiness selectors close to the scraper code and test them against the site’s current markup.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Or skip the browser setup

If your goal is a clean image or PDF rather than DOM text, ScreenshotNeo provides a website screenshot API and MCP server. A single request captures the target after rendering:

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

See the ScreenshotNeo documentation for parameters and response headers. Before capture, it accepts cookie or consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each step can be disabled. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and headers identify the page verdict and billing result. Its MCP server exposes take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients. The Free plan includes 1,000 screenshots per month without a card; paid plans start at $5 for 3,000 shots. Sign up free.

When to choose each approach

  • Use Puppeteer Sharp when you need rendered DOM HTML, element text, authenticated browser interaction, or custom extraction logic.
  • Use a selector or expression wait when correctness depends on a particular application state.
  • Use ScreenshotNeo when the deliverable is a cleaned screenshot or PDF and you do not want to maintain Chromium setup, popup handling, or capture infrastructure.

Frequently Asked Questions

Does GetContentAsync execute JavaScript itself?

No. JavaScript runs as part of the Chromium page lifecycle; GetContentAsync then serializes the DOM that exists when you call it.

Can I use Puppeteer Sharp with pages that keep making requests?

Yes. Avoid relying solely on network-idle completion and wait for a selector or application-state expression that proves the required content is ready.

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

Should I always return the entire HTML document?

No. If you need one field or element, query it after the readiness wait and extract only that value.

The Bottom Line

Navigate, wait for the signal that proves your target content is rendered, and then call GetContentAsync(). That separation between navigation and readiness is what prevents JavaScript-rendered data from disappearing from your extraction.

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.