run relay server in-process during tests for WS message interception

This commit is contained in:
Tommy D. Rossi
2025-12-18 16:11:10 +01:00
parent daf01b8b69
commit b7ff5c6d75
3 changed files with 47 additions and 50 deletions
+26
View File
@@ -0,0 +1,26 @@
import fs from 'node:fs'
import util from 'node:util'
import { ensureDataDir, getLogFilePath } from './utils.js'
export type Logger = {
log(...args: unknown[]): void
error(...args: unknown[]): void
}
export function createFileLogger({ logFilePath }: { logFilePath?: string } = {}): Logger {
const resolvedLogFilePath = logFilePath || getLogFilePath()
ensureDataDir()
fs.writeFileSync(resolvedLogFilePath, '')
const log = (...args: unknown[]) => {
const message = args.map(arg =>
typeof arg === 'string' ? arg : util.inspect(arg, { depth: null, colors: false })
).join(' ')
fs.appendFileSync(resolvedLogFilePath, message + '\n')
}
return {
log,
error: log
}
}
+18 -33
View File
@@ -1,6 +1,6 @@
import { createMCPClient } from './mcp-client.js' import { createMCPClient } from './mcp-client.js'
import { describe, it, expect, beforeAll, afterAll } from 'vitest' import { describe, it, expect, beforeAll, afterAll } from 'vitest'
import { exec, spawn } from 'node:child_process' import { exec } from 'node:child_process'
import { promisify } from 'node:util' import { promisify } from 'node:util'
import { chromium, BrowserContext } from 'playwright-core' import { chromium, BrowserContext } from 'playwright-core'
import path from 'node:path' import path from 'node:path'
@@ -11,6 +11,8 @@ import type { ExtensionState } from 'mcp-extension/src/types.js'
import type { Protocol } from 'devtools-protocol' import type { Protocol } from 'devtools-protocol'
import { imageSize } from 'image-size' import { imageSize } from 'image-size'
import { getCDPSessionForPage } from './cdp-session.js' import { getCDPSessionForPage } from './cdp-session.js'
import { startPlayWriterCDPRelayServer } from './extension/cdp-relay.js'
import { createFileLogger } from './create-logger.js'
declare const window: any declare const window: any
declare const document: any declare const document: any
@@ -63,7 +65,7 @@ async function killProcessOnPort(port: number): Promise<void> {
interface TestContext { interface TestContext {
browserContext: Awaited<ReturnType<typeof chromium.launchPersistentContext>> browserContext: Awaited<ReturnType<typeof chromium.launchPersistentContext>>
userDataDir: string userDataDir: string
relayServerProcess: ReturnType<typeof spawn> relayServer: { close(): void }
} }
async function setupTestContext({ tempDirPrefix }: { tempDirPrefix: string }): Promise<TestContext> { async function setupTestContext({ tempDirPrefix }: { tempDirPrefix: string }): Promise<TestContext> {
@@ -74,31 +76,8 @@ async function setupTestContext({ tempDirPrefix }: { tempDirPrefix: string }): P
console.log('Extension built') console.log('Extension built')
const localLogPath = path.join(process.cwd(), 'relay-server.log') const localLogPath = path.join(process.cwd(), 'relay-server.log')
const relayServerProcess = spawn('pnpm', ['tsx', 'src/start-relay-server.ts'], { const logger = createFileLogger({ logFilePath: localLogPath })
cwd: process.cwd(), const relayServer = await startPlayWriterCDPRelayServer({ port: 19988, logger })
stdio: 'inherit',
env: { ...process.env, PLAYWRITER_LOG_PATH: localLogPath }
})
await new Promise<void>((resolve, reject) => {
let retries = 0
const interval = setInterval(async () => {
try {
const { stdout } = await execAsync('lsof -ti:19988')
if (stdout.trim()) {
clearInterval(interval)
resolve()
}
} catch {
// ignore
}
retries++
if (retries > 30) {
clearInterval(interval)
reject(new Error('Relay server failed to start'))
}
}, 1000)
})
const userDataDir = fs.mkdtempSync(path.join(os.tmpdir(), tempDirPrefix)) const userDataDir = fs.mkdtempSync(path.join(os.tmpdir(), tempDirPrefix))
const extensionPath = path.resolve('../extension/dist') const extensionPath = path.resolve('../extension/dist')
@@ -121,17 +100,16 @@ async function setupTestContext({ tempDirPrefix }: { tempDirPrefix: string }): P
await globalThis.toggleExtensionForActiveTab() await globalThis.toggleExtensionForActiveTab()
}) })
return { browserContext, userDataDir, relayServerProcess } return { browserContext, userDataDir, relayServer }
} }
async function cleanupTestContext(ctx: TestContext | null, cleanup?: (() => Promise<void>) | null): Promise<void> { async function cleanupTestContext(ctx: TestContext | null, cleanup?: (() => Promise<void>) | null): Promise<void> {
if (ctx?.browserContext) { if (ctx?.browserContext) {
await ctx.browserContext.close() await ctx.browserContext.close()
} }
if (ctx?.relayServerProcess) { if (ctx?.relayServer) {
ctx.relayServerProcess.kill() ctx.relayServer.close()
} }
await killProcessOnPort(19988)
if (ctx?.userDataDir) { if (ctx?.userDataDir) {
try { try {
@@ -1796,13 +1774,20 @@ describe('CDP Session Tests', () => {
sampleFunctionNames: functionNames, sampleFunctionNames: functionNames,
}).toMatchInlineSnapshot(` }).toMatchInlineSnapshot(`
{ {
"durationMicroseconds": 6205, "durationMicroseconds": 12993,
"hasNodes": true, "hasNodes": true,
"nodeCount": 3, "nodeCount": 21,
"sampleFunctionNames": [ "sampleFunctionNames": [
"(root)", "(root)",
"(program)", "(program)",
"(idle)", "(idle)",
"evaluate",
"fibonacci",
"fibonacci",
"fibonacci",
"fibonacci",
"fibonacci",
"fibonacci",
], ],
} }
`) `)
+3 -17
View File
@@ -1,25 +1,11 @@
import { startPlayWriterCDPRelayServer } from './extension/cdp-relay.js' import { startPlayWriterCDPRelayServer } from './extension/cdp-relay.js'
import fs from 'node:fs' import { createFileLogger } from './create-logger.js'
import util from 'node:util' import { getLogFilePath } from './utils.js'
import { ensureDataDir, getLogFilePath } from './utils.js'
const logFilePath = getLogFilePath() const logFilePath = getLogFilePath()
process.title = 'playwriter-ws-server' process.title = 'playwriter-ws-server'
ensureDataDir()
fs.writeFileSync(logFilePath, '')
const log = (...args: any[]) => { const logger = createFileLogger({ logFilePath })
const message = args.map(arg =>
typeof arg === 'string' ? arg : util.inspect(arg, { depth: null, colors: false })
).join(' ')
return fs.promises.appendFile(logFilePath, message + '\n')
}
const logger = {
log,
error: log
}
process.on('uncaughtException', async (err) => { process.on('uncaughtException', async (err) => {
await logger.error('Uncaught Exception:', err); await logger.error('Uncaught Exception:', err);