Modern web applications commonly support both light and dark themes to enhance user experience. When automating screenshot captures for testing or documentation, handling both themes effectively becomes crucial. Let's explore how to master theme-aware screenshots using Playwright.
Capturing theme-specific screenshots presents several challenges:
Theme Detection : Websites implement themes differently - some use CSS classes, others use data attributes or media queries
Theme Switching : Ensuring the theme has fully applied before capturing
Visual Consistency : Maintaining consistent results across different environments
Dynamic Content : Handling theme-dependent assets and transitions
Let's start with a basic implementation that handles both themes:
import { chromium, Browser, Page } from 'playwright' ;
interface ThemeScreenshotOptions {
url : string ;
selector ?: string ;
fullPage ?: boolean ;
waitForTimeout ?: number ;
}
async function captureThemeScreenshots ( options : ThemeScreenshotOptions ) {
const browser = await chromium. launch ();
const context = await browser. newContext ();
const page = await context. newPage ();
try {
// Navigate to the page
await page. goto (options.url, {
waitUntil: 'networkidle'
});
// Capture light theme first
const lightScreenshot = await captureScreenshot (page, {
... options,
theme: 'light'
});
// Switch to dark theme and capture
const darkScreenshot = await captureScreenshot (page, {
... options,
theme: 'dark'
});
return {
light: lightScreenshot,
dark: darkScreenshot
};
} finally {
await browser. close ();
}
}
To ensure consistent theme rendering, we'll inject custom styles:
const themeScripts = {
dark: `
(function() {
document.documentElement.classList.add('dark');
document.body.classList.add('dark');
const style = document.createElement('style');
style.textContent = \`
html.dark, body.dark {
background-color: #1a1a1a !important;
color: #ffffff !important;
}
html.dark *, body.dark * {
color-scheme: dark !important;
}
html.dark img, body.dark img {
filter: brightness(.8) contrast(1.2);
}
\` ;
document.head.appendChild(style);
})();
` ,
light: `
(function() {
document.documentElement.classList.remove('dark');
document.body.classList.remove('dark');
const style = document.createElement('style');
style.textContent = \`
html, body {
background-color: #ffffff !important;
color: #000000 !important;
}
* {
color-scheme: light !important;
}
\` ;
document.head.appendChild(style);
})();
`
};
Here's a more robust implementation that handles various theme scenarios:
async function captureScreenshot ( page : Page , options : {
theme : 'light' | 'dark' ,
selector ?: string ,
fullPage ?: boolean ,
waitForTimeout ?: number
}) {
// Set color scheme at browser level
await page. emulateMedia ({ colorScheme: options.theme });
// Inject theme-specific scripts
await page. addInitScript (themeScripts[options.theme]);
// Handle common theme storage mechanisms
await page. evaluate (( theme ) => {
// Local storage
localStorage. setItem ( 'theme' , theme);
// Data attributes
document.documentElement. setAttribute ( 'data-theme' , theme);
// Media query override
window. matchMedia = ( query ) => ({
matches: query. includes ( 'dark' ) ? theme === 'dark' : theme === 'light' ,
addEventListener : () => {},
removeEventListener : () => {}
});
}, options.theme);
// Wait for theme to apply
await page. waitForTimeout (options.waitForTimeout || 1000 );
// Wait for any theme transitions
await page. waitForFunction (() => {
const elements = document. querySelectorAll ( '[class*="transition"]' );
return Array. from (elements). every (
el => getComputedStyle (el).transitionDuration === '0s'
);
});
// Take the screenshot
if (options.selector) {
const element = await page. $ (options.selector);
return element?. screenshot ({
type: 'png' ,
fullPage: options.fullPage
});
}
return page. screenshot ({
type: 'png' ,
fullPage: options.fullPage
});
}
Different websites implement themes in various ways. Here's how to handle common patterns:
async function handleThemePatterns ( page : Page , theme : 'light' | 'dark' ) {
// Handle CSS class-based themes
await page. evaluate (( theme ) => {
const classList = document.documentElement.classList;
classList. remove ( 'light' , 'dark' );
classList. add (theme);
}, theme);
// Handle data attribute-based themes
await page. evaluate (( theme ) => {
document.documentElement. setAttribute ( 'data-theme' , theme);
document.documentElement. setAttribute ( 'data-color-scheme' , theme);
}, theme);
// Handle CSS variables
await page. evaluate (( theme ) => {
const root = document.documentElement;
if (theme === 'dark' ) {
root.style. setProperty ( '--background' , '#1a1a1a' );
root.style. setProperty ( '--text' , '#ffffff' );
} else {
root.style. setProperty ( '--background' , '#ffffff' );
root.style. setProperty ( '--text' , '#000000' );
}
}, theme);
}
Wait for Theme Application
async function waitForThemeApplication ( page : Page ) {
// Wait for any CSS transitions to complete
await page. waitForFunction (() => {
return ! document. querySelector ( '[class*="transition"][style*="transition"]' );
});
// Additional delay for safety
await page. waitForTimeout ( 500 );
}
Handle Dynamic Content
async function waitForThemeContent ( page : Page ) {
// Wait for theme-specific images
await page. waitForFunction (() => {
const images = document. querySelectorAll ( 'img' );
return Array. from (images). every ( img => img.complete);
});
}
Verify Theme Application
async function verifyTheme ( page : Page , theme : 'light' | 'dark' ) {
const backgroundColor = await page. evaluate (() => {
return getComputedStyle (document.body).backgroundColor;
});
const isDark = theme === 'dark' ;
const isCorrectTheme = isDark ?
backgroundColor. includes ( '1a1a1a' ) :
backgroundColor. includes ( '255, 255, 255' );
if ( ! isCorrectTheme) {
throw new Error ( `Theme verification failed: Expected ${ theme } theme` );
}
}
Here's how to use these implementations:
async function example () {
const screenshots = await captureThemeScreenshots ({
url: 'https://example.com' ,
fullPage: true ,
waitForTimeout: 2000
});
console. log ( 'Screenshots captured:' , {
light: screenshots.light ? 'Success' : 'Failed' ,
dark: screenshots.dark ? 'Success' : 'Failed'
});
}
Visual Regression Testing
Automatically detect unwanted theme-related changes during development
Compare theme implementations across different branches
Ensure consistent styling across theme switches
async function visualRegressionTest () {
const baselineScreenshots = await loadBaselineScreenshots ();
const currentScreenshots = await captureThemeScreenshots ({
url: 'https://your-app.com' ,
fullPage: true
});
const differences = await compareScreenshots (baselineScreenshots, currentScreenshots);
if (differences. length > 0 ) {
console. error ( 'Visual differences detected:' , differences);
}
}
Documentation Generation
Automatically generate theme-aware documentation
Create visual guides showing both theme variants
Document component states across themes
async function generateComponentDocs () {
const components = [ 'button' , 'card' , 'modal' ];
const states = [ 'default' , 'hover' , 'active' , 'disabled' ];
for ( const component of components) {
for ( const state of states) {
await captureThemeScreenshots ({
url: `https://your-docs.com/components/${ component }` ,
selector: `#${ component }-${ state }` ,
waitForTimeout: 1000
});
}
}
}
Cross-browser Theme Testing
Verify theme consistency across different browsers
Test theme implementations in various viewport sizes
async function crossBrowserThemeTest () {
const browsers = [ 'chromium' , 'firefox' , 'webkit' ];
const viewports = [
{ width: 375 , height: 667 }, // Mobile
{ width: 1024 , height: 768 }, // Tablet
{ width: 1920 , height: 1080 } // Desktop
];
for ( const browser of browsers) {
for ( const viewport of viewports) {
await captureThemeScreenshots ({
url: 'https://your-app.com' ,
browserType: browser,
viewport
});
}
}
}
Continuous Integration
Integrate theme testing into your CI/CD pipeline
Block deployments on theme-related visual regressions
async function ciThemeCheck () {
try {
await captureAndCompareThemes ();
console. log ( 'Theme verification passed' );
process. exit ( 0 );
} catch (error) {
console. error ( 'Theme verification failed:' , error);
process. exit ( 1 );
}
}
Playwright Official Documentation : Comprehensive guides and tutorials to get started with Playwright. View Documentation
Playwright API Reference : Detailed API documentation for Playwright's classes and methods. View API Reference
Playwright Test Documentation : Information on using Playwright's testing framework for end-to-end testing. View Test Documentation
MDN: Dark Mode : Learn about the color-scheme
CSS property for implementing dark mode. Read Article
Web.dev: Dark Mode : An article discussing the prefers-color-scheme
media query for dark mode support. Read Article
CSS Tricks: Dark Mode : Insights into implementing dark mode using CSS. Read Article
Percy : Integrate visual testing with Playwright using Percy's platform. Learn More
Applitools Eyes : Automated visual testing for Playwright applications. Learn More
Chromatic : Visual testing and review for UI components, integrating with Playwright. Learn More
Playwright Visual Regression Example : An example project demonstrating visual regression testing with Playwright. View Repository
Playwright Test Examples : A collection of test examples using Playwright. View Examples
Awesome Playwright : A curated list of awesome tools, utils, and projects using Playwright. View List
Playwright Discord Community : Join the Playwright community on Discord for discussions and support. Join Discord
Playwright GitHub Discussions : Engage in discussions and find answers to questions about Playwright. View Discussions
Stack Overflow: Playwright Tags : Browse questions tagged with 'playwright' on Stack Overflow for community support. View Questions
Advanced Playwright Testing Patterns : Explore advanced testing patterns with Playwright. Read More
Visual Regression Testing Best Practices : Learn about techniques and importance of visual regression testing. Read More
Implementing Dark Mode in Web Applications : A guide on implementing dark mode in web applications. Read More
Automated Visual Testing at Scale : Insights into scaling automated visual testing for mobile and web applications. Read More
Remember that theme testing is just one part of a comprehensive testing strategy. Combine these techniques with other testing approaches like unit testing, integration testing, and end-to-end testing for the most robust results.
Remember to:
Always wait for theme transitions to complete
Handle multiple theme implementation patterns
Verify theme application before capturing
Consider dynamic content loading
Implement proper error handling and validation
This approach provides a robust foundation for capturing theme-specific screenshots in your testing or documentation workflows.
Note : If you're looking for a managed solution, screenshotsapi.dev offers professional screenshot services with features like theme handling, responsive captures, and automated testing - all through a simple API without managing your own infrastructure.
Want to learn more about Playwright? Check out our other guides: