ScreenshotNeo

BlogEngineering

Using a JavaScript Screenshot API on HTTPS Websites

Capture reliable screenshots of HTTPS JavaScript apps with Puppeteer or Playwright, explicit readiness waits, production safeguards, and a hosted API option.

By the ScreenshotNeo team1 October 20268 min read

Direct answer: A JavaScript screenshot API for an HTTPS website normally launches (or reuses) a headless browser, navigates to the HTTPS URL, waits until the application is ready, and captures the rendered page. Puppeteer exposes page.screenshot() for image bytes or files, while Playwright supports Chromium, Firefox, and WebKit with full-page, element, masking, animation, clipping, and format controls. See the Puppeteer screenshot API and Playwright screenshot guide.

1. How the HTTPS screenshot flow works

  1. Validate and normalize the requested URL. Allow only https: (and optionally http: for local development).
  2. Launch or reuse a browser, then create an isolated context or page.
  3. Set the viewport and device scale factor to match the target layout.
  4. Navigate with page.goto().
  5. Wait for a suitable readiness signal: a load state, a stable selector, a short delay, or an application-defined completion flag.
  6. Capture the viewport, the full scrollable page, an element, or a clip.
  7. Return or store PNG, JPEG, or WebP bytes.
  8. Close or recycle the page and enforce timeouts, concurrency, and output-size limits.

HTTPS protects transport, but it does not mean the first HTML response contains the final pixels. A React, Vue, Angular, or other client-rendered app can still show placeholders while data and images load. Readiness is therefore the key correctness decision.

2. Puppeteer: complete HTTPS screenshot example

Install Puppeteer:

npm install puppeteer

This script validates an HTTPS URL, waits for a page-specific selector, captures a full-page WebP, and closes the browser even when navigation fails.

const puppeteer = require('puppeteer');

function validateHttpsUrl(value) {
  const url = new URL(value);
  if (url.protocol !== 'https:') {
    throw new Error('Only HTTPS URLs are allowed');
  }
  return url.href;
}

(async () => {
  const target = validateHttpsUrl(process.argv[2] || 'https://example.com');
  const browser = await puppeteer.launch({ headless: true });
  try {
    const page = await browser.newPage();
    await page.setViewport({ width: 1440, height: 900, deviceScaleFactor: 1 });
    await page.goto(target, {
      waitUntil: 'domcontentloaded',
      timeout: 45_000
    });
    await page.waitForSelector('body', { visible: true, timeout: 15_000 });
    await page.screenshot({
      path: 'screenshot.webp',
      type: 'webp',
      fullPage: true
    });
  } finally {
    await browser.close();
  }
})();

Run it with:

node capture-puppeteer.js https://example.com

domcontentloaded is only an initial milestone. Replace the body selector with a selector that proves your application is ready, such as [data-page-ready="true"] or a chart container. Puppeteer’s navigation examples also demonstrate a networkidle2 policy; it is useful for some pages but is not a universal guarantee because analytics, ads, streaming, and long polling can keep connections open.

Useful Puppeteer variations

// Viewport screenshot
await page.screenshot({ path: 'viewport.png', type: 'png' });

// JPEG with quality (0-100)
await page.screenshot({ path: 'photo.jpg', type: 'jpeg', quality: 85 });

// Full page
await page.screenshot({ path: 'full.png', fullPage: true });

// One element
const card = await page.$('.pricing-card');
await card.screenshot({ path: 'card.png' });

// Wait for a known app signal
await page.waitForFunction(() => window.__SCREENSHOT_READY__ === true, { timeout: 20_000 });

// Disable motion for repeatable captures
await page.addStyleTag({ content: `*, *::before, *::after { animation: none !important; transition: none !important; }` });

3. Playwright: browser choice and richer capture controls

Install Playwright and its browsers:

npm install playwright
npx playwright install chromium
const { chromium } = require('playwright');

(async () => {
  const browser = await chromium.launch();
  const context = await browser.newContext({
    viewport: { width: 1440, height: 900 },
    deviceScaleFactor: 1,
    colorScheme: 'light'
  });
  const page = await context.newPage();
  try {
    await page.goto('https://example.com', {
      waitUntil: 'domcontentloaded',
      timeout: 45_000
    });
    await page.locator('body').waitFor({ state: 'visible', timeout: 15_000 });
    await page.screenshot({ path: 'playwright-full.png', fullPage: true });
  } finally {
    await context.close();
    await browser.close();
  }
})();

Playwright is a practical choice when you need one API across Chromium, Firefox, and WebKit, or when you need documented controls such as masking and animation handling. Puppeteer is a concise Chrome/Chromium path. Choose based on the browser coverage and operational model your service requires.

// Element capture
await page.locator('.chart').screenshot({ path: 'chart.png' });

// Clip a rectangle
await page.screenshot({
  path: 'clip.png',
  clip: { x: 0, y: 0, width: 800, height: 600 }
});

// Mask changing or sensitive regions
await page.screenshot({
  path: 'masked.png',
  mask: [page.locator('.timestamp'), page.locator('.avatar')]
});

// JPEG or WebP
await page.screenshot({ path: 'page.jpg', type: 'jpeg', quality: 85 });
await page.screenshot({ path: 'page.webp', type: 'webp' });

4. Readiness for JavaScript applications

Use the least broad signal that proves the pixels you need are ready:

Signal Use it when Risk
domcontentloaded Server HTML is already useful Client data may still be missing
load Images and subresources should be loaded Some app requests continue afterward
networkidle The page has a finite request burst Long polling or ads may never become idle
Selector wait A stable component marks completion Selector must be maintained with the app
App completion flag You control the application Requires an explicit integration

