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

Use Playwright when the PNG must look like a real browser render. Install the Python package and its browser binaries, open either a URL or an HTML string, wait for the content your page needs, and call page.screenshot(path='output.png'). The same API can capture a viewport, a full scrolling page, one element, transparent output, or image bytes held in memory.

Install Playwright and its browsers

Playwright needs both the Python package and browser binaries. Run these commands in the virtual environment used by your application:

python -m pip install playwright
python -m playwright install

The browser-install command can install Chromium, Firefox, and WebKit. Chromium is a practical default for most HTML-to-PNG jobs. Playwright launches headless browsers by default; set headless=False while diagnosing a page visually.

Choose one programming style and keep it consistent with the rest of your application. The synchronous API is easiest for scripts. The asynchronous API fits an asyncio service that already performs concurrent work.

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.

Convert a web page URL to PNG

This complete synchronous example navigates to a URL and writes a PNG:

from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch()
    page = browser.new_page()
    page.goto('https://example.com')
    page.screenshot(path='output.png')
    browser.close()

After the script exits, output.png contains the current viewport. The file extension selects PNG, which is Playwright’s default screenshot format. Navigation returning does not guarantee that every image, animation, or client-rendered component has finished; add a readiness condition for pages that load content after navigation.

Convert an HTML string to PNG

When the markup is already in Python, use page.set_content() instead of navigating to a remote URL:

from playwright.sync_api import sync_playwright

html = '''
<!doctype html>
<html>
  <head>
    <meta charset="utf-8">
    <style>
      body { margin: 0; font-family: sans-serif; background: #f4f6f8; }
      .card { width: 640px; padding: 32px; color: #17202a; }
      h1 { margin-top: 0; }
    </style>
  </head>
  <body>
    <section class="card">
      <h1>Rendered from a Python string</h1>
      <p>Playwright turns this markup into a browser screenshot.</p>
    </section>
  </body>
</html>
'''

with sync_playwright() as p:
    browser = p.chromium.launch()
    page = browser.new_page(viewport={"width": 800, "height": 500})
    page.set_content(html)
    page.screenshot(path='html-string.png')
    browser.close()

set_content assigns the supplied markup to the page. External stylesheets, fonts, images, and scripts referenced by that markup still need to be reachable from the browser process. For a self-contained result, inline the CSS and use data URLs or otherwise ensure that referenced assets are available.

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

Choose what the PNG contains

Capture the viewport

page.screenshot(path='viewport.png') captures the visible viewport. Set its dimensions when layout must be repeatable:

page = browser.new_page(viewport={"width": 1440, "height": 900}, device_scale_factor=1)

A larger viewport can expose responsive breakpoints that are hidden on a phone-sized page. Keep the viewport and scale fixed when comparing screenshots in tests.

Capture the entire scrolling page

Use full_page=True to render the whole scrollable page as one tall image:

page.screenshot(path='full-page.png', full_page=True)

Very long documents produce very large bitmap dimensions and can consume substantial memory. For reports or pages with thousands of rows, consider capturing sections separately or generating a PDF instead of one enormous PNG.

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

Capture one element

Locate the component you need and screenshot only that element:

page.locator('.invoice').screenshot(path='invoice.png')

The locator must resolve to the intended element. A selector that matches multiple nodes or an element that is hidden can cause a failure; make the selector specific and ensure the component is displayed before capture.

Keep the image in memory

Omit path when the next step is an upload, database write, or HTTP response:

png_bytes = page.screenshot()
# send png_bytes to your storage or response layer

This avoids a temporary file. The returned value is PNG bytes unless you request another format.

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

Make the background transparent

Set omit_background=True when the page should retain alpha transparency:

page.screenshot(path='transparent.png', omit_background=True)

Transparent-background capture applies to PNG and other formats that support alpha; it does not apply to JPEG.

Wait for dynamic HTML before taking the shot

