DEV Community

Custodia-Admin
Custodia-Admin

Posted on • Originally published at pagebolt.dev

How to Take Screenshots in Playwright Without Managing a Browser

How to Take Screenshots in Playwright Without Managing a Browser

You're using Playwright to automate browser tasks. It's fast, reliable, and works everywhere. But when you add screenshots to your workflow, you hit a wall:

Self-hosted Playwright screenshots are expensive.

Browser instances consume memory. CI pipelines timeout. Serverless functions can't spin up Chromium. Every screenshot adds seconds to your automation.

There's a simpler way: use a screenshot API.

One HTTP request. Binary PNG back. No browser management. No memory overhead. No vendor lock-in.

Here's how to replace self-hosted Playwright screenshots with a hosted alternative.

The Problem: Self-Hosted Playwright Screenshots Are Heavy

Playwright is great for automation. But screenshots come with a cost.

// Self-hosted Playwright: screenshot costs resources
const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto('https://example.com');

// This keeps the browser running in memory
const screenshot = await page.screenshot({ path: 'screenshot.png' });

await browser.close();
Enter fullscreen mode Exit fullscreen mode

What this costs:

  • Memory: 100–300MB per browser instance
  • Time: 2–5 seconds to launch + navigate
  • Complexity: Error handling, cleanup, retry logic
  • Scalability: CI runners choke on multiple simultaneous screenshots
  • Cost: Serverless functions are billed for entire browser lifetime

If you're taking 100 screenshots, you're spinning up 100 browser instances. Each one eats memory and time.

The Solution: Screenshot API

One API call. No browser.

// Hosted API: screenshot in one request
const response = await fetch('https://api.pagebolt.dev/v1/screenshot', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${PAGEBOLT_API_KEY}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    url: 'https://example.com',
    format: 'png',
    width: 1280,
    height: 720
  })
});

const buffer = await response.arrayBuffer();
const screenshot = Buffer.from(buffer);
// Done. No browser. No memory overhead.
Enter fullscreen mode Exit fullscreen mode

What you get:

  • Speed: One HTTP request (50–200ms)
  • Simplicity: No browser management
  • Scalability: Unlimited concurrent screenshots
  • Reliability: Automatic retries, global CDN
  • Cost: $0–199/month based on volume

Playwright + API: Best of Both Worlds

Use Playwright for automation. Use API for screenshots.

// Playwright for automation, API for proof
import { chromium } from 'playwright';

const browser = await chromium.launch();
const page = await browser.newPage();

// Navigate and interact with Playwright
await page.goto('https://example.com/checkout');
await page.fill('input[name="email"]', 'user@example.com');
await page.click('button:has-text("Checkout")');
await page.waitForNavigation();

// Take screenshot via API (no extra browser needed)
const finalUrl = page.url();
const screenshotResponse = await fetch('https://api.pagebolt.dev/v1/screenshot', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${PAGEBOLT_API_KEY}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    url: finalUrl,
    format: 'png'
  })
});

const buffer = await screenshotResponse.arrayBuffer();
// Use screenshot for verification, logging, etc.

await browser.close();
Enter fullscreen mode Exit fullscreen mode

You still use Playwright. But you don't spin up a separate browser just for screenshots.

Comparison: Self-Hosted vs. API

Factor Self-Hosted Playwright Screenshot API
Setup 10 min (install deps) 2 min (API key)
Code complexity 20+ lines per screenshot 5–8 lines
Memory per screenshot 150–300MB 0MB
Time per screenshot 2–5 sec 0.1–0.3 sec
CI/serverless support Difficult (timeout, resource limits) Works everywhere
Scaling to 1,000 screenshots Requires parallelization, resource limits Unlimited concurrency
Error handling Custom retry logic needed Built-in retries
Cost Free (hardware) $0–199/month

Real Use Case: E-Commerce Order Confirmation

Playwright handles the business logic. API handles the screenshot.

import { chromium } from 'playwright';