For lazy-loaded images, scroll the document before capture or use a full-page mode that loads content as it measures the page. For animations, inject CSS to pause them or use the framework’s reduced-motion setting. For timestamps, ads, rotating content, and personalized data, mask or hide those regions when repeatability matters.

5. Capture options to design into an API

  • Viewport: width, height, and device scale factor determine responsive layout and output pixels.
  • Full page: captures the complete scrollable document, not only the visible viewport.
  • Element or clip: captures a card, chart, or selected rectangle.
  • Format: PNG is lossless; JPEG is smaller for photographic content; WebP is compact when supported by the consumer.
  • Color and theme: set light or dark color scheme and, where needed, emulate media features.
  • Interaction: click a menu or tab before capture, then wait for its content.
  • Network controls: block ads, trackers, selected requests, or resource types to reduce noise and work.
  • Identity and locale: set headers, cookies, user agent, timezone, and geolocation only when authorized.
  • Output limits: cap dimensions, bytes, and page time so one URL cannot exhaust workers.

6. Secure production design

Treat every requested URL as untrusted input. Parse it with the URL API, restrict protocols, and consider an allowlist or private-network blocklist to prevent server-side request forgery. Use an isolated browser context per job, do not print cookies or authorization headers in logs, and do not return screenshots containing private data to an untrusted caller. Keep navigation and screenshot timeouts finite. Run the browser with the permissions and filesystem access it actually needs.

Do not turn off HTTPS certificate verification in production. An option such as ignoreHTTPSErrors can hide certificate problems and should be reserved for controlled test environments where that trade-off is understood.

7. Performance, reliability, and cost

  • Reuse browsers carefully: launching Chromium for every request adds startup work; reusing a browser while creating a fresh context per job reduces that overhead and preserves isolation.
  • Bound concurrency: too many pages compete for CPU, memory, and bandwidth. Queue requests and apply back pressure.
  • Set two timeouts: a navigation timeout and an overall job deadline that includes waiting and encoding.
  • Cache deliberately: cache only when the URL, headers, cookies, locale, and viewport produce equivalent output. Add a TTL when pages change.
  • Retry selectively: retry transient navigation or browser failures with a small limit; do not retry invalid URLs, authentication failures, or bot checks indefinitely.
  • Control output size: use a viewport or element capture when a full document is unnecessary, and choose JPEG/WebP when lossless output is not required.
  • Measure your own workload: latency and success depend on browser version, page complexity, geography, concurrency, and hosting. The official sources provide API behavior, not a universal benchmark.

8. Troubleshooting HTTPS screenshots

Symptom Likely cause Fix
Blank or skeleton page Capture happened before client rendering finished Wait for a stable selector or app completion signal.
Navigation timeout Slow origin, blocked request, or never-ending connections Increase the bounded timeout, use a narrower readiness signal, and inspect failed requests.
Certificate error Invalid or private TLS certificate Fix the certificate; avoid disabling verification in production.
Full page cuts off content Virtualized list or lazy content is loaded only while scrolling Scroll in steps, trigger the app’s load-more behavior, then capture.
Fonts or images missing Resources are still loading, blocked, or cross-origin protected Wait for the relevant resource state, verify network responses, and provide authorized headers or cookies.
Cookie banner covers content Consent UI remains in the DOM Accept it through a documented interaction, hide its selector, or use a service that handles consent before capture.
Different pixels on each run Animation, ads, timestamps, or personalized data Freeze motion, mask variable regions, and control locale and identity.
Browser crashes under load Unbounded pages, huge documents, or memory leaks Limit concurrency and dimensions, recycle workers, and enforce an output byte cap.

9. Or skip the browser setup

ScreenshotNeo provides a hosted website screenshot API and MCP server. One GET request returns PNG, JPEG, WebP, or PDF. Before capture it accepts cookie/consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each step can be turned off. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies the result with X-Page-Verdict and X-Billed headers. Its MCP tools (take_screenshot, get_page_info, and capture_pdf) work with Claude, Cursor, and other MCP clients.

See the ScreenshotNeo API documentation for all options.

cURL

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

Python

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)

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}`);

ScreenshotNeo also supports full-page and CSS-selector captures, dark mode, device presets or custom viewports, retina scale, PDF paper and margin controls, custom CSS and JavaScript, clicks and waits, blocking rules, headers, cookies, user agents, authorization, timezone, geolocation, transparent backgrounds, resizing, configurable caching, signed links, asynchronous webhooks, bulk capture of up to 100 URLs per call, a usage API, and an OpenAPI specification. Every feature is available on every plan. The Free plan includes 1,000 shots per month with no card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.

10. FAQ

Can a browser screenshot an HTTPS page with mixed content?

The page may load, but browsers can block insecure subresources. Fix the page’s resource URLs or verify which requests are being blocked before relying on the image.

Should I wait for network idle on every site?

No. Use network idle only when the application has a finite request burst. A stable selector or app-defined signal is usually more precise for dynamic sites.

When is an element screenshot better than full page?

Use an element capture for cards, charts, receipts, and components where surrounding navigation is irrelevant. It also keeps output dimensions and bytes smaller.

Which format should an API return?

Use PNG for exact text and transparency, JPEG for photographic images where smaller files matter, and WebP when clients support it and you want a compact modern format.

How do I keep screenshots reproducible?

Fix the viewport, device scale, timezone, locale, color scheme, and identity; disable animation; wait for a deterministic selector; and mask timestamps or other changing regions.