fix: address multi-browser routing issues

- Move recording cleanup before connection delete (was no-op)
- Only fallback to default extension when extensionId is null
- Add cwd param to /cli/session/new, CLI passes process.cwd()
- Require pre-existing sessions for execute/reset endpoints
- Remove duplicate ensureRelayServer call in CLI
- Increase fetch timeouts from 500ms to 2000ms
This commit is contained in:
Tommy D. Rossi
2026-02-04 16:51:44 +01:00
parent 44514f00c8
commit 5641b3803b
2 changed files with 34 additions and 44 deletions
+29 -32
View File
@@ -107,9 +107,11 @@ export async function startPlayWriterCDPRelayServer({
}
const getExtensionConnection = (extensionId?: string | null): ExtensionConnection | null => {
if (extensionId && extensionConnections.has(extensionId)) {
// If specific extensionId requested, only return that one (no fallback)
if (extensionId) {
return extensionConnections.get(extensionId) || null
}
// Only fallback to default when no extensionId specified
const fallbackId = getDefaultExtensionId()
if (fallbackId) {
return extensionConnections.get(fallbackId) || null
@@ -1224,6 +1226,15 @@ export async function startPlayWriterCDPRelayServer({
logger?.log(`Extension disconnected: code=${event.code} reason=${event.reason || 'none'} (${connectionId})`)
stopExtensionPing(connectionId)
// Cancel any active recordings BEFORE removing connection (cancelRecording checks isExtensionConnected)
const recordingRelay = recordingRelays.get(connectionId)
if (recordingRelay) {
recordingRelay.cancelRecording({}).catch(() => {
// Ignore errors during cleanup
})
}
recordingRelays.delete(connectionId)
const connection = extensionConnections.get(connectionId)
if (connection) {
for (const pending of connection.pendingRequests.values()) {
@@ -1234,7 +1245,6 @@ export async function startPlayWriterCDPRelayServer({
}
extensionConnections.delete(connectionId)
recordingRelays.delete(connectionId)
for (const [clientId, client] of playwrightClients.entries()) {
if (client.extensionId !== connectionId) {
@@ -1275,28 +1285,19 @@ export async function startPlayWriterCDPRelayServer({
app.post('/cli/execute', async (c) => {
try {
const body = await c.req.json() as { sessionId: string; code: string; timeout?: number; cwd?: string }
const { sessionId, code, timeout = 10000, cwd } = body
const body = await c.req.json() as { sessionId: string; code: string; timeout?: number }
const { sessionId, code, timeout = 10000 } = body
if (!sessionId || !code) {
return c.json({ error: 'sessionId and code are required' }, 400)
}
const manager = await getExecutorManager()
const defaultExtension = getExtensionConnection(null)
const executor = manager.getExecutor({
sessionId,
cwd,
sessionMetadata: {
extensionId: defaultExtension?.id || null,
browser: defaultExtension?.info.browser || null,
profile: defaultExtension?.info ? { email: defaultExtension.info.email || '', id: defaultExtension.info.id || '' } : null,
},
})
const result = await executor.execute(code, timeout)
// Increment session counter after each execution to avoid conflicts
nextSessionNumber++
const existingExecutor = manager.getSession(sessionId)
if (!existingExecutor) {
return c.json({ text: `Session ${sessionId} not found. Run 'playwriter session new' first.`, images: [], isError: true }, 404)
}
const result = await existingExecutor.execute(code, timeout)
return c.json(result)
} catch (error: any) {
@@ -1307,25 +1308,19 @@ export async function startPlayWriterCDPRelayServer({
app.post('/cli/reset', async (c) => {
try {
const body = await c.req.json() as { sessionId: string; cwd?: string }
const { sessionId, cwd } = body
const body = await c.req.json() as { sessionId: string }
const { sessionId } = body
if (!sessionId) {
return c.json({ error: 'sessionId is required' }, 400)
}
const manager = await getExecutorManager()
const defaultExtension = getExtensionConnection(null)
const executor = manager.getExecutor({
sessionId,
cwd,
sessionMetadata: {
extensionId: defaultExtension?.id || null,
browser: defaultExtension?.info.browser || null,
profile: defaultExtension?.info ? { email: defaultExtension.info.email || '', id: defaultExtension.info.id || '' } : null,
},
})
const { page, context } = await executor.reset()
const existingExecutor = manager.getSession(sessionId)
if (!existingExecutor) {
return c.json({ error: `Session ${sessionId} not found. Run 'playwriter session new' first.` }, 404)
}
const { page, context } = await existingExecutor.reset()
return c.json({
success: true,
@@ -1348,9 +1343,10 @@ export async function startPlayWriterCDPRelayServer({
})
app.post('/cli/session/new', async (c) => {
const body = await c.req.json().catch(() => ({})) as { extensionId?: string | null }
const body = await c.req.json().catch(() => ({})) as { extensionId?: string | null; cwd?: string }
const sessionId = String(nextSessionNumber++)
const extensionId = body.extensionId || null
const cwd = body.cwd
const extension = getExtensionConnection(extensionId)
if (!extension) {
return c.json({ error: 'Extension not connected' }, 404)
@@ -1358,6 +1354,7 @@ export async function startPlayWriterCDPRelayServer({
const manager = await getExecutorManager()
const executor = manager.getExecutor({
sessionId,
cwd,
sessionMetadata: {
extensionId: extension.id,
browser: extension.info.browser || null,
+5 -12
View File
@@ -55,11 +55,11 @@ async function fetchExtensionsStatus(host?: string): Promise<Array<{
try {
const serverUrl = await getServerUrl(host)
const response = await fetch(`${serverUrl}/extensions/status`, {
signal: AbortSignal.timeout(500),
signal: AbortSignal.timeout(2000),
})
if (!response.ok) {
const fallback = await fetch(`${serverUrl}/extension/status`, {
signal: AbortSignal.timeout(500),
signal: AbortSignal.timeout(2000),
})
if (!fallback.ok) {
return []
@@ -231,21 +231,14 @@ cli
process.exit(1)
}
if (!options.host && !process.env.PLAYWRITER_HOST) {
await ensureRelayServer({ logger: console, env: cliRelayEnv })
const connected = await waitForExtension({ logger: console, timeoutMs: 10000 })
if (!connected) {
console.error('Warning: Extension not connected. Commands may fail.')
}
}
try {
const serverUrl = await getServerUrl(options.host)
const extensionId = selectedExtension.extensionId === 'default' ? null : selectedExtension.extensionId
const cwd = process.cwd()
const response = await fetch(`${serverUrl}/cli/session/new`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ extensionId }),
body: JSON.stringify({ extensionId, cwd }),
})
if (!response.ok) {
const text = await response.text()
@@ -279,7 +272,7 @@ cli
try {
const response = await fetch(`${serverUrl}/cli/sessions`, {
signal: AbortSignal.timeout(500),
signal: AbortSignal.timeout(2000),
})
if (!response.ok) {
console.error(`Error: ${response.status} ${await response.text()}`)