add --color-scheme flag for persistent dark/light mode (#528)

Fixes #519. Playwright defaults `colorScheme` to `light` on all new contexts, overriding the browser/OS dark mode setting. This is especially disruptive in CDP mode, where every reconnection resets the scheme. The `set media dark` command also didn't persist its choice to new tabs or pages.

- Add `--color-scheme <dark|light|no-preference>` flag, config key (`colorScheme`), and env var (`AGENT_BROWSER_COLOR_SCHEME`)
- Store the preference in `BrowserManager` and automatically apply it to all new contexts (via Playwright's context option) and all new pages (via `page.emulateMedia` in `setupPageTracking`)
- `set media dark/light` now also persists its choice for subsequent pages and tabs
This commit is contained in:
Chris Tate
2026-02-23 01:50:17 -06:00
committed by GitHub
parent 467b830974
commit 12d79e4428
14 changed files with 112 additions and 4 deletions
+25 -1
View File
@@ -97,6 +97,15 @@ export class BrowserManager {
private refMap: RefMap = {};
private lastSnapshot: string = '';
private scopedHeaderRoutes: Map<string, (route: Route) => Promise<void>> = new Map();
private colorScheme: 'light' | 'dark' | 'no-preference' | null = null;
/**
* Set the persistent color scheme preference.
* Applied automatically to all new pages and contexts.
*/
setColorScheme(scheme: 'light' | 'dark' | 'no-preference' | null): void {
this.colorScheme = scheme;
}
// CDP session for screencast and input injection
private cdpSession: CDPSession | null = null;
@@ -252,7 +261,9 @@ export class BrowserManager {
if (this.contexts.length > 0) {
context = this.contexts[this.contexts.length - 1];
} else if (this.browser) {
context = await this.browser.newContext();
context = await this.browser.newContext({
...(this.colorScheme && { colorScheme: this.colorScheme }),
});
context.setDefaultTimeout(60000);
this.contexts.push(context);
this.setupContextTracking(context);
@@ -1140,6 +1151,10 @@ export class BrowserManager {
}
}
if (options.colorScheme) {
this.colorScheme = options.colorScheme;
}
if (cdpEndpoint) {
await this.connectViaCDP(cdpEndpoint);
return;
@@ -1224,6 +1239,7 @@ export class BrowserManager {
userAgent: options.userAgent,
...(options.proxy && { proxy: options.proxy }),
ignoreHTTPSErrors: options.ignoreHTTPSErrors ?? false,
...(this.colorScheme && { colorScheme: this.colorScheme }),
}
);
this.isPersistentContext = true;
@@ -1240,6 +1256,7 @@ export class BrowserManager {
userAgent: options.userAgent,
...(options.proxy && { proxy: options.proxy }),
ignoreHTTPSErrors: options.ignoreHTTPSErrors ?? false,
...(this.colorScheme && { colorScheme: this.colorScheme }),
});
this.isPersistentContext = true;
} else {
@@ -1325,6 +1342,7 @@ export class BrowserManager {
storageState,
...(options.proxy && { proxy: options.proxy }),
ignoreHTTPSErrors: options.ignoreHTTPSErrors ?? false,
...(this.colorScheme && { colorScheme: this.colorScheme }),
});
}
@@ -1555,6 +1573,10 @@ export class BrowserManager {
* Set up console, error, and close tracking for a page
*/
private setupPageTracking(page: Page): void {
if (this.colorScheme) {
page.emulateMedia({ colorScheme: this.colorScheme }).catch(() => {});
}
page.on('console', (msg) => {
this.consoleMessages.push({
type: msg.type(),
@@ -1642,6 +1664,7 @@ export class BrowserManager {
const context = await this.browser.newContext({
viewport: viewport === undefined ? { width: 1280, height: 720 } : viewport,
...(this.colorScheme && { colorScheme: this.colorScheme }),
});
context.setDefaultTimeout(60000);
this.contexts.push(context);
@@ -2381,6 +2404,7 @@ export class BrowserManager {
this.kernelApiKey = null;
this.isPersistentContext = false;
this.activePageIndex = 0;
this.colorScheme = null;
this.refMap = {};
this.lastSnapshot = '';
this.frameCallback = null;