Skip to content

Playwright Screenshots

Capture retina-quality screenshots with Playwright — custom viewports, device scale factors, and tooltip states.

When to use

Use these patterns when capturing polished screenshots of a web page for documentation, design review, or visual storytelling. Particularly useful for prototypes where you need to show hover states, tooltips, or multiple UI configurations in a single batch.

The pattern

Retina screenshots with a dedicated context

Create a separate browser context with a specific viewport and device scale factor. A scale factor of 2 produces retina-quality output at double the CSS pixel dimensions:

javascript
import { chromium } from "playwright";

const browser = await chromium.launch({ headless: true });
const context = await browser.newContext({
  viewport: { width: 1440, height: 1024 },
  deviceScaleFactor: 2,
});
const page = await context.newPage();

await page.goto("http://localhost:8080");
await page.waitForLoadState("networkidle");

Take screenshots at device scale so the output reflects the scale factor. A 1440×1024 viewport at 2x produces a 2880×2048 PNG:

javascript
await page.screenshot({
  path: "screenshots/01-default.png",
  scale: "device",
});

Use zero-padded filenames (01-, 02-) to keep screenshots in sequence order.

Adding a cursor overlay

Real mouse cursors don't appear in Playwright screenshots. See Playwright Mouse Cursor for the showCursor/hideCursor helpers and setup. The examples below assume those helpers are available.

Capturing tooltip and hover states

Tooltips require a specific interaction sequence — click the target first to select it, move the mouse away, then hover back to trigger the tooltip. For example, to capture a tooltip on a button:

javascript
const button = page.locator("button.my-button");

// Click to select, move away, hover back to trigger tooltip
await button.click();
await page.mouse.move(0, 0);
await button.hover();
await page.waitForSelector('[role="tooltip"]', {
  state: "visible",
});

// Position pointer cursor centered on the button
const box = await button.boundingBox();
await showCursor(page, {
  src: pointerUri,
  x: box.x + box.width / 2 - 16,
  y: box.y + box.height / 2 - 16,
  width: 32,
  height: 32,
});

await page.screenshot({
  path: "screenshots/03-tooltip.png",
  scale: "device",
});
await hideCursor(page);

Switching UI states between screenshots

When the page has multiple configurations (radio buttons, tabs, toggles), select each state and capture it in sequence. For example, to screenshot each option in a radio group:

javascript
const states = [
  { label: "Option A", file: "02-option-a.png" },
  { label: "Option B", file: "03-option-b.png" },
];

for (const { label, file } of states) {
  await page.getByLabel(label).click();
  await page.waitForTimeout(300);
  await showCursor(page, {
    src: arrowUri,
    x: 700, y: 500,
    width: 32, height: 32,
  });
  await page.screenshot({
    path: `screenshots/${file}`,
    scale: "device",
  });
  await hideCursor(page);
}

Verifying screenshot dimensions

On macOS, use sips to confirm output dimensions match expectations:

sh
sips -g pixelWidth -g pixelHeight screenshots/*.png

Expected output for a 1440×1024 viewport at 2x scale:

pixelWidth: 2880
pixelHeight: 2048

Cleanup

Close the dedicated context after all screenshots are taken. If you created the context on an existing browser instance, don't close the browser itself:

javascript
await context.close();
// Don't close browser if other contexts are still in use

Trade-offs

ChoiceProCon
deviceScaleFactor: 2Retina-crisp output4× file size vs 1x
Click-away-hoverReliable tooltip triggerMore steps per shot
Zero-padded namesCorrect sort orderPlan sequence upfront