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:
@@ -2069,6 +2069,9 @@ async function handleEmulateMedia(
|
||||
reducedMotion: command.reducedMotion,
|
||||
forcedColors: command.forcedColors,
|
||||
});
|
||||
if (command.colorScheme) {
|
||||
browser.setColorScheme(command.colorScheme);
|
||||
}
|
||||
return successResponse(command.id, { emulated: true });
|
||||
}
|
||||
|
||||
|
||||
+25
-1
@@ -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;
|
||||
|
||||
@@ -418,6 +418,13 @@ export async function startDaemon(options?: {
|
||||
|
||||
const ignoreHTTPSErrors = process.env.AGENT_BROWSER_IGNORE_HTTPS_ERRORS === '1';
|
||||
const allowFileAccess = process.env.AGENT_BROWSER_ALLOW_FILE_ACCESS === '1';
|
||||
const colorSchemeEnv = process.env.AGENT_BROWSER_COLOR_SCHEME;
|
||||
const colorScheme =
|
||||
colorSchemeEnv === 'dark' ||
|
||||
colorSchemeEnv === 'light' ||
|
||||
colorSchemeEnv === 'no-preference'
|
||||
? colorSchemeEnv
|
||||
: undefined;
|
||||
await manager.launch({
|
||||
id: 'auto',
|
||||
action: 'launch' as const,
|
||||
@@ -431,6 +438,7 @@ export async function startDaemon(options?: {
|
||||
proxy,
|
||||
ignoreHTTPSErrors: ignoreHTTPSErrors,
|
||||
allowFileAccess: allowFileAccess,
|
||||
colorScheme,
|
||||
autoStateFilePath: getSessionAutoStatePath(),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -49,6 +49,7 @@ const launchSchema = baseCommandSchema.extend({
|
||||
provider: z.string().optional(),
|
||||
ignoreHTTPSErrors: z.boolean().optional(),
|
||||
allowFileAccess: z.boolean().optional(),
|
||||
colorScheme: z.enum(['light', 'dark', 'no-preference']).optional(),
|
||||
profile: z.string().optional(),
|
||||
storageState: z.string().optional(),
|
||||
});
|
||||
|
||||
@@ -31,6 +31,7 @@ export interface LaunchCommand extends BaseCommand {
|
||||
provider?: string;
|
||||
ignoreHTTPSErrors?: boolean;
|
||||
allowFileAccess?: boolean; // Enable file:// URL access and cross-origin file requests
|
||||
colorScheme?: 'light' | 'dark' | 'no-preference'; // Persistent color scheme override
|
||||
// Auto-load state file for session persistence
|
||||
autoStateFilePath?: string;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user