refactor: add sleep utility, reduce extension wait to 1s, add comments for CDP race condition

This commit is contained in:
Tommy D. Rossi
2025-12-21 17:43:11 +01:00
parent 9b73ab56f9
commit 2decaf6b83
4 changed files with 23 additions and 21 deletions
+8 -4
View File
@@ -5,6 +5,10 @@ import type { ExtensionCommandMessage, ExtensionResponseMessage } from 'playwrit
const RELAY_URL = 'ws://localhost:19988/extension'
function sleep(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms))
}
let ws: WebSocket | null = null
let childSessions: Map<string, number> = new Map()
let nextSessionId = 1
@@ -224,7 +228,7 @@ async function handleCommand(msg: ExtensionCommandMessage): Promise<any> {
}
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<any> {
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<Protocol.Target.TargetInfo> {
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<void> {
break
} catch {
logger.debug('Server not available, retrying in 1 second...')
await new Promise((resolve) => setTimeout(resolve, 1000))
await sleep(1000)
}
}
+8 -15
View File
@@ -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<vo
return
}
options.deviceScaleFactor = 2
// for (const page of context.pages()) {
// const delegate = (page as any)._delegate
// if (delegate?.updateEmulatedViewportSize) {
// await delegate.updateEmulatedViewportSize().catch(() => {})
// }
// }
}
async function preserveSystemColorScheme(context: BrowserContext): Promise<void> {
@@ -127,11 +121,6 @@ async function preserveSystemColorScheme(context: BrowserContext): Promise<void>
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<string | null> {
async function killRelayServer(port: number): Promise<void> {
try {
await killPortProcess(port)
await new Promise((resolve) => setTimeout(resolve, 500))
await sleep(500)
} catch {}
}
@@ -210,11 +199,11 @@ async function ensureRelayServer(): Promise<void> {
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)
+4
View File
@@ -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<void> {
return new Promise((resolve) => setTimeout(resolve, ms))
}
+3 -2
View File
@@ -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