ScreenshotNeo

BlogHow-to

How to Capture a Webpage Screenshot in Dark Mode with Playwright

Emulate dark mode with Playwright, capture a viewport or full page, and troubleshoot pages that ignore the preference.

By the ScreenshotNeo team4 October 20268 min read

To capture a webpage in dark mode with Playwright, emulate the browser’s prefers-color-scheme: dark preference before taking the screenshot. In JavaScript, call await page.emulateMedia({ colorScheme: 'dark' }), then await page.screenshot({ path: 'page-dark.png' }). For a full-page image, add fullPage: true. This tells the page that the browser prefers dark mode; it does not force a site to provide dark styles.

1. Capture a dark-mode screenshot with JavaScript

Install Playwright and its browser, then save this as screenshot.mjs:

npm install playwright
npx playwright install chromium
import { chromium } from 'playwright';

const browser = await chromium.launch();
try {
  const context = await browser.newContext({
    colorScheme: 'dark',
    viewport: { width: 1280, height: 800 },
  });
  const page = await context.newPage();
  await page.goto('https://example.com', { waitUntil: 'load' });
  await page.screenshot({ path: 'page-dark.png', fullPage: true });
  await context.close();
} finally {
  await browser.close();
}

Run it with node screenshot.mjs. The viewport dimensions are an example; choose values that match the output you need. fullPage: true captures the full scrollable page. Omit it to capture just the visible viewport.

The context option applies the preference from the start, which is useful if the site checks the preference during its initial render. You can also change it for a page after creating or navigating to it:

await page.emulateMedia({ colorScheme: 'dark' });
await page.screenshot({ path: 'page-dark.png' });

For the documented options and details, see Playwright’s emulation guide and Page API.

2. Choose where to set the color scheme

Approach Use it when
browser.newContext({ colorScheme: 'dark' }) You want every page in a context to start with the dark preference, including the first render.
page.emulateMedia({ colorScheme: 'dark' }) You want to change the media preference for one page, for example between captures.
Playwright Test configuration or test.use() You want multiple tests or pages to share the same emulated preference.

Playwright Test can set it in configuration with use: { colorScheme: 'dark' }, or for a test with test.use({ colorScheme: 'dark' }). This avoids repeating the setting for each screenshot. See the Playwright emulation guide.

Playwright also documents 'light', 'dark', and 'no-preference' color scheme values. Use 'no-preference' when you need to test how the site behaves without a light-or-dark preference.

3. Select the screenshot output

Playwright’s screenshot API supports PNG, JPEG, and WebP. PNG is the default. Set the extension and, when useful, the type explicitly so the intended format is clear:

await page.screenshot({ path: 'page-dark.webp', type: 'webp', fullPage: true });
Option Effect Considerations
path Saves the image to a file; the extension can determine the format. Make the destination directory first if it does not exist.
fullPage: true Captures the full scrollable page. Long pages can produce large images and take longer to capture.
type Selects 'png', 'jpeg', or 'webp'. PNG is the default; JPEG and WebP can reduce file size depending on content and quality settings.
scale 'css' produces one image pixel per CSS pixel; 'device' uses device pixels. The documented default is 'device'. Use CSS scale for smaller, dimension-stable output when high-DPI pixels are unnecessary.
animations: 'disabled' Disables animations for the screenshot. Useful for repeatable captures; the resulting image will not show the animation in progress.
style Applies a stylesheet during capture. Can hide dynamic elements or adjust presentation, but changes what the screenshot shows.

Example using explicit format, scale, and animation handling:

await page.screenshot({
  path: 'page-dark.png',
  type: 'png',
  fullPage: true,
  scale: 'css',
  animations: 'disabled',
});

Check the Page API screenshot options for the current complete option list and behavior.

4. Python: capture a dark-mode page

Install Playwright for Python and its Chromium browser:

pip install playwright
playwright install chromium

Save as screenshot.py and run python screenshot.py:

import asyncio
from playwright.async_api import async_playwright

async def main():
    async with async_playwright() as p:
        browser = await p.chromium.launch()
        try:
            context = await browser.new_context(
                color_scheme="dark",
                viewport={"width": 1280, "height": 800},
            )
            page = await context.new_page()
            await page.goto("https://example.com", wait_until="load")
            await page.screenshot(path="page-dark.png", full_page=True)
            await context.close()
        finally:
            await browser.close()

asyncio.run(main())

Python can also change the preference on an existing page: await page.emulate_media(color_scheme="dark"). The Python API uses snake_case option names such as color_scheme and full_page. See the Playwright Python emulation guide.

5. cURL and Node.js alternatives

Playwright itself is a browser automation library, so cURL alone cannot render a webpage or take a browser screenshot. It can call a screenshot API that performs the browser capture remotely. For example, ScreenshotNeo accepts a URL and returns an image or PDF:

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