Single-page applications often render a shell first and fill it with data later. Wait for a condition that represents readiness on your page instead of relying on one fixed sleep:

from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch()
    page = browser.new_page()
    page.goto('https://example.com/dashboard')
    page.wait_for_selector('[data-rendered="true"]')
    page.screenshot(path='dashboard.png', full_page=True)
    browser.close()

Choose a selector that appears only after the important content is present. If a page has several independent widgets, wait for the last required widget or for an application-specific completion marker. A navigation event alone is not a universal signal that remote assets and client-rendered components are ready.

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

Use the asynchronous Python API

In an asyncio application, use Playwright’s async API rather than blocking the event loop:

import asyncio
from playwright.async_api import async_playwright

async def main():
    async with async_playwright() as p:
        browser = await p.chromium.launch()
        page = await browser.new_page(viewport={"width": 1280, "height": 800})
        await page.goto('https://example.com')
        await page.screenshot(path='async-output.png', full_page=True)
        await browser.close()

asyncio.run(main())

The async and sync APIs expose the same screenshot choices. Keep browser shutdown in the same lifecycle block so an exception does not leave browser processes running.

Build a reusable conversion function

For repeated jobs, wrap setup and capture in a function that makes the important choices explicit:

from pathlib import Path
from playwright.sync_api import sync_playwright

def html_to_png(html: str, output: str, width: int = 1200, height: int = 800,
                full_page: bool = False) -> None:
    output_path = Path(output)
    output_path.parent.mkdir(parents=True, exist_ok=True)

    with sync_playwright() as p:
        browser = p.chromium.launch()
        try:
            page = browser.new_page(viewport={"width": width, "height": height})
            page.set_content(html)
            page.screenshot(path=str(output_path), full_page=full_page)
        finally:
            browser.close()

html_to_png('<h1>Hello</h1>', 'renders/hello.png')

The finally block closes the browser even when markup, rendering, or file output raises an exception. In a high-volume worker, reuse a browser process across jobs and create a fresh page or context for each isolated render; this is an operational recommendation, not a guarantee that every site is safe to share.

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

Browser, CSS, and asset considerations

  • Browser choice: Chromium, Firefox, and WebKit can be installed. Select the engine that matches the browser behavior you need to reproduce.
  • Fonts: A missing font changes line wrapping and therefore pixel output. Install required fonts in the execution environment or use web fonts that the browser can reach.
  • Cross-origin assets: Images, stylesheets, and scripts must be accessible to the browser. Authentication, private networks, certificate errors, and blocked requests can leave a page incomplete.
  • Animations: Capture at a deterministic state by waiting for the application to signal readiness and, where appropriate, disabling animation in your test CSS.
  • Security: Treat arbitrary HTML and URLs as untrusted input. Isolate rendering workers and avoid giving untrusted pages access to sensitive credentials or internal network resources.

Is WeasyPrint a direct HTML-to-PNG replacement?

WeasyPrint is useful when your target is print-oriented HTML/CSS and PDF output. Its API reference covers embedded and linked stylesheets and notes that presentational hints are not enabled by default. The referenced material documents PDF generation, but it does not establish a direct HTML-to-PNG workflow. Do not substitute it for a browser screenshot when you need JavaScript execution, browser layout behavior, full-page capture, or element screenshots without first verifying the exact requirement against its current documentation.

Troubleshoot common failures

Symptom Likely cause Fix
Executable doesn't exist or a browser launch error The Python package is installed but browser binaries are not. Run python -m playwright install in the same environment used by the script.
PNG shows a loading spinner or empty shell Capture happened before client-side rendering completed. Wait for a page-specific selector or completion marker before calling screenshot.
Images or fonts are missing Asset URLs are unreachable, require authentication, or are blocked by the runtime. Verify URLs from the render environment, provide the required access, or inline critical assets.
Element screenshot fails The selector matches nothing, matches hidden content, or identifies more than one unintended node. Use a unique selector and confirm the element is visible before capture.
Output is unexpectedly cropped You captured the viewport rather than the full document. Pass full_page=True, or capture the specific element that defines the required bounds.
Transparent output still looks white The page or a container paints its own white background. Remove that CSS background; omit_background=True only omits the browser’s default background.
Different machines produce different pixels Viewport, device scale, fonts, browser engine, or loaded assets differ. Pin those inputs and run captures in a consistent environment.
Browser processes remain after an error Shutdown code was skipped. Use a context manager or try/finally around browser usage.