async function captureOrderConfirmation(orderId) {
  const browser = await chromium.launch();
  const page = await browser.newPage();

  // Playwright: Navigate to order confirmation
  await page.goto(`https://example.com/orders/${orderId}`);

  // Wait for confirmation to render
  await page.waitForSelector('.order-confirmation');

  // API: Take screenshot of final state
  const screenshotResponse = await fetch('https://api.pagebolt.dev/v1/screenshot', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${PAGEBOLT_API_KEY}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      url: page.url(),
      format: 'png',
      fullPage: true,
      blockBanners: true // Hide cookie popups
    })
  });

  const buffer = await screenshotResponse.arrayBuffer();

  // Store for audit trail
  const filename = `order-${orderId}-confirmation.png`;
  fs.writeFileSync(filename, Buffer.from(buffer));

  await browser.close();
  return filename;
}
Enter fullscreen mode Exit fullscreen mode

What this gives you:

  • ✅ Playwright's reliability for navigation
  • ✅ API's speed for screenshots
  • ✅ One browser instance (not two)
  • ✅ Fast, lightweight workflow

Real Use Case: Headless Test Reports

Testing framework + API = visual test reports.

import { test, expect } from '@playwright/test';

test('checkout flow creates confirmation', async ({ page }) => {
  // Test with Playwright
  await page.goto('https://example.com');
  await page.fill('input[name="email"]', 'test@example.com');
  await page.click('button:has-text("Next")');

  // Assert behavior
  await expect(page).toHaveURL(/confirmation/);

  // Capture proof screenshot
  const screenshotResponse = await fetch('https://api.pagebolt.dev/v1/screenshot', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${PAGEBOLT_API_KEY}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      url: page.url(),
      format: 'png'
    })
  });

  const buffer = await screenshotResponse.arrayBuffer();
  await fs.promises.writeFile(`test-${Date.now()}-proof.png`, Buffer.from(buffer));
});
Enter fullscreen mode Exit fullscreen mode

Tests pass/fail as usual. But you have visual proof of what the page looked like when the test ran.

API Parameters (Playwright Common Use Cases)

Parameter Example Use Case
url page.url() Screenshot current page
format png PNG format (supports pdf, webp)
fullPage true Capture entire scrollable page (lazy content)
width 1280 Viewport width (desktop)
height 720 Viewport height
blockBanners true Hide cookie consent popups
blockAds true Remove ads for clean screenshots

Error Handling

async function safeScreenshot(url) {
  try {
    const response = await fetch('https://api.pagebolt.dev/v1/screenshot', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${PAGEBOLT_API_KEY}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({ url, format: 'png' })
    });

    if (!response.ok) {
      throw new Error(`Screenshot failed: ${response.status}`);
    }

    const buffer = await response.arrayBuffer();
    return Buffer.from(buffer);
  } catch (error) {
    console.error('Screenshot error:', error.message);
    // Fallback: return null or use Playwright screenshot instead
    return null;
  }
}
Enter fullscreen mode Exit fullscreen mode

Migration: From Self-Hosted to API

Before:

const screenshot = await page.screenshot({ path: 'out.png' });
Enter fullscreen mode Exit fullscreen mode

After:

const response = await fetch('https://api.pagebolt.dev/v1/screenshot', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${PAGEBOLT_API_KEY}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({ url: page.url(), format: 'png' })
});
const screenshot = Buffer.from(await response.arrayBuffer());
Enter fullscreen mode Exit fullscreen mode

Benefits of switching:

  • ✅ Faster (50ms vs. 2-5 sec)
  • ✅ Lighter (0MB vs. 150-300MB per screenshot)
  • ✅ Scales to unlimited concurrent requests
  • ✅ Works in serverless, CI, containers without browser installation
  • ✅ No complex retry logic needed

Pricing

Plan Requests/Month Cost Best For
Free 100 $0 Development & testing
Starter 5,000 $29 Small projects, side gigs
Growth 25,000 $79 Production apps, frequent screenshots
Scale 100,000 $199 High-volume automation

Summary

Playwright is excellent for automation. But screenshots don't have to be expensive.

  • ✅ Use Playwright for navigation, interaction, testing
  • ✅ Use Screenshot API for visual proof (no browser)
  • ✅ One API key, one HTTP request per screenshot
  • ✅ No memory overhead, no CI timeout issues
  • ✅ Scales from 10 screenshots to 10,000

Get started free: pagebolt.dev — 100 requests/month, no credit card required.

Top comments (0)