refactor: add sleep utility, reduce extension wait to 1s, add comments for CDP race condition
This commit is contained in:
@@ -5,6 +5,10 @@ import type { ExtensionCommandMessage, ExtensionResponseMessage } from 'playwrit
|
|||||||
|
|
||||||
const RELAY_URL = 'ws://localhost:19988/extension'
|
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 ws: WebSocket | null = null
|
||||||
let childSessions: Map<string, number> = new Map()
|
let childSessions: Map<string, number> = new Map()
|
||||||
let nextSessionId = 1
|
let nextSessionId = 1
|
||||||
@@ -224,7 +228,7 @@ async function handleCommand(msg: ExtensionCommandMessage): Promise<any> {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await chrome.debugger.sendCommand(debuggee, 'Runtime.disable')
|
await chrome.debugger.sendCommand(debuggee, 'Runtime.disable')
|
||||||
await new Promise((resolve) => setTimeout(resolve, 200))
|
await sleep(200)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.debug('Error disabling Runtime (ignoring):', 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 })
|
const tab = await chrome.tabs.create({ url, active: false })
|
||||||
if (!tab.id) throw new Error('Failed to create tab')
|
if (!tab.id) throw new Error('Failed to create tab')
|
||||||
logger.debug('Created tab:', tab.id, 'waiting for it to load...')
|
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)
|
const targetInfo = await attachTab(tab.id)
|
||||||
return { targetId: targetInfo.targetId } satisfies Protocol.Target.CreateTargetResponse
|
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')
|
await chrome.debugger.attach(debuggee, '1.3')
|
||||||
logger.debug('Debugger attached successfully to tab:', tabId)
|
logger.debug('Debugger attached successfully to tab:', tabId)
|
||||||
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 400))
|
await sleep(400)
|
||||||
|
|
||||||
const result = (await chrome.debugger.sendCommand(
|
const result = (await chrome.debugger.sendCommand(
|
||||||
debuggee,
|
debuggee,
|
||||||
@@ -495,7 +499,7 @@ async function ensureConnection(): Promise<void> {
|
|||||||
break
|
break
|
||||||
} catch {
|
} catch {
|
||||||
logger.debug('Server not available, retrying in 1 second...')
|
logger.debug('Server not available, retrying in 1 second...')
|
||||||
await new Promise((resolve) => setTimeout(resolve, 1000))
|
await sleep(1000)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+8
-15
@@ -11,7 +11,7 @@ import { fileURLToPath } from 'node:url'
|
|||||||
import vm from 'node:vm'
|
import vm from 'node:vm'
|
||||||
import dedent from 'string-dedent'
|
import dedent from 'string-dedent'
|
||||||
import { createPatch } from 'diff'
|
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 { killPortProcess } from 'kill-port-process'
|
||||||
import { waitForPageLoad, WaitForPageLoadOptions, WaitForPageLoadResult } from './wait-for-page-load.js'
|
import { waitForPageLoad, WaitForPageLoadOptions, WaitForPageLoadResult } from './wait-for-page-load.js'
|
||||||
import { getCDPSessionForPage, CDPSession } from './cdp-session.js'
|
import { getCDPSessionForPage, CDPSession } from './cdp-session.js'
|
||||||
@@ -111,12 +111,6 @@ async function setDeviceScaleFactorForMacOS(context: BrowserContext): Promise<vo
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
options.deviceScaleFactor = 2
|
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> {
|
async function preserveSystemColorScheme(context: BrowserContext): Promise<void> {
|
||||||
@@ -127,11 +121,6 @@ async function preserveSystemColorScheme(context: BrowserContext): Promise<void>
|
|||||||
options.colorScheme = 'no-override'
|
options.colorScheme = 'no-override'
|
||||||
options.reducedMotion = 'no-override'
|
options.reducedMotion = 'no-override'
|
||||||
options.forcedColors = '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 {
|
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> {
|
async function killRelayServer(port: number): Promise<void> {
|
||||||
try {
|
try {
|
||||||
await killPortProcess(port)
|
await killPortProcess(port)
|
||||||
await new Promise((resolve) => setTimeout(resolve, 500))
|
await sleep(500)
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -210,11 +199,11 @@ async function ensureRelayServer(): Promise<void> {
|
|||||||
serverProcess.unref()
|
serverProcess.unref()
|
||||||
|
|
||||||
for (let i = 0; i < 10; i++) {
|
for (let i = 0; i < 10; i++) {
|
||||||
await new Promise((resolve) => setTimeout(resolve, 500))
|
await sleep(500)
|
||||||
const newVersion = await getServerVersion(RELAY_PORT)
|
const newVersion = await getServerVersion(RELAY_PORT)
|
||||||
if (newVersion === VERSION) {
|
if (newVersion === VERSION) {
|
||||||
console.error('CDP relay server started successfully, waiting for extension to connect...')
|
console.error('CDP relay server started successfully, waiting for extension to connect...')
|
||||||
await new Promise((resolve) => setTimeout(resolve, 3000))
|
await sleep(1000)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -249,6 +238,10 @@ async function ensureConnection(): Promise<{ browser: Browser; page: Page }> {
|
|||||||
// Set up console listener for all existing pages
|
// Set up console listener for all existing pages
|
||||||
context.pages().forEach((p) => setupPageConsoleListener(p))
|
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 preserveSystemColorScheme(context)
|
||||||
await setDeviceScaleFactorForMacOS(context)
|
await setDeviceScaleFactorForMacOS(context)
|
||||||
|
|
||||||
|
|||||||
@@ -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')
|
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 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))
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import type { Page } from 'playwright-core'
|
import type { Page } from 'playwright-core'
|
||||||
|
import { sleep } from './utils.js'
|
||||||
|
|
||||||
const FILTERED_DOMAINS = [
|
const FILTERED_DOMAINS = [
|
||||||
'doubleclick',
|
'doubleclick',
|
||||||
@@ -130,7 +131,7 @@ export async function waitForPageLoad(options: WaitForPageLoadOptions): Promise<
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, minWait))
|
await sleep(minWait)
|
||||||
|
|
||||||
while (Date.now() - startTime < timeout) {
|
while (Date.now() - startTime < timeout) {
|
||||||
try {
|
try {
|
||||||
@@ -158,7 +159,7 @@ export async function waitForPageLoad(options: WaitForPageLoadOptions): Promise<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, pollInterval))
|
await sleep(pollInterval)
|
||||||
}
|
}
|
||||||
|
|
||||||
timedOut = true
|
timedOut = true
|
||||||
|
|||||||
Reference in New Issue
Block a user