Performance, reliability, and cost planning

  • Startup overhead: Launching a browser for every image is simple but slower than keeping a worker alive. Reuse a browser where your isolation model allows it, while still closing pages and contexts promptly.
  • Memory: Full-page and high-resolution captures allocate large bitmaps. Limit page height, viewport scale, or concurrency when workers approach their memory limit.
  • Determinism: Fix viewport dimensions, browser engine, device scale factor, fonts, timezone, and test data if PNGs are used for visual regression.
  • Retries: Retry transient navigation or asset failures with a bounded policy. Do not hide permanent selector errors behind endless retries.
  • Billing: A local Playwright workflow uses your own Python runtime, browser binaries, CPU, memory, and storage. Measure those resources in the environment where the script will run rather than assuming desktop behavior matches a server.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Or skip the browser setup

ScreenshotNeo provides a website screenshot API and MCP server when you want one request instead of packaging Playwright and browser binaries. Its API accepts a URL and returns PNG, JPEG, WebP, or PDF. Before capture it accepts cookie or consent banners as a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be switched off. Bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and whether the shot was billed.

Use the API documentation at https://screenshotneo.com/docs/ for parameters. This cURL request saves a WebP image:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

The equivalent Python request is:

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)

In 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(`Screenshot failed: ${res.status}`);
const fs = await import('node:fs/promises');
await fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));

ScreenshotNeo also supports full-page capture with lazy images loaded, CSS-selector element capture, dark mode, 12 device presets or custom viewports, retina scale, PDF paper and margin controls, custom CSS and JavaScript, pre-capture clicks, hidden selectors, waits for selectors, delays or network idle, request and resource blocking, custom headers, cookies, user agents and 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, a usage API, an OpenAPI specification, and parameter names used by other screenshot APIs for easier migration. An MCP server exposes take_screenshot, get_page_info, and capture_pdf to Claude, Cursor, and other MCP clients.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Plan Monthly allowance Price
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 gives two months free. Create a ScreenshotNeo account to get 1,000 screenshots a month free with no card.

Frequently asked questions

Does converting HTML to PNG preserve links and selectable text?

No. A PNG is a bitmap. Links, text selection, semantics, and accessibility metadata are not retained; keep the original HTML or generate a PDF when those properties matter.

Can I use a local HTML file?

Yes. Read the file into a Python string and pass it to page.set_content(), or navigate to a permitted local file URL in an environment configured for that access. Ensure relative asset paths resolve from the location you intend.

Should I use a viewport or full-page screenshot for a social-card image?

Use a fixed viewport for a card with known dimensions. Use full_page=True only when the complete document, rather than a designed canvas, is the asset you need.

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

Why does a browser screenshot differ from a PDF renderer?

They solve different layout problems. Browser screenshots execute web-page layout and JavaScript in a browser engine, while print-oriented renderers may implement a different CSS subset and pagination model. Choose the output whose rendering rules match your requirement.

Frequently Asked Questions

Can a PNG retain clickable links or selectable text?

No. PNG is a bitmap; retain the HTML or generate a PDF when interaction, selection, or semantics are required.

Can I render HTML stored in a local file?

Yes. Read it into a string and pass it to page.set_content(), making sure relative assets resolve correctly.

Which capture mode suits a fixed social-card design?

Use a fixed viewport. Reserve full_page=True for documents whose complete scrollable height is the intended image.

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

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.