From 78cf4e527f41e0517040eca98481c3ecb8462030 Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Wed, 11 Mar 2026 08:59:18 -0700 Subject: [PATCH] chore: do not bind remote browsers twice --- packages/playwright-core/src/cli/daemon/program.ts | 6 ++---- packages/playwright-core/src/client/browser.ts | 4 ++-- packages/playwright-core/src/client/types.ts | 8 ++++++++ packages/playwright-core/src/protocol/validator.ts | 3 +++ packages/playwright-core/src/server/browser.ts | 7 ++++--- packages/playwright-core/src/serverRegistry.ts | 1 + packages/protocol/src/channels.d.ts | 6 ++++++ packages/protocol/src/protocol.yml | 3 +++ 8 files changed, 29 insertions(+), 9 deletions(-) diff --git a/packages/playwright-core/src/cli/daemon/program.ts b/packages/playwright-core/src/cli/daemon/program.ts index 5ce7ceefa1151..eb814b2d9f03a 100644 --- a/packages/playwright-core/src/cli/daemon/program.ts +++ b/packages/playwright-core/src/cli/daemon/program.ts @@ -53,12 +53,10 @@ export function decorateCLICommand(command: Command, version: string) { const socketPath = await startCliDaemonServer(sessionName, browserContext, mcpConfig, clientInfo, { persistent, exitOnClose: true }); console.log(`### Success\nDaemon listening on ${socketPath}`); console.log(''); - try { + + if (!(browser as any)._connection.isRemote()) { await (browser as any)._startServer(sessionName, { workspaceDir: clientInfo.workspaceDir }); browserContext.on('close', () => (browser as any)._stopServer().catch(() => {})); - } catch (error) { - if (!error.message.includes('Server is already running')) - throw error; } } catch (error) { const message = process.env.PWDEBUGIMPL ? (error as Error).stack || (error as Error).message : (error as Error).message; diff --git a/packages/playwright-core/src/client/browser.ts b/packages/playwright-core/src/client/browser.ts index 0ab453827a237..df4f9aff42ba0 100644 --- a/packages/playwright-core/src/client/browser.ts +++ b/packages/playwright-core/src/client/browser.ts @@ -24,7 +24,7 @@ import { mkdirIfNeeded } from './fileUtils'; import type { BrowserType } from './browserType'; import type { Page } from './page'; -import type { BrowserContextOptions, LaunchOptions, Logger } from './types'; +import type { BrowserContextOptions, LaunchOptions, Logger, StartServerOptions } from './types'; import type * as api from '../../types/types'; import type * as channels from '@protocol/channels'; @@ -130,7 +130,7 @@ export class Browser extends ChannelOwner implements ap return this._initializer.version; } - async _startServer(title: string, options: { wsPath?: string, workspaceDir?: string } = {}): Promise<{ wsEndpoint?: string, pipeName?: string }> { + async _startServer(title: string, options: StartServerOptions = {}): Promise<{ wsEndpoint?: string, pipeName?: string }> { return await this._channel.startServer({ title, ...options }); } diff --git a/packages/playwright-core/src/client/types.ts b/packages/playwright-core/src/client/types.ts index 7191942df6695..b5a526ab94441 100644 --- a/packages/playwright-core/src/client/types.ts +++ b/packages/playwright-core/src/client/types.ts @@ -120,6 +120,14 @@ export type LaunchAndroidServerOptions = { wsPath?: string, }; +export type StartServerOptions = { + host?: string, + port?: number, + wsPath?: string, + workspaceDir?: string, + metadata?: Record, +}; + export type SelectorEngine = { /** * Returns the first element matching given selector in the root's subtree. diff --git a/packages/playwright-core/src/protocol/validator.ts b/packages/playwright-core/src/protocol/validator.ts index bd7e6e8d095bd..97052f7479820 100644 --- a/packages/playwright-core/src/protocol/validator.ts +++ b/packages/playwright-core/src/protocol/validator.ts @@ -652,8 +652,11 @@ scheme.BrowserContextEvent = tObject({ scheme.BrowserCloseEvent = tOptional(tObject({})); scheme.BrowserStartServerParams = tObject({ title: tString, + host: tOptional(tString), + port: tOptional(tInt), wsPath: tOptional(tString), workspaceDir: tOptional(tString), + metadata: tOptional(tAny), }); scheme.BrowserStartServerResult = tObject({ wsEndpoint: tOptional(tString), diff --git a/packages/playwright-core/src/server/browser.ts b/packages/playwright-core/src/server/browser.ts index c065e68f4dc12..ebc4ea14aa8a4 100644 --- a/packages/playwright-core/src/server/browser.ts +++ b/packages/playwright-core/src/server/browser.ts @@ -174,7 +174,7 @@ export abstract class Browser extends SdkObject { return video?.artifact; } - async startServer(title: string, options: { workspaceDir?: string, wsPath?: string, pipeName?: string }): Promise<{ wsEndpoint?: string, pipeName?: string }> { + async startServer(title: string, options: channels.BrowserStartServerOptions): Promise<{ wsEndpoint?: string, pipeName?: string }> { return await this._server.start(title, options); } @@ -219,7 +219,7 @@ export class BrowserServer { this._browser = browser; } - async start(title: string, options: { workspaceDir?: string, wsPath?: string }): Promise<{ wsEndpoint?: string, pipeName?: string }> { + async start(title: string, options: channels.BrowserStartServerOptions): Promise<{ wsEndpoint?: string, pipeName?: string }> { if (this._isStarted) throw new Error(`Server is already started.`); this._isStarted = true; @@ -233,7 +233,7 @@ export class BrowserServer { if (options.wsPath) { const path = options.wsPath.startsWith('/') ? options.wsPath : `/${options.wsPath}`; this._wsServer = new PlaywrightWebSocketServer(this._browser, path); - result.wsEndpoint = await this._wsServer.listen(0, 'localhost', path); + result.wsEndpoint = await this._wsServer.listen(options.port ?? 0, options.host ?? 'localhost', path); } const browserInfo: BrowserInfo = { @@ -247,6 +247,7 @@ export class BrowserServer { wsEndpoint: result.wsEndpoint, pipeName: result.pipeName, workspaceDir: options.workspaceDir, + metadata: options.metadata, }); return result; } diff --git a/packages/playwright-core/src/serverRegistry.ts b/packages/playwright-core/src/serverRegistry.ts index 4aa14109697b4..1d01068390b18 100644 --- a/packages/playwright-core/src/serverRegistry.ts +++ b/packages/playwright-core/src/serverRegistry.ts @@ -36,6 +36,7 @@ export type EndpointInfo = { wsEndpoint?: string; pipeName?: string; workspaceDir?: string; + metadata?: Record; }; export type BrowserDescriptor = EndpointInfo & { diff --git a/packages/protocol/src/channels.d.ts b/packages/protocol/src/channels.d.ts index 13207e08eb551..5344df2987286 100644 --- a/packages/protocol/src/channels.d.ts +++ b/packages/protocol/src/channels.d.ts @@ -1181,12 +1181,18 @@ export type BrowserContextEvent = { export type BrowserCloseEvent = {}; export type BrowserStartServerParams = { title: string, + host?: string, + port?: number, wsPath?: string, workspaceDir?: string, + metadata?: any, }; export type BrowserStartServerOptions = { + host?: string, + port?: number, wsPath?: string, workspaceDir?: string, + metadata?: any, }; export type BrowserStartServerResult = { wsEndpoint?: string, diff --git a/packages/protocol/src/protocol.yml b/packages/protocol/src/protocol.yml index b8d0e30d73728..bbe13e5c33562 100644 --- a/packages/protocol/src/protocol.yml +++ b/packages/protocol/src/protocol.yml @@ -1043,8 +1043,11 @@ Browser: title: Start server parameters: title: string + host: string? + port: int? wsPath: string? workspaceDir: string? + metadata: json? returns: wsEndpoint: string? pipeName: string?