How to Convert a URL to a WebP Image with Playwright
Save a rendered web page as WebP with Playwright. Learn how to set quality, capture full pages, return image bytes, and handle common issues.
To make a WebP image of a rendered web page with Playwright, navigate a browser page to the URL and call page.screenshot({ type: 'webp' }). Set path to save directly to a file, or omit it to get a Node.js Buffer. Playwright documents quality 100 as lossless WebP; lower quality values use lossy compression. See the Playwright Page API.
This captures what the browser renders. It does not download an existing image file from a URL and transcode that asset. For image-asset conversion, download the image and use an image-processing library with WebP encoding.
1. Install Playwright
The examples below use Node.js. Create a project, install Playwright, and install its Chromium browser:
mkdir url-to-webp
cd url-to-webp
npm init -y
npm install playwright
npx playwright install chromium
Save the standalone example below as capture.js, then run node capture.js https://example.com. Replace the example URL with the page you are authorized to capture.
2. Save a URL as a WebP file
const { chromium } = require('playwright');
async function main() {
const url = process.argv[2] || 'https://example.com';
const browser = await chromium.launch();
try {
const page = await browser.newPage();
await page.goto(url, { waitUntil: 'load', timeout: 30_000 });
await page.screenshot({ path: 'output.webp', type: 'webp' });
console.log('Saved output.webp');
} finally {
await browser.close();
}
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
The type option explicitly selects WebP. Playwright can also infer the format from a supported output file extension, but specifying type: 'webp' makes the intent clear. The documented screenshot formats are PNG, JPEG, and WebP; PNG is the default. Screenshot options
3. Choose viewport, full-page scope, and quality
By default, the screenshot covers the visible viewport. Set the viewport before navigation if the output needs a particular layout size. Use fullPage: true to capture the page’s full scrollable height.
const page = await browser.newPage({
viewport: { width: 1440, height: 1000 },
deviceScaleFactor: 1,
});
await page.goto(url, { waitUntil: 'load' });
await page.screenshot({
path: 'full-page.webp',
type: 'webp',
quality: 85,
fullPage: true,
});
| Option | Use | Notes |
|---|---|---|
type |
Choose 'webp' |
Also supports 'png' and 'jpeg'. |
quality |
Control WebP compression | 100 is the documented default and produces lossless WebP; lower values are lossy. Quality does not apply to PNG. Choose a value based on your file-size and visual-quality needs. |
path |
Write output to disk | Use a .webp extension to make the output type obvious. |
fullPage |
Capture beyond the viewport | Set it to true for the full scrollable page. |
viewport |
Control the browser layout dimensions | Set it when the page must render at a specific CSS viewport size. |
deviceScaleFactor |
Control pixel density | Set when creating the browser context or page; higher density produces more pixels and can increase output size. |
For large pages, full-page output can be much taller and larger than a viewport capture. Check that the target page’s layout works at the chosen width: responsive breakpoints can change the result.
4. Return WebP bytes instead of writing a file
Omit path to receive the screenshot bytes. In Node.js, Playwright returns a Buffer, which you can send in an HTTP response, store, or pass to another image-processing step.
const imageBuffer = await page.screenshot({
type: 'webp',
quality: 90,
});
// Example: write the returned Buffer to disk.
const fs = require('node:fs/promises');
await fs.writeFile('output.webp', imageBuffer);
The buffer route is useful when an application needs to choose the destination at runtime. Playwright’s screenshots guide describes returning image bytes for further processing.
5. Wait for the page state you need
A screenshot taken too early can miss content that appears after navigation, such as a client-rendered section or an image. Pick a navigation condition and, when necessary, wait for a page-specific signal before capture.
await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 30_000 });
await page.locator('main').waitFor({ state: 'visible', timeout: 10_000 });
await page.screenshot({ path: 'ready.webp', type: 'webp' });
waitUntil controls when navigation is considered complete; it does not prove that every application-specific task has finished. Prefer waiting for a selector that represents the content you need. If the site relies on a delayed animation or data fetch, use a targeted wait appropriate to that page. Avoid depending on arbitrary long delays when a visible element can signal readiness.
Some pages lazy-load images as they enter the viewport. A viewport screenshot may only include visible content. For full-page output, use fullPage: true, and verify that the page’s own loading behavior has completed before capture.
6. Use WebP for a Playwright Test visual snapshot
If the goal is a visual regression baseline inside Playwright Test, use toHaveScreenshot with a .webp name. This is a test assertion workflow, distinct from using page.screenshot() in an application script.
const { test, expect } = require('@playwright/test');
test('page visual snapshot', async ({ page }) => {
await page.goto('https://example.com');
await expect(page).toHaveScreenshot('homepage.webp');
});
Playwright’s visual comparisons guide documents WebP snapshots as lossless. Use page.screenshot() when your script needs a screenshot file or buffer directly; use toHaveScreenshot() when the screenshot is part of a test comparison. Visual comparisons
7. Python and cURL alternatives
The title’s Playwright example is in Node.js, where Playwright supports the screenshot API shown above. The research sources establish the WebP screenshot option for that API; they do not establish equivalent WebP output code for Python or cURL. cURL alone does not render a web page. If you need Python or cURL for a rendered-page screenshot, use a screenshot service that accepts an HTTP request.
Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server. One GET request returns a screenshot as PNG, JPEG, or WebP, or a PDF. See the ScreenshotNeo website and API documentation.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
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)
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 request failed: ${res.status}`);
const bytes = Buffer.from(await res.arrayBuffer());
await require('node:fs/promises').writeFile('shot.webp', bytes);
Before capture, ScreenshotNeo accepts cookie or consent banners like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each step can be turned off. Bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, with the outcome reported in X-Page-Verdict and X-Billed headers. Its MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 screenshots.
Sign up for 1,000 free screenshots a month, with no card required.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
browserType.launch reports that an executable is missing |
The Playwright package is installed, but its browser binary is not. | Run npx playwright install chromium. In a Linux deployment, install the browser dependencies required by the environment as well. |
| Navigation times out | The destination is slow, blocked, or waiting for a load condition that never completes. | Check the URL and access requirements. Set a suitable timeout and use a less strict navigation condition such as domcontentloaded, then wait for the specific content needed. A timeout does not guarantee the page is unusable; inspect the failure and page state. |
| Output is PNG or JPEG instead of WebP | The screenshot options or output extension selected another format. | Set type: 'webp' explicitly and use a .webp file name. Check the actual response bytes if another layer handles the output. |
| Capture is missing content | The page has not rendered the needed section yet, or lazy content has not loaded. | Wait for a meaningful selector or application-ready condition. Use fullPage: true if content lies below the viewport, and account for the page’s lazy-loading behavior. |
| Image is larger than expected | Quality is 100 (lossless), the page is long, or the pixel density is high. | Try a lower WebP quality for lossy compression, capture only the needed scope, or lower the device scale factor. Compare the output visually for your use case. |
| Visual test snapshots differ between runs | Dynamic content, animation, time-dependent data, or different rendering conditions affect the page. | Make the test page state deterministic and wait for the expected content before asserting. Playwright’s snapshot workflow is intended for visual comparisons, so control the inputs that affect rendering. |
| A URL points to an image, but the result is a page screenshot | page.screenshot() captures the browser-rendered page, not a downloaded asset conversion. |
Download the source image and transcode the image bytes with an image library that supports WebP, or open the asset in a browser and capture it only if a screenshot of its rendered display is what you need. |
Performance, reliability, and cost
- Browser work: launching Chromium and rendering a page takes more resources than converting an existing image file. Reuse a browser process for multiple captures when building a long-running worker, while creating an appropriate page or context for each job.
- Waits: use the narrowest readiness condition that matches the content you need. Waiting for every network connection to become idle can be unreliable on pages with persistent requests; a selector tied to the desired content is often clearer.
- Output size: quality 100 is lossless according to Playwright’s documentation. Lower quality trades some image fidelity for smaller output. Full-page captures and higher pixel density increase the amount of image data.
- Failures: websites can redirect, require authentication, block automated browsers, or render differently over time. Handle navigation and screenshot errors, close the browser in a
finallyblock, and avoid assuming every URL is publicly capturable. - Cost: Playwright is an open-source browser automation library, but running captures still uses your compute and storage. For a managed API, ScreenshotNeo has a free tier of 1,000 shots per month with no card, then Starter is $5 for 3,000; Growth $15 for 15,000; Pro $39 for 60,000; Scale $99 for 250,000; and Business $249 for 1,000,000. Yearly billing gives two months free, and every feature is on every plan. Confirm current plan details on the product site before choosing.
FAQ
Can Playwright turn any URL into a WebP?
It can capture a page that the browser can navigate to and render. Whether a particular destination loads successfully depends on that page and its access requirements.
Does quality 100 mean the smallest file?
No. Playwright documents 100 as lossless WebP. Lower quality values use lossy compression and may reduce size, with a corresponding visual trade-off.
Can I use this output in a visual test?
Yes. For Playwright Test snapshots, give toHaveScreenshot() a filename ending in .webp.
Can cURL or Python render a page through Playwright?
Playwright itself is a browser automation library rather than a cURL command. Use its supported language API for browser capture, or call a screenshot API over HTTP from cURL or Python.


