ScreenshotNeo

BlogHow-to

How to Screenshot a Website with Playwright in Python on Windows

Install Playwright on Windows, capture a website’s viewport, full page, or one element, and troubleshoot common browser and event-loop errors.

By the ScreenshotNeo team4 October 20267 min read

To screenshot a website with Playwright in Python on Windows, install the Python package and its browser binaries, open a page, then call page.screenshot(). Use full_page=True for the entire scrollable document, or take a screenshot from a locator to capture one element.

1. Install Playwright and its browsers

Open PowerShell in your project directory. Playwright’s Python package and the browser binaries it launches are separate installations.

python -m pip install --upgrade pip
python -m pip install playwright
python -m playwright install

If the playwright command is available in your shell, playwright install is equivalent. Using python -m playwright helps ensure the install command runs under the same Python interpreter that received the package.

Playwright lists Python 3.8 or higher and Windows 11+, Windows Server 2019+, or WSL among supported environments. Check the current installation requirements if you use an older Windows version.

2. Capture a viewport screenshot

Create screenshot.py and run it with python screenshot.py. This synchronous example captures the currently visible browser viewport.

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", wait_until="load")
    page.screenshot(path="screenshot.png")
    browser.close()

The relative output path is resolved from the process’s current working directory. For a fixed Windows destination, use a raw string:

page.screenshot(path=r"C:\Users\YourName\Pictures\site.png")

Replace the example URL with the page you are authorized to access. If the site renders important content after its load event, the next section explains how to wait for it.

3. Choose what to capture

Visible viewport

The default screenshot covers the visible viewport:

page.screenshot(path="viewport.png")

Entire scrollable page

Set full_page=True to capture the full scrollable document in one tall image:

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

This captures a long image, which can use substantial memory and be awkward to view or print. For a page that changes as you scroll, consider whether lazy-loaded content has appeared before capturing; a full-page screenshot does not guarantee every site-specific scroll-triggered component has loaded.

One element

Use a locator to capture a specific element, such as a header or product card. Locator screenshots wait for the element to be visible and bring it into view as needed.

page.locator("header").screenshot(path="header.png")

Choose a selector that identifies one intended element. If a selector matches several nodes, narrow it or use locator methods such as first where appropriate. The locator screenshot API is preferred over the discouraged ElementHandle screenshot API.

Keep the screenshot in memory

Omit path to receive image bytes instead of writing a file. This is useful when passing the result to an image-processing step.

image_bytes = page.screenshot()
# Pass image_bytes to your image-processing code.

4. Wait for the page to be ready

A navigation event does not always mean that a single-page application has finished rendering its content. Wait for a meaningful selector when possible, or use a short delay only when the page has no reliable readiness signal.

page.goto("https://example.com", wait_until="domcontentloaded")
page.locator("main article").wait_for(state="visible", timeout=15000)
page.screenshot(path="article.png")

For a known animation or changing element, locator screenshots accept animations="disabled" to reduce motion-related variation:

page.locator(".chart").screenshot(
    path="chart.png",
    animations="disabled",
)

Use a selector that corresponds to the actual content you need. A fixed sleep can make a script slower while still failing on a slower page.

5. Set viewport, browser, and output options

Set the viewport when the result must have predictable dimensions. You can also choose a different Playwright browser engine when your task depends on browser-specific rendering.

from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch()
    page = browser.new_page(viewport={"width": 1440, "height": 900}, device_scale_factor=1)
    page.goto("https://example.com")
    page.screenshot(path="desktop.png", type="png")
    browser.close()

Playwright supports Chromium, Firefox, and WebKit. The screenshot workflow is the same; use another engine when you need to inspect how that engine renders the site. Microsoft Edge can be launched through Chromium with p.chromium.launch(channel="msedge") when Edge is installed.

Relevant screenshot choices include:

  • full_page=True captures the scrollable document rather than just the viewport.
  • type="png", type="jpeg", or type="webp" selects an image format supported by the screenshot API. PNG is the default. JPEG and WebP are lossy formats; use the documented quality option where supported for those formats.
  • clip limits a page screenshot to a specified rectangle when you need a region rather than the whole viewport.
  • Locator screenshots support animations="disabled" and scale="css" or scale="device". CSS scale produces one output pixel per CSS pixel; device scale uses device pixels.
  • Use a page context with a chosen viewport and device scale factor to control the rendering dimensions and pixel density.

