From 2decaf6b83fc6264ce7118420b93498487e5e6c4 Mon Sep 17 00:00:00 2001 From: "Tommy D. Rossi" Date: Sun, 21 Dec 2025 17:43:11 +0100 Subject: [PATCH] refactor: add sleep utility, reduce extension wait to 1s, add comments for CDP race condition --- extension/src/background.ts | 12 ++++++++---- playwriter/src/mcp.ts | 23 ++++++++--------------- playwriter/src/utils.ts | 4 ++++ playwriter/src/wait-for-page-load.ts | 5 +++-- 4 files changed, 23 insertions(+), 21 deletions(-) diff --git a/extension/src/background.ts b/extension/src/background.ts index b152c6e..27bafe1 100644 --- a/extension/src/background.ts +++ b/extension/src/background.ts @@ -5,6 +5,10 @@ import type { ExtensionCommandMessage, ExtensionResponseMessage } from 'playwrit const RELAY_URL = 'ws://localhost:19988/extension' +function sleep(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)) +} + let ws: WebSocket | null = null let childSessions: Map = new Map() let nextSessionId = 1 @@ -224,7 +228,7 @@ async function handleCommand(msg: ExtensionCommandMessage): Promise { } try { await chrome.debugger.sendCommand(debuggee, 'Runtime.disable') - await new Promise((resolve) => setTimeout(resolve, 200)) + await sleep(200) } catch (e) { logger.debug('Error disabling Runtime (ignoring):', e) } @@ -237,7 +241,7 @@ async function handleCommand(msg: ExtensionCommandMessage): Promise { const tab = await chrome.tabs.create({ url, active: false }) if (!tab.id) throw new Error('Failed to create tab') logger.debug('Created tab:', tab.id, 'waiting for it to load...') - await new Promise((resolve) => setTimeout(resolve, 100)) + await sleep(100) const targetInfo = await attachTab(tab.id) return { targetId: targetInfo.targetId } satisfies Protocol.Target.CreateTargetResponse } @@ -350,7 +354,7 @@ async function attachTab(tabId: number): Promise { await chrome.debugger.attach(debuggee, '1.3') logger.debug('Debugger attached successfully to tab:', tabId) - await new Promise((resolve) => setTimeout(resolve, 400)) + await sleep(400) const result = (await chrome.debugger.sendCommand( debuggee, @@ -495,7 +499,7 @@ async function ensureConnection(): Promise { break } catch { logger.debug('Server not available, retrying in 1 second...') - await new Promise((resolve) => setTimeout(resolve, 1000)) + await sleep(1000) } } diff --git a/playwriter/src/mcp.ts b/playwriter/src/mcp.ts index ed59eaf..4d9e195 100644 --- a/playwriter/src/mcp.ts +++ b/playwriter/src/mcp.ts @@ -11,7 +11,7 @@ import { fileURLToPath } from 'node:url' import vm from 'node:vm' import dedent from 'string-dedent' import { createPatch } from 'diff' -import { getCdpUrl, LOG_FILE_PATH, VERSION } from './utils.js' +import { getCdpUrl, LOG_FILE_PATH, VERSION, sleep } from './utils.js' import { killPortProcess } from 'kill-port-process' import { waitForPageLoad, WaitForPageLoadOptions, WaitForPageLoadResult } from './wait-for-page-load.js' import { getCDPSessionForPage, CDPSession } from './cdp-session.js' @@ -111,12 +111,6 @@ async function setDeviceScaleFactorForMacOS(context: BrowserContext): Promise {}) - // } - // } } async function preserveSystemColorScheme(context: BrowserContext): Promise { @@ -127,11 +121,6 @@ async function preserveSystemColorScheme(context: BrowserContext): Promise options.colorScheme = 'no-override' options.reducedMotion = 'no-override' options.forcedColors = 'no-override' - // await Promise.all( - // context.pages().map((page) => { - // return page.emulateMedia({ colorScheme: null, reducedMotion: null, forcedColors: null }).catch(() => {}) - // }), - // ) } function isRegExp(value: any): value is RegExp { @@ -182,7 +171,7 @@ async function getServerVersion(port: number): Promise { async function killRelayServer(port: number): Promise { try { await killPortProcess(port) - await new Promise((resolve) => setTimeout(resolve, 500)) + await sleep(500) } catch {} } @@ -210,11 +199,11 @@ async function ensureRelayServer(): Promise { serverProcess.unref() for (let i = 0; i < 10; i++) { - await new Promise((resolve) => setTimeout(resolve, 500)) + await sleep(500) const newVersion = await getServerVersion(RELAY_PORT) if (newVersion === VERSION) { console.error('CDP relay server started successfully, waiting for extension to connect...') - await new Promise((resolve) => setTimeout(resolve, 3000)) + await sleep(1000) return } } @@ -249,6 +238,10 @@ async function ensureConnection(): Promise<{ browser: Browser; page: Page }> { // Set up console listener for all existing pages context.pages().forEach((p) => setupPageConsoleListener(p)) + // These functions only set context-level options, they do NOT send CDP commands to pages. + // Sending CDP commands (like Emulation.setEmulatedMedia or setDeviceMetricsOverride) to pages + // immediately after connectOverCDP causes pages to render white/blank with about:blank URLs, + // because pages may not be fully initialized yet. Playwright applies these settings lazily. await preserveSystemColorScheme(context) await setDeviceScaleFactorForMacOS(context) diff --git a/playwriter/src/utils.ts b/playwriter/src/utils.ts index 00f85ea..d375d1c 100644 --- a/playwriter/src/utils.ts +++ b/playwriter/src/utils.ts @@ -12,3 +12,7 @@ export const LOG_FILE_PATH = path.join(os.tmpdir(), 'playwriter', 'relay-server. const packageJsonPath = path.join(path.dirname(fileURLToPath(import.meta.url)), '..', 'package.json') export const VERSION = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8')).version as string + +export function sleep(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)) +} diff --git a/playwriter/src/wait-for-page-load.ts b/playwriter/src/wait-for-page-load.ts index 5fe080c..4b84856 100644 --- a/playwriter/src/wait-for-page-load.ts +++ b/playwriter/src/wait-for-page-load.ts @@ -1,4 +1,5 @@ import type { Page } from 'playwright-core' +import { sleep } from './utils.js' const FILTERED_DOMAINS = [ 'doubleclick', @@ -130,7 +131,7 @@ export async function waitForPageLoad(options: WaitForPageLoadOptions): Promise< return result } - await new Promise((resolve) => setTimeout(resolve, minWait)) + await sleep(minWait) while (Date.now() - startTime < timeout) { try { @@ -158,7 +159,7 @@ export async function waitForPageLoad(options: WaitForPageLoadOptions): Promise< } } - await new Promise((resolve) => setTimeout(resolve, pollInterval)) + await sleep(pollInterval) } timedOut = true