For a direct Node.js Playwright capture, use the JavaScript example above. To request an image from ScreenshotNeo in Node.js instead:

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer())));

Use ScreenshotNeo’s API documentation for authentication and request options.

6. Make captures reliable and repeatable

Setting dark mode is only one part of a stable capture. Pages may continue rendering after navigation, load images as you scroll, animate content, or fetch data asynchronously. Wait for the specific content your capture depends on instead of assuming a fixed delay works on every site.

await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
await page.locator('main').waitFor({ state: 'visible' });
await page.screenshot({ path: 'page-dark.png', fullPage: true, animations: 'disabled' });

Replace main with a selector that identifies the content your application needs. For visual comparisons, keep the browser version, operating system, viewport, device scale, and headless setting consistent. Playwright notes that rendering can vary with host operating system, browser version, settings, hardware, power conditions, and headless mode; consult its visual comparisons guide.

For dynamic pages, decide what should happen to animations, timestamps, ads, and personalized content. Use a capture stylesheet only when intentionally changing the output. Full-page captures can be significantly taller than the viewport, so consider whether a viewport capture or a specific element better serves the use case.

7. Troubleshooting

Symptom Likely cause Fix
The page still looks light. The site does not implement dark styles for prefers-color-scheme, or its own theme setting overrides that preference. Confirm the site supports dark mode. If it uses a theme control, interact with that control as part of the test; the media preference cannot invent dark CSS.
The first render is light, then changes. The preference was set after navigation or after the app initialized. Set colorScheme: 'dark' on the browser context before creating the page and navigating.
The screenshot is blank or incomplete. Navigation finished before required content rendered, or the page has asynchronous work. Wait for a relevant locator or application-ready signal before capture; verify the page URL and navigation errors.
Images are missing in a full-page shot. Some pages lazy-load images only when their region is brought into view. Scroll through the page or wait for the relevant images to load before taking the full-page screenshot.
The file format or dimensions are unexpected. The path extension, screenshot type, scale, viewport, or device scale factor differs from the intended output. Set type and scale explicitly and specify context viewport/device scale factor as needed.
Visual snapshots differ between runs or machines. Rendering environment, animations, dynamic data, or content changed. Use the same browser and host environment, disable animations if appropriate, and stabilize dynamic content before capture.
Browser launch fails. The browser binary has not been installed for the Playwright version in use, or the environment lacks required dependencies. Run npx playwright install chromium (or playwright install chromium for Python) and follow the official installation guidance for the operating system.

8. Performance, reliability, and cost

Local Playwright captures run in your own environment, so there is no per-screenshot ScreenshotNeo charge for this DIY route. You manage browser installation, updates, compute, storage, retries, and any proxy or network costs yourself. A remote screenshot API trades that setup for an HTTP request and the provider’s plan pricing.

For faster and more predictable local captures, launch a browser once and reuse it across pages or jobs, while keeping each job’s context settings explicit. Avoid capturing a full page when only the viewport or one element is needed. Select CSS scale when device-pixel output is unnecessary, and choose a compressed format when file size matters. For visual baselines, stability usually matters more than shaving a small amount off capture time: wait for the relevant content and control the environment.

ScreenshotNeo is a website screenshot API and MCP server from Yorker Media. It supports dark mode along with viewport and full-page capture, and its parameters are designed to accept names used by other screenshot APIs to ease switching. Every plan includes all features. Pricing is Free for 1,000 shots per month with no card; Starter is $5 for 3,000, Growth $15 for 15,000, Pro $39 for 60,000, Scale $99 for 250,000, and Business $249 for 1,000,000. Yearly billing gives two months free. See ScreenshotNeo for the product and the API docs for request configuration.

Or skip the browser setup

With ScreenshotNeo, one GET request can return a screenshot. Its dark-mode option lets you request a dark capture without installing or managing a browser locally:

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

Or use 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)

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

See the ScreenshotNeo docs for the dark-mode parameter and other request options. ScreenshotNeo accepts cookie and consent banners like a visitor and removes 60+ known consent platforms, newsletter popups, and chat widgets before the shot; each step can be turned off. Bot checks, blank pages, failed loads, and cache hits cost nothing, and response headers report the page verdict and billing status. Its MCP server gives AI agents tools for screenshots, page information, and PDF capture. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000. Sign up for 1,000 free screenshots a month, with no card required.

9. FAQ

Does emulating dark mode change the website’s saved theme?

No. It emulates a browser media preference for the page. A site’s own theme control may use separate state.

Can I take a dark-mode screenshot without launching a browser locally?

Yes. Use a screenshot API such as ScreenshotNeo, which accepts a URL and can return a screenshot without local Playwright browser setup.

Why does the same page look different in CI and on my computer?

Browser rendering can vary by operating system, browser build, settings, hardware, and headless mode. Keep those conditions consistent for visual comparison.