#6686: fix: clear Playwright's default colorScheme override on CDP-connected pages
Cluster:
Browser Enhancements and Fixes
## Problem
When Playwright connects to an existing browser via `connectOverCDP()`, it initialises each page with `Emulation.setEmulatedMedia` using a default `colorScheme` of `"light"`. This is a hardcoded fallback in Playwright's `Page.emulatedMedia()` ([source](https://github.com/nicephil/playwright/blob/1a60af3e9e26b91b70c0fe7d4e54f14bbae33f69/packages/playwright-core/src/server/page.ts#L434)):
```typescript
colorScheme: this._emulatedMedia.colorScheme !== void 0
? this._emulatedMedia.colorScheme
: contextOptions.colorScheme ?? "light", // ← fallback
```
Since `connectOverCDP` creates contexts with no explicit `colorScheme` option, every page gets `"light"` applied via CDP's `Emulation.setEmulatedMedia`. This override:
1. **Flips the host tab to light mode** regardless of the user's system `prefers-color-scheme` setting
2. **Persists after disconnection** — Chrome keeps the emulated media override even after the CDP session ends
3. **Is most visible with the Chrome extension relay** — after a screenshot or snapshot, the attached tab stays in light mode
## Root Cause Trace
```
connectOverCDP()
→ Playwright creates Page wrappers
→ CRFrameSession._initialize() calls _updateEmulateMedia()
→ page.emulatedMedia() returns { colorScheme: "light" } (default)
→ Emulation.setEmulatedMedia({ features: [{ name: "prefers-color-scheme", value: "light" }] })
→ Host tab's color scheme is now overridden
```
## Fix
Add `clearDefaultColorScheme()` — calls `page.emulateMedia({ colorScheme: null })` which maps to Playwright's internal `"no-override"` sentinel, sending an empty-string feature value via CDP that clears the override and restores the browser's native preference.
Applied in two places:
- **`connectBrowser()`** — clears override on all existing pages immediately after CDP connection
- **`observeContext()`** — clears override on new pages via the context `"page"` event handler
## Changes
Single file: `src/browser/pw-session.ts` (+27 lines)
- New `clearDefaultColorScheme(page)` helper with JSDoc explaining the Playwright internals
- Called after `observeBrowser()` in `connectBrowser()` for existing pages
- Called in the `context.on("page", ...)` handler for new pages
The explicit `/set/media` endpoint (`emulateMediaViaPlaywright`) still works — it sets `_emulatedMedia.colorScheme` on the page object, which takes precedence over the cleared default on subsequent `_updateEmulateMedia()` calls.
## Testing
Manually verified the fix path:
- `page.emulateMedia({ colorScheme: null })` → Playwright sends `"no-override"` → CDP receives `prefers-color-scheme: ""` → Chrome clears the override
- Confirmed the managed browser path is unaffected (headless Chrome has no system preference to override)
- The `/set/media` endpoint continues to work correctly after this fix
Relates to #6167
<!-- greptile_comment -->
<h2>Greptile Overview</h2>
<h3>Greptile Summary</h3>
This PR adds a small helper in `src/browser/pw-session.ts` to clear Playwright’s implicit `prefers-color-scheme: light` emulation that occurs when connecting to an existing browser via `chromium.connectOverCDP()`. The helper is applied both for already-open pages right after the CDP connection is established and for newly created pages via the context `"page"` event, preventing the host tab from being left in a forced light-mode state after the session ends.
<h3>Confidence Score: 5/5</h3>
- This PR is safe to merge with minimal risk.
- The change is localized to CDP-connected page initialization and uses Playwright’s supported `page.emulateMedia({ colorScheme: null })` API in a best-effort way; failures are swallowed and existing behavior should remain intact aside from removing an unintended persistent override.
- No files require special attention
<!-- greptile_other_comments_section -->
<sub>(3/5) Reply to the agent's comments like "Can you suggest a fix for this @greptileai?" or ask follow-up questions!</sub>
<!-- /greptile_comment -->
Most Similar PRs
#4663: fix: per-profile browser caching to allow parallel connections
by tsukhani · 2026-01-30
79.4%
#19823: fix(browser): stability improvements for headless Chrome
by Milofax · 2026-02-18
79.4%
#8303: fix(browser): enable downloads via CDP Browser.setDownloadBehavior
by gavinbmoore · 2026-02-03
76.2%
#14944: fix(browser): prefer openclaw profile in headless/noSandbox environ...
by BenediktSchackenberg · 2026-02-12
75.2%
#23363: Browser: fallback to managed profile and improve runtime diagnostics
by isdoho · 2026-02-22
73.9%
#9020: fix(browser): skip port ownership and WS reachability checks for re...
by yepack · 2026-02-04
73.8%
#9693: fix: add configurable browser.actTimeoutMs to prevent action timeouts
by JitendraZaa · 2026-02-05
72.1%
#13568: Fix browser (OpenClaw-managed) launch fail by binding remote debugg...
by singlag · 2026-02-10
71.9%
#12075: feat(browser): session-aware context isolation for multi-agent brow...
by xiaoyaner0201 · 2026-02-08
71.5%
#8007: Firefox browser support
by krazyjakee · 2026-02-03
71.0%