How to Make a Website Screenshot from a URL
Learn how to capture a website URL as a viewport, full-page, or element screenshot with Playwright, then automate it with ScreenshotNeo.
Direct answer: load the URL in a real browser, wait for the page to render, then call the browser’s screenshot method. With Playwright, the basic flow is page.goto() followed by page.screenshot(). Use fullPage: true for the entire scrollable page, or capture a locator when you need one element.
1. Capture a URL with Playwright
Playwright controls Chromium, Firefox, or WebKit, so the screenshot reflects the page after HTML, CSS, images, and JavaScript have rendered. Install it with:
npm install playwright
npx playwright install
The official workflow is documented in Playwright’s screenshot guide.
Basic Node.js screenshot
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto('https://example.com');
await page.screenshot({ path: 'screenshot.png' });
await browser.close();
})();
Run it with node screenshot.js. The image is written to screenshot.png. If you omit path, Playwright returns image bytes, which you can upload or process in memory.
Python version
pip install playwright
playwright install
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="screenshot.png")
browser.close()
2. Choose what to capture
Viewport screenshot
A normal screenshot captures the currently visible viewport. Set the viewport explicitly when repeatability matters:
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch();
const page = await browser.newPage({ viewport: { width: 1440, height: 900 } });
await page.goto('https://example.com', { waitUntil: 'networkidle' });
await page.screenshot({ path: 'viewport.png', type: 'png' });
await browser.close();
})();
Full webpage screenshot
Set fullPage: true to capture the full scrollable page as one tall image:
await page.screenshot({ path: 'full-page.png', fullPage: true });
Very long pages can create large files or exceed image-dimension limits. For those pages, capture sections or use a PDF workflow instead.
One element
Use a locator’s screenshot method when you need a card, header, chart, or other region:
await page.locator('.header').screenshot({ path: 'header.png' });
Prefer a stable selector such as a data attribute over a generated class. If the locator matches multiple elements, narrow it with .first(), .nth(), or a more specific selector.
Clip a rectangle
await page.screenshot({
path: 'crop.png',
clip: { x: 0, y: 0, width: 800, height: 600 }
});
3. Control rendering before the shot
The browser must finish loading the page before capture. Choose a wait strategy that matches the page:
| Need | Approach |
|---|---|
| Initial HTML and assets | page.goto(url) |
| Many network requests to settle | waitUntil: 'networkidle' |
| A known component | page.locator('.chart').waitFor() |
| A fixed animation or delayed widget | page.waitForTimeout(1000) |
await page.goto('https://example.com/dashboard', { waitUntil: 'domcontentloaded' });
await page.locator('[data-ready="true"]').waitFor();
await page.screenshot({ path: 'dashboard.png' });
Use a selector wait when possible. A fixed delay is simple but can be either too short or unnecessarily slow.
Set device, scale, and color scheme
const page = await browser.newPage({
viewport: { width: 390, height: 844 },
deviceScaleFactor: 2,
isMobile: true,
colorScheme: 'dark'
});
Keep the browser version, operating system, viewport, fonts, and headless settings consistent for visual comparisons. Playwright warns that rendering can vary across these environments.
Hide dynamic content
await page.addStyleTag({ content: `
*, *::before, *::after {
animation: none !important;
transition: none !important;
caret-color: transparent !important;
}
` });
await page.screenshot({ path: 'stable.png' });
You can also remove timestamps, rotating banners, or cursor indicators before capture with page-specific CSS or JavaScript.
4. Output formats and bytes
Playwright can save PNG, JPEG, or other supported screenshot formats according to the current API. JPEG supports a quality value:
await page.screenshot({ path: 'photo.jpg', type: 'jpeg', quality: 85 });
When you need transparent pixels, configure a transparent page background and use a format that preserves transparency:
await page.screenshot({ path: 'transparent.png', omitBackground: true });
For an in-memory buffer:
const bytes = await page.screenshot({ type: 'png' });
// bytes is a Buffer; send it to storage or another API
5. Make repeated captures reliable
- Use a fixed browser engine and viewport.
- Wait for a meaningful selector rather than guessing with a long delay.
- Disable animations and hide changing data.
- Set a navigation timeout and handle failures.
- Close the browser in a
finallyblock.
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch();
try {
const page = await browser.newPage({ viewport: { width: 1440, height: 900 } });
page.setDefaultNavigationTimeout(30000);
await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
await page.locator('body').waitFor();
await page.screenshot({ path: 'reliable.png', fullPage: true });
} finally {
await browser.close();
}
})();
For screenshot comparisons, Playwright Test’s toHaveScreenshot() waits for two consecutive screenshots to stabilize before comparing with a baseline. Keep the baseline and capture environment consistent, and control dynamic content.
6. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Browser executable missing | Playwright browsers were not installed | Run npx playwright install or playwright install. |
| Blank or half-rendered image | Capture happened before content loaded | Wait for a selector, an appropriate load state, or a short page-specific delay. |
| Full-page image is too large | The document is extremely tall | Capture sections, reduce scale, or produce a PDF. |
| Element screenshot fails | Selector does not match, is hidden, or is outside the current state | Check the selector, wait for visibility, and reproduce the required UI state. |
| Images differ between runs | Fonts, browser version, animations, time, or dynamic data changed | Pin the environment, disable motion, and mask or remove changing content. |
| Navigation timeout | The site is slow, blocked, or never reaches the chosen load state | Increase the timeout carefully, use a less strict load state, and verify the URL independently. |
| Cookie dialog covers the page | A consent banner is part of the rendered page | Click its accept button or hide it before capture when your use case permits. |
7. Performance, reliability, and cost
- Startup: launching a browser for every URL adds overhead. Reuse one browser process and create isolated pages for batches.
- Page weight: full-page images and retina scale increase memory, processing time, and storage.
- Waiting: network-idle waits improve completeness on busy pages but may delay pages with long-lived connections.
- Concurrency: limit simultaneous pages to the CPU and memory available to your runner.
- Retries: retry transient navigation failures with a limit and record the URL and error; do not retry indefinitely.
- Cost: local Playwright has no per-shot service charge, but your browser runtime consumes compute, memory, bandwidth, and storage.
8. Or skip the browser setup
ScreenshotNeo provides a hosted website screenshot API: one GET request returns a PNG, JPEG, WebP, or PDF. Its capture options include full-page screenshots with lazy images loaded, CSS-selector element capture, dark mode, device presets or custom viewports, retina scale, custom CSS and JavaScript, clicks, selector or network-idle waits, blocked requests, headers, cookies, user agents, authorization, timezone, geolocation, transparent backgrounds, resizing, caching, signed links, asynchronous jobs, webhooks, bulk capture, and a usage API. See the ScreenshotNeo documentation for request options.
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}`);
Cookie and consent banners, newsletter popups, and chat widgets can be removed before the shot. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing status. ScreenshotNeo also provides an MCP server for AI agents with take_screenshot, get_page_info, and capture_pdf. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000.
Create a free ScreenshotNeo account and start with 1,000 screenshots per month at no charge.
9. FAQ
How do I screenshot a website from a URL without opening it manually?
Use a browser automation script such as Playwright, or send the URL to a hosted screenshot API. Both load the page programmatically before returning an image.
How do I save a website screenshot as a PNG?
Use page.screenshot({ path: 'screenshot.png', type: 'png' }) in Playwright, or save the API response body with a .png filename when requesting PNG output.
Can I screenshot only part of a webpage?
Yes. Capture a locator for one element or pass a rectangular clip region.
Why does my screenshot differ from what I see in my browser?
Viewport size, fonts, browser version, device scale, animations, login state, time-based content, and consent dialogs can all change the rendered result. Make those inputs explicit.


