restart ws process if already running with different version
This commit is contained in:
@@ -45,6 +45,7 @@
|
||||
"devtools-protocol": "^0.0.1543509",
|
||||
"diff": "^8.0.2",
|
||||
"hono": "^4.10.6",
|
||||
"kill-port-process": "^3.2.1",
|
||||
"playwright-core": "^1.56.1",
|
||||
"string-dedent": "^3.0.2",
|
||||
"user-agents": "^1.1.669",
|
||||
|
||||
@@ -9,33 +9,12 @@ export type Logger = {
|
||||
logFilePath: string
|
||||
}
|
||||
|
||||
function cleanupOldLogs(logsDir: string): void {
|
||||
if (!fs.existsSync(logsDir)) {
|
||||
return
|
||||
}
|
||||
const files = fs.readdirSync(logsDir)
|
||||
.filter((f) => f.startsWith('relay-server-') && f.endsWith('.log'))
|
||||
.map((f) => ({
|
||||
name: f,
|
||||
path: path.join(logsDir, f),
|
||||
mtime: fs.statSync(path.join(logsDir, f)).mtime.getTime(),
|
||||
}))
|
||||
.sort((a, b) => b.mtime - a.mtime)
|
||||
|
||||
if (files.length > 10) {
|
||||
files.slice(10).forEach((f) => {
|
||||
fs.unlinkSync(f.path)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export function createFileLogger({ logFilePath }: { logFilePath?: string } = {}): Logger {
|
||||
const resolvedLogFilePath = logFilePath || LOG_FILE_PATH
|
||||
const logDir = path.dirname(resolvedLogFilePath)
|
||||
if (!fs.existsSync(logDir)) {
|
||||
fs.mkdirSync(logDir, { recursive: true })
|
||||
}
|
||||
cleanupOldLogs(logDir)
|
||||
fs.writeFileSync(resolvedLogFilePath, '')
|
||||
|
||||
let queue: Promise<void> = Promise.resolve()
|
||||
|
||||
@@ -7,6 +7,7 @@ import type { CDPCommand, CDPResponseBase, CDPEventBase, CDPEventFor, RelayServe
|
||||
import type { ExtensionMessage, ExtensionEventMessage } from './protocol.js'
|
||||
import chalk from 'chalk'
|
||||
import { EventEmitter } from 'node:events'
|
||||
import { VERSION } from '../utils.js'
|
||||
|
||||
type ConnectedTarget = {
|
||||
sessionId: string
|
||||
@@ -276,6 +277,10 @@ export async function startPlayWriterCDPRelayServer({ port = 19988, host = '127.
|
||||
return c.text('OK')
|
||||
})
|
||||
|
||||
app.get('/version', (c) => {
|
||||
return c.json({ version: VERSION })
|
||||
})
|
||||
|
||||
app.post('/mcp-log', async (c) => {
|
||||
try {
|
||||
const { level, args } = await c.req.json()
|
||||
|
||||
+14
-12
@@ -14,6 +14,7 @@ import { getCDPSessionForPage } from './cdp-session.js'
|
||||
import { startPlayWriterCDPRelayServer, type RelayServer } from './extension/cdp-relay.js'
|
||||
import { createFileLogger } from './create-logger.js'
|
||||
import type { CDPCommand } from './cdp-types.js'
|
||||
import { killPortProcess } from 'kill-port-process'
|
||||
|
||||
declare const window: any
|
||||
declare const document: any
|
||||
@@ -51,15 +52,10 @@ function js(strings: TemplateStringsArray, ...values: any[]): string {
|
||||
|
||||
async function killProcessOnPort(port: number): Promise<void> {
|
||||
try {
|
||||
const { stdout } = await execAsync(`lsof -ti:${port}`)
|
||||
const pids = stdout.trim().split('\n').filter(Boolean)
|
||||
if (pids.length > 0) {
|
||||
await execAsync(`kill -9 ${pids.join(' ')}`)
|
||||
console.log(`Killed processes ${pids.join(', ')} on port ${port}`)
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000))
|
||||
}
|
||||
} catch (error) {
|
||||
// No process running on port or already killed
|
||||
await killPortProcess(port)
|
||||
console.log(`Killed processes on port ${port}`)
|
||||
} catch (err) {
|
||||
console.error('Error killing process on port:', err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1635,7 +1631,7 @@ describe('MCP Server Tests', () => {
|
||||
|
||||
const wsUrl = getCdpUrl()
|
||||
const client = await getCDPSessionForPage({ page: cdpPage!, wsUrl })
|
||||
|
||||
|
||||
const layoutMetrics = await client.send('Page.getLayoutMetrics')
|
||||
expect(layoutMetrics.cssVisualViewport).toBeDefined()
|
||||
expect(layoutMetrics.cssVisualViewport.clientWidth).toBeGreaterThan(0)
|
||||
@@ -1968,14 +1964,20 @@ describe('CDP Session Tests', () => {
|
||||
sampleFunctionNames: functionNames,
|
||||
}).toMatchInlineSnapshot(`
|
||||
{
|
||||
"durationMicroseconds": 15772,
|
||||
"durationMicroseconds": 7398,
|
||||
"hasNodes": true,
|
||||
"nodeCount": 7,
|
||||
"nodeCount": 21,
|
||||
"sampleFunctionNames": [
|
||||
"(root)",
|
||||
"(program)",
|
||||
"(idle)",
|
||||
"evaluate",
|
||||
"fibonacci",
|
||||
"fibonacci",
|
||||
"fibonacci",
|
||||
"fibonacci",
|
||||
"fibonacci",
|
||||
"fibonacci",
|
||||
],
|
||||
}
|
||||
`)
|
||||
|
||||
+32
-17
@@ -11,7 +11,8 @@ import { fileURLToPath } from 'node:url'
|
||||
import vm from 'node:vm'
|
||||
import dedent from 'string-dedent'
|
||||
import { createPatch } from 'diff'
|
||||
import { getCdpUrl, LOG_FILE_PATH } from './utils.js'
|
||||
import { getCdpUrl, LOG_FILE_PATH, VERSION } 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'
|
||||
|
||||
@@ -152,7 +153,7 @@ function clearConnectionState() {
|
||||
|
||||
async function sendLogToRelayServer(level: string, ...args: any[]) {
|
||||
try {
|
||||
await fetch(`http://localhost:${RELAY_PORT}/mcp-log`, {
|
||||
await fetch(`http://127.0.0.1:${RELAY_PORT}/mcp-log`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ level, args }),
|
||||
@@ -163,25 +164,41 @@ async function sendLogToRelayServer(level: string, ...args: any[]) {
|
||||
}
|
||||
}
|
||||
|
||||
async function isPortTaken(port: number): Promise<boolean> {
|
||||
async function getServerVersion(port: number): Promise<string | null> {
|
||||
try {
|
||||
const response = await fetch(`http://localhost:${port}/`, {
|
||||
signal: AbortSignal.timeout(100),
|
||||
const response = await fetch(`http://127.0.0.1:${port}/version`, {
|
||||
signal: AbortSignal.timeout(500),
|
||||
})
|
||||
return response.ok
|
||||
} catch {
|
||||
return false
|
||||
if (!response.ok) {
|
||||
return null
|
||||
}
|
||||
const data = (await response.json()) as { version: string }
|
||||
return data.version
|
||||
} catch (error) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
async function ensureRelayServer(): Promise<void> {
|
||||
const portTaken = await isPortTaken(RELAY_PORT)
|
||||
async function killRelayServer(port: number): Promise<void> {
|
||||
try {
|
||||
await killPortProcess(port)
|
||||
await new Promise((resolve) => setTimeout(resolve, 500))
|
||||
} catch {}
|
||||
}
|
||||
|
||||
if (portTaken) {
|
||||
async function ensureRelayServer(): Promise<void> {
|
||||
const serverVersion = await getServerVersion(RELAY_PORT)
|
||||
|
||||
if (serverVersion === VERSION) {
|
||||
return
|
||||
}
|
||||
|
||||
console.error('CDP relay server not running, starting it...')
|
||||
if (serverVersion !== null) {
|
||||
console.error(`CDP relay server version mismatch (server: ${serverVersion}, mcp: ${VERSION}), restarting...`)
|
||||
await killRelayServer(RELAY_PORT)
|
||||
} else {
|
||||
console.error('CDP relay server not running, starting it...')
|
||||
}
|
||||
|
||||
const scriptPath = require.resolve('../dist/start-relay-server.js')
|
||||
|
||||
@@ -194,8 +211,8 @@ async function ensureRelayServer(): Promise<void> {
|
||||
|
||||
for (let i = 0; i < 10; i++) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 500))
|
||||
const isRunning = await isPortTaken(RELAY_PORT)
|
||||
if (isRunning) {
|
||||
const newVersion = await getServerVersion(RELAY_PORT)
|
||||
if (newVersion === VERSION) {
|
||||
console.error('CDP relay server started successfully')
|
||||
return
|
||||
}
|
||||
@@ -622,9 +639,7 @@ server.tool(
|
||||
timeout,
|
||||
displayErrors: true,
|
||||
}),
|
||||
new Promise((_, reject) =>
|
||||
setTimeout(() => reject(new CodeExecutionTimeoutError(timeout)), timeout),
|
||||
),
|
||||
new Promise((_, reject) => setTimeout(() => reject(new CodeExecutionTimeoutError(timeout)), timeout)),
|
||||
])
|
||||
|
||||
let responseText = formatConsoleLogs(consoleLogs)
|
||||
|
||||
@@ -69,76 +69,76 @@ Return value:
|
||||
- generic [ref=e50]: Theme
|
||||
- combobox "Theme" [ref=e51]:
|
||||
- generic [ref=e52]: "Theme:"
|
||||
- generic: Neutral
|
||||
- img
|
||||
- button "Copy Code" [ref=e53]:
|
||||
- combobox [ref=e53]
|
||||
- button "Copy Code" [ref=e54]:
|
||||
- img
|
||||
- generic [ref=e54]: Copy Code
|
||||
- generic [ref=e58]:
|
||||
- generic [ref=e62]:
|
||||
- group "Payment Method" [ref=e63]:
|
||||
- generic [ref=e64]: Payment Method
|
||||
- paragraph [ref=e65]: All transactions are secure and encrypted
|
||||
- generic [ref=e66]:
|
||||
- group [ref=e67]:
|
||||
- generic [ref=e68]: Name on Card
|
||||
- textbox "Name on Card" [ref=e69]:
|
||||
- generic [ref=e55]: Copy Code
|
||||
- generic [ref=e59]:
|
||||
- generic [ref=e63]:
|
||||
- group "Payment Method" [ref=e64]:
|
||||
- generic [ref=e65]: Payment Method
|
||||
- paragraph [ref=e66]: All transactions are secure and encrypted
|
||||
- generic [ref=e67]:
|
||||
- group [ref=e68]:
|
||||
- generic [ref=e69]: Name on Card
|
||||
- textbox "Name on Card" [ref=e70]:
|
||||
- /placeholder: John Doe
|
||||
- generic [ref=e70]:
|
||||
- group [ref=e71]:
|
||||
- generic [ref=e72]: Card Number
|
||||
- textbox "Card Number" [ref=e73]:
|
||||
- generic [ref=e71]:
|
||||
- group [ref=e72]:
|
||||
- generic [ref=e73]: Card Number
|
||||
- textbox "Card Number" [ref=e74]:
|
||||
- /placeholder: 1234 5678 9012 3456
|
||||
- paragraph [ref=e74]: Enter your 16-digit number.
|
||||
- group [ref=e75]:
|
||||
- generic [ref=e76]: CVV
|
||||
- textbox "CVV" [ref=e77]:
|
||||
- paragraph [ref=e75]: Enter your 16-digit number.
|
||||
- group [ref=e76]:
|
||||
- generic [ref=e77]: CVV
|
||||
- textbox "CVV" [ref=e78]:
|
||||
- /placeholder: "123"
|
||||
- generic [ref=e78]:
|
||||
- group [ref=e79]:
|
||||
- generic [ref=e80]: Month
|
||||
- combobox "Month" [ref=e81]:
|
||||
- generic [ref=e79]:
|
||||
- group [ref=e80]:
|
||||
- generic [ref=e81]: Month
|
||||
- combobox "Month" [ref=e82]:
|
||||
- generic: MM
|
||||
- img
|
||||
- combobox [ref=e82]
|
||||
- group [ref=e83]:
|
||||
- generic [ref=e84]: Year
|
||||
- combobox "Year" [ref=e85]:
|
||||
- combobox [ref=e83]
|
||||
- group [ref=e84]:
|
||||
- generic [ref=e85]: Year
|
||||
- combobox "Year" [ref=e86]:
|
||||
- generic: YYYY
|
||||
- img
|
||||
- combobox [ref=e86]
|
||||
- group "Billing Address" [ref=e88]:
|
||||
- generic [ref=e89]: Billing Address
|
||||
- paragraph [ref=e90]: The billing address associated with your payment method
|
||||
- group [ref=e92]:
|
||||
- checkbox "Same as shipping address" [checked] [ref=e93]:
|
||||
- combobox [ref=e87]
|
||||
- group "Billing Address" [ref=e89]:
|
||||
- generic [ref=e90]: Billing Address
|
||||
- paragraph [ref=e91]: The billing address associated with your payment method
|
||||
- group [ref=e93]:
|
||||
- checkbox "Same as shipping address" [checked] [ref=e94]:
|
||||
- generic:
|
||||
- img
|
||||
- checkbox [checked]
|
||||
- generic [ref=e94]: Same as shipping address
|
||||
- group [ref=e96]:
|
||||
- group [ref=e98]:
|
||||
- generic [ref=e99]: Comments
|
||||
- textbox "Comments" [ref=e100]:
|
||||
- generic [ref=e95]: Same as shipping address
|
||||
- group [ref=e97]:
|
||||
- group [ref=e99]:
|
||||
- generic [ref=e100]: Comments
|
||||
- textbox "Comments" [ref=e101]:
|
||||
- /placeholder: Add any additional comments
|
||||
- group [ref=e101]:
|
||||
- button "Submit" [ref=e102]
|
||||
- button "Cancel" [ref=e103]
|
||||
- generic [ref=e104]:
|
||||
- generic [ref=e105]:
|
||||
- generic [ref=e106]:
|
||||
- generic [ref=e108]:
|
||||
- img "@shadcn" [ref=e110]
|
||||
- img "@maxleiter" [ref=e112]
|
||||
- img "@evilrabbit" [ref=e114]
|
||||
- generic [ref=e115]: No Team Members
|
||||
- generic [ref=e116]: Invite your team to collaborate on this project.
|
||||
- button "Invite Members" [ref=e118]:
|
||||
- group [ref=e102]:
|
||||
- button "Submit" [ref=e103]
|
||||
- button "Cancel" [ref=e104]
|
||||
- generic [ref=e105]:
|
||||
- generic [ref=e106]:
|
||||
- generic [ref=e107]:
|
||||
- generic [ref=e109]:
|
||||
- generic [ref=e111]: CN
|
||||
- generic [ref=e113]: LR
|
||||
- generic [ref=e115]: ER
|
||||
- generic [ref=e116]: No Team Members
|
||||
- generic [ref=e117]: Invite your team to collaborate on this project.
|
||||
- button "Invite Members" [ref=e119]:
|
||||
- img
|
||||
- text: Invite Members
|
||||
- generic [ref=e119]:
|
||||
- generic [ref=e120]:
|
||||
- generic [ref=e120]:
|
||||
- generic [ref=e121]:
|
||||
- status "Loading"
|
||||
|
||||
- tex
|
||||
|
||||
[Truncated to 6000 characters. Better manage your logs or paginate them to read the full logs]
|
||||
+4
-12
@@ -1,22 +1,14 @@
|
||||
import fs from 'node:fs'
|
||||
import os from 'node:os'
|
||||
import path from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
export function getCdpUrl({ port = 19988, host = '127.0.0.1' }: { port?: number; host?: string } = {}) {
|
||||
const id = `${Math.random().toString(36).substring(2, 15)}_${Date.now()}`
|
||||
return `ws://${host}:${port}/cdp/${id}`
|
||||
}
|
||||
|
||||
function getLogFilePath(): string {
|
||||
if (process.env.PLAYWRITER_LOG_PATH) {
|
||||
return process.env.PLAYWRITER_LOG_PATH
|
||||
}
|
||||
const logsDir = path.join(os.tmpdir(), 'playwriter')
|
||||
if (!fs.existsSync(logsDir)) {
|
||||
fs.mkdirSync(logsDir, { recursive: true })
|
||||
}
|
||||
const timestamp = new Date().toISOString().replace(/[:.]/g, '-')
|
||||
return path.join(logsDir, `relay-server-${timestamp}.log`)
|
||||
}
|
||||
export const LOG_FILE_PATH = path.join(os.tmpdir(), 'playwriter', 'relay-server.log')
|
||||
|
||||
export const LOG_FILE_PATH = getLogFilePath()
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user