ScreenshotNeo

BlogHow-to

Fix Dark Mode Showing Up in Automated Website Screenshots

Unexpected dark screenshots often come from the browser’s color-scheme preference or the site’s saved theme. Diagnose both and set Playwright’s mode explicitly.

By the ScreenshotNeo team4 October 20266 min read

If an automated website screenshot unexpectedly appears dark, first check the color-scheme preference the browser exposes to the page, then check whether the site has selected or saved its own dark theme. In Playwright, set colorScheme explicitly and verify the page’s prefers-color-scheme media query before capturing. The screenshot records the page as rendered; changing the image capture step will not fix a page that is already rendering in dark mode.

1. Check what the page is rendering

Run this in the affected page before taking the screenshot:

const scheme = await page.evaluate(() => ({
  dark: matchMedia('(prefers-color-scheme: dark)').matches,
  light: matchMedia('(prefers-color-scheme: light)').matches,
}));
console.log(scheme);

If dark is true, the page sees a dark preference. If light is true but the site still looks dark, inspect the application’s theme controls, initialization logic, and persisted state. A website can choose its theme independently of the media query, so the media-query result is a useful diagnostic, not proof of the site’s full theme state.

Playwright documents color-scheme configuration in project/test settings, browser context options, page creation options, and page.emulateMedia. Check all of them: a later page-level emulation call can override an earlier setting. Playwright: Emulation and the Page API document these options.

2. Force light or dark mode in Playwright

Choose one intended scheme for the capture run. Here is a complete Node.js example using Playwright’s test runner and a browser context:

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

test('capture the page in light mode', async ({ browser }) => {
  const context = await browser.newContext({ colorScheme: 'light' });
  const page = await context.newPage();

  await page.goto('https://example.com', { waitUntil: 'networkidle' });

  const scheme = await page.evaluate(() => ({
    dark: matchMedia('(prefers-color-scheme: dark)').matches,
    light: matchMedia('(prefers-color-scheme: light)').matches,
  }));
  console.log('Page color-scheme preference:', scheme);
  expect(scheme.dark).toBe(false);

  await page.screenshot({ path: 'example-light.png', fullPage: true });
  await context.close();
});

For dark mode, change colorScheme: 'light' to colorScheme: 'dark' and assert that scheme.dark is true. The assertion catches configuration drift early, before an unexpected image gets saved.

Set a shared test or project default

When many tests need the same preference, configure it once in Playwright’s test configuration:

import { defineConfig } from '@playwright/test';

export default defineConfig({
  use: {
    colorScheme: 'light',
  },
});

A test can still create a context or call page.emulateMedia with a different scheme. Search for those overrides if only some screenshots are wrong.

Override the preference on an existing page

await page.emulateMedia({ colorScheme: 'light' });
const isDark = await page.evaluate(() =>
  matchMedia('(prefers-color-scheme: dark)').matches
);
if (isDark) throw new Error('Expected light color scheme');

Playwright supports 'light', 'dark', and null to disable color-scheme emulation. The documented 'no-preference' value is deprecated. Use null when you want to remove the explicit emulation and return control to the browser context’s normal behavior.

3. Reproduce the issue in Chrome DevTools

  1. Open Chrome DevTools and show the Rendering panel.
  2. Under the emulated CSS media feature for prefers-color-scheme, choose light or dark.
  3. Refresh the page and compare its appearance with the automated capture.
  4. Check whether Chrome’s Automatic Dark Theme effect is enabled. Chrome documents that it sets the emulated scheme to dark and disables the ordinary preference selector.

This separates a browser-preference mismatch from application-specific theme state. See Chrome’s guides to emulating CSS media features and applying effects, including automatic dark theme.

4. Diagnose site-specific theme state

If Playwright reports light but the page looks dark, inspect how the application initializes its theme. Check whether it sets a theme attribute or class on the document, reads a saved preference, or offers an in-page theme selector. For a reliable reproduction, use the same application state as the real user: start with the same storage state, cookies, and URL, then inspect the site’s own theme control. Do not assume that changing colorScheme will override a theme the application has explicitly stored.

5. Troubleshoot common causes

Symptom Likely cause What to do
The page is dark and the dark media query is true A project, test, context, or page setting selected dark. Set colorScheme: 'light' at the configuration level used by the test. Check later emulateMedia calls.
The shared test config says light, but one test is dark A context option or page-level override takes precedence for that capture. Search the test and setup code for colorScheme and emulateMedia; log the media-query result just before the screenshot.
The media query says light, but the design is dark The site may use its own selected or persisted theme. Inspect the application’s theme initialization, theme toggle, and relevant browser state.
DevTools keeps using dark or the selector is unavailable Automatic Dark Theme may be active. Check the Rendering panel’s effects and disable that behavior for the manual comparison, or account for its forced dark preference.
Light and dark assertions both appear unexpected The test may be checking at the wrong time or a later call changed emulation. Evaluate the media queries immediately before capture and inspect all preference overrides in the test flow.
The screenshot differs from a local manual capture The two runs may have different browser preference or application theme state. Compare the requested scheme, the media-query result, and the page’s own theme state in both runs.

6. Keep screenshot runs predictable

  • Make the preference explicit. Use one deliberate light or dark setting at the context or shared test configuration level.
  • Verify at capture time. Check matchMedia after navigation and immediately before the screenshot if setup code can alter emulation.
  • Control application state. Keep cookies and storage consistent when the application remembers its own theme.
  • Compare like with like. Use the same URL, browser, viewport, and page state when comparing runs.
  • Capture both modes when needed. A deliberate light and dark pass can reveal whether the site responds to the preference as expected.

web.dev describes using Puppeteer to capture a page in both light and dark modes: prefers-color-scheme: Hello darkness, my old friend. The same basic principle applies across browser automation tools: set the desired mode and verify what the page receives.

7. Performance, reliability, and cost

Color-scheme selection is a browser rendering preference, so the main reliability concern is configuration and page state rather than image processing. Explicit configuration plus a pre-capture check makes failures easier to identify. If you capture pages in both schemes, each run must reach the intended page state before its screenshot is useful; keep waits and storage state consistent with the page you are trying to reproduce.

The 2024 Web Almanac reports that 12% of desktop and mobile websites used the prefers-color-scheme media query in 2024, up from 8% in 2022. That statistic measures media-query use, not complete dark-mode support. HTTP Archive, 2024 Web Almanac.

Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server for developers. Its one-call API accepts a URL and returns a PNG, JPEG, WebP, or PDF. For a light-mode capture, pass color_scheme=light as an option; use dark for dark mode. See the ScreenshotNeo API documentation for request options.

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

Cookie banners, popups, and chat widgets are removed before the shot. Bot checks, blank pages, and failed loads are never billed. An MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000. Create a free account and get 1,000 screenshots a month with no card.

FAQ

Does changing my operating system theme fix the automation?

It may not. Playwright can emulate a color scheme through its test, context, or page configuration, so inspect what the page reports instead of inferring the preference from the desktop.

Can I test light and dark mode without changing my OS theme?

Yes. Set Playwright’s colorScheme to 'light' or 'dark' for each run and verify the corresponding media query in the page.

What does a dark media-query result prove?

It proves that the page sees the dark preference. It does not prove that every part of the site supports dark mode or that the site has no separate theme setting.

Why does DevTools disagree with Playwright?

Compare the emulated scheme in both environments and check whether Chrome’s Automatic Dark Theme is forcing dark mode during manual reproduction.