Check the current screenshot guide and Locator API for exact parameter details and supported combinations.

6. Use the asynchronous Python API

For an async application, await Playwright operations and run the coroutine with asyncio.run() in a normal script:

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()
        await page.goto("https://example.com", wait_until="load")
        await page.screenshot(path="screenshot.png", full_page=True)
        await browser.close()

asyncio.run(main())

On Windows, Playwright’s async driver needs a Proactor event loop for subprocess support. Python 3.8+ uses this by default. If a framework or older/customized setup replaced the loop, restore the Proactor loop where your application creates its event loop. Avoid calling asyncio.run() inside an environment that already owns a running event loop; use that environment’s async entry point instead.

See Playwright’s Python library guide for the sync and async workflows and the Windows event-loop note.

7. Troubleshooting

Symptom Likely cause Fix
Browser executable is missing The Python package is installed, but its browser binaries are not, or they do not match this Playwright version. Run python -m playwright install with the same Python environment used by the script.
Playwright command is not recognized The executable directory is not on the shell’s PATH. Use python -m playwright install from the intended environment.
The screenshot only shows the visible area The default is a viewport capture. Pass full_page=True, or use a locator screenshot for a single element.
Output file is not where expected A relative path is relative to the process working directory, which may differ from the script’s folder. Use an absolute path such as r"C:\Users\YourName\Pictures\site.png", or inspect the process working directory.
Async subprocess or event-loop error on Windows A custom loop policy may have replaced the Proactor loop required by Playwright’s Windows async driver. Use the default Proactor loop on supported Python, and check framework or application loop configuration.
Browser starts but page content is incomplete The page may render after navigation, depend on an API request, or reveal content only after interaction or scrolling. Wait for a meaningful locator or the specific state your capture needs before taking the screenshot.
Element screenshot is inconsistent The element may be animating, hidden, or matched ambiguously. Use a unique visible locator and consider animations="disabled".
Browser version mismatch after updating Playwright Playwright browser binaries are versioned alongside the package. Run the browser install command again. Playwright documents that each version needs specific browser binaries; see browser management.

8. Performance, reliability, and cost

A local Playwright capture requires a Python environment and downloaded browser binaries. The first setup includes that browser download; subsequent captures reuse the installed browser. Reuse one browser process for multiple pages in a batch rather than launching a new process for every URL, and close pages and browsers when finished.

Full-page images can be much larger than viewport captures, especially at high device scale. Choose the smallest viewport and output scope that meets the task. For repeatable results, pin your project dependencies, install browsers for that Playwright version, wait for a specific page state, and keep viewport and device scale consistent. Network latency and the target site’s own rendering affect capture time; avoid arbitrary long sleeps when a selector or response condition can express readiness.

The Playwright library itself is available through pip, and the browser installation consumes local disk and runtime resources. There is no per-screenshot API charge in this local workflow; your costs are the machine, storage, and maintenance of the script and browser versions.

9. Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server from Yorker Media. One GET request returns a PNG, JPEG, WebP, or PDF. See the ScreenshotNeo API documentation for request options.

import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://example.com"},
    timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)

ScreenshotNeo removes cookie and consent banners, newsletter popups, and chat widgets before capture. Bot checks, blank pages, failed loads, timeouts, and cache hits are never billed. Its MCP server lets AI agents use screenshot tools. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 screenshots.

Sign up free for 1,000 screenshots a month, with no card required.

10. Frequently asked questions

Can I use this to capture a page that requires login?

Yes, if you configure the browser context and navigation for the authorized session. Keep credentials out of source code and avoid saving sensitive screenshots to shared locations.

Playwright captures what the page renders. You can interact with the site’s consent controls in your script where permitted; the basic examples do not automatically dismiss banners.

Where does Playwright store its browser downloads on Windows?

The default browser cache is under %USERPROFILE%\AppData\Local\ms-playwright. Browser management options are described in the official browser documentation.

Can I capture a PDF instead of an image?

Playwright’s page PDF workflow is Chromium-specific. Consult the current Page API for PDF options and limitations.