Taking a screenshot of a website programmatically is one of the most common automation tasks. Whether you're building visual regression tests, generating OG images, archiving web pages, or monitoring competitors — you need a reliable way to capture what a web page looks like.

This guide covers three approaches in three languages: browser automation (Playwright), headless browser libraries, and API-based capture. Each has trade-offs in complexity, reliability, and scalability.

Node.js — Playwright

Playwright is the gold standard for browser automation in Node.js. It supports Chromium, Firefox, and WebKit with a clean async API:

import { chromium } from 'playwright';

async function screenshotUrl(url, outputPath, options = {}) {
  const browser = await chromium.launch();
  const page = await browser.newPage();

  // Set viewport
  await page.setViewportSize({
    width: options.width ?? 1280,
    height: options.height ?? 720,
  });

  // Navigate and wait for content
  await page.goto(url, { waitUntil: 'networkidle' });

  // Optional: wait for specific element
  if (options.waitFor) {
    await page.waitForSelector(options.waitFor, { timeout: 10000 });
  }

  // Capture screenshot
  await page.screenshot({
    path: outputPath,
    fullPage: options.fullPage ?? false,
    type: options.format ?? 'png',
    quality: options.format === 'jpeg' ? (options.quality ?? 80) : undefined,
  });

  await browser.close();
  console.log(`Screenshot saved: ${outputPath}`);
}

// Usage
await screenshotUrl('https://example.com', 'screenshot.png', {
  fullPage: true,
  width: 1440,
  height: 900,
});

Advanced: Element Screenshot

// Capture a specific element
async function screenshotElement(url, selector, outputPath) {
  const browser = await chromium.launch();
  const page = await browser.newPage();
  await page.goto(url, { waitUntil: 'networkidle' });

  const element = await page.$(selector);
  if (element) {
    await element.screenshot({ path: outputPath });
    console.log(`Element screenshot saved: ${outputPath}`);
  } else {
    console.error(`Selector not found: ${selector}`);
  }

  await browser.close();
}

await screenshotElement(
  'https://example.com',
  '.hero-section',
  'hero.png'
);

Python — Playwright & Selenium

Python + Playwright

from playwright.sync_api import sync_playwright

def screenshot_url(url, output_path, width=1280, height=720, full_page=False):
    with sync_playwright() as p:
        browser = p.chromium.launch()
        page = browser.new_page(viewport={"width": width, "height": height})
        page.goto(url, wait_until="networkidle")
        page.screenshot(path=output_path, full_page=full_page)
        browser.close()
        print(f"Screenshot saved: {output_path}")

# Usage
screenshot_url("https://example.com", "screenshot.png", full_page=True)

Python + Selenium

from selenium import webdriver
from selenium.webdriver.chrome.options import Options

def screenshot_selenium(url, output_path, width=1280, height=720):
    options = Options()
    options.add_argument("--headless=new")
    options.add_argument("--no-sandbox")
    options.add_argument("--disable-dev-shm-usage")
    options.add_argument(f"--window-size={width},{height}")

    driver = webdriver.Chrome(options=options)
    driver.get(url)

    # Full page screenshot (Selenium 4+)
    driver.save_screenshot(output_path)
    driver.quit()
    print(f"Screenshot saved: {output_path}")

screenshot_selenium("https://example.com", "screenshot.png")

Go — chromedp

package main

import (
    "context"
    "log"
    "os"
    "time"

    "github.com/chromedp/chromedp"
)

func screenshotURL(url, outputPath string, width, height int64) error {
    opts := append(chromedp.DefaultExecAllocatorOptions[:],
        chromedp.WindowSize(int(width), int(height)),
        chromedp.Flag("headless", true),
        chromedp.Flag("no-sandbox", true),
    )

    ctx, cancel := chromedp.NewExecAllocator(
        context.Background(), opts...)
    defer cancel()

    ctx, cancel = chromedp.NewContext(ctx)
    defer cancel()

    ctx, cancel = context.WithTimeout(ctx, 30*time.Second)
    defer cancel()

    var buf []byte
    err := chromedp.Run(ctx,
        chromedp.Navigate(url),
        chromedp.WaitReady("body"),
        chromedp.Sleep(2*time.Second),
        chromedp.FullScreenshot(&buf, 90),
    )
    if err != nil {
        return err
    }

    return os.WriteFile(outputPath, buf, 0644)
}

func main() {
    err := screenshotURL(
        "https://example.com",
        "screenshot.png",
        1280, 720,
    )
    if err != nil {
        log.Fatal(err)
    }
    log.Println("Screenshot saved")
}

API Approach — No Browser Required

All the browser-based approaches above require installing Chromium (400MB+), managing processes, and handling crashes. A screenshot API eliminates all that complexity:

Node.js

const response = await fetch('https://api.snapapi.pics/v1/screenshot', {
  method: 'POST',
  headers: {
    'X-Api-Key': 'sk_live_your_key_here',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    url: 'https://example.com',
    format: 'png',
    width: 1280,
    height: 720,
    full_page: true,
    block_ads: true,
    device: 'macbook-pro-16',
  }),
});

const buffer = Buffer.from(await response.arrayBuffer());
fs.writeFileSync('screenshot.png', buffer);

Python

import requests

response = requests.post(
    'https://api.snapapi.pics/v1/screenshot',
    headers={'X-Api-Key': 'sk_live_your_key_here'},
    json={
        'url': 'https://example.com',
        'format': 'png',
        'full_page': True,
        'block_ads': True,
        'device': 'iphone-15-pro',
    }
)

with open('screenshot.png', 'wb') as f:
    f.write(response.content)

Go

payload, _ := json.Marshal(map[string]interface{}{
    "url":       "https://example.com",
    "format":    "png",
    "full_page": true,
    "device":    "pixel-8",
})

req, _ := http.NewRequest("POST",
    "https://api.snapapi.pics/v1/screenshot",
    bytes.NewBuffer(payload))
req.Header.Set("X-Api-Key", "sk_live_your_key_here")
req.Header.Set("Content-Type", "application/json")

resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()

f, _ := os.Create("screenshot.png")
defer f.Close()
f.ReadFrom(resp.Body)

Approach Comparison

Factor Playwright Selenium chromedp (Go) SnapAPI (API)
Dependencies Chromium binary Chrome + ChromeDriver Chrome binary None (HTTP only)
Setup time ~5 min ~15 min ~10 min ~1 min
Device emulation Manual config Limited Manual config 30+ presets
Anti-bot bypass Stealth plugins Limited Limited Built-in
Scaling Manual (browser pools) Selenium Grid Manual Automatic
Serverless compatible Difficult (binary size) No No Yes (HTTP call)
Extra features Full browser control Full browser control Full browser control Scraping, PDF, video, AI

Screenshot Any Website in One API Call

No Chromium to install. No browser crashes. 30+ device presets. 200 free requests/month.

Get Your Free API Key