diff --git a/docs/src/api/class-browser.md b/docs/src/api/class-browser.md index 7867ce5c8ba71..3b2404d630bf1 100644 --- a/docs/src/api/class-browser.md +++ b/docs/src/api/class-browser.md @@ -158,6 +158,12 @@ System.Console.WriteLine(browser.Contexts.Count); // prints "1" Indicates that the browser is connected. +## method: Browser.isRemote +* since: v1.59 +- returns: <[boolean]> + +Indicates that the browser is remote, i.e. connected over a WebSocket. + ## async method: Browser.newBrowserCDPSession * since: v1.11 - returns: <[CDPSession]> diff --git a/packages/playwright-client/types/types.d.ts b/packages/playwright-client/types/types.d.ts index c7b0375a83056..5b5ae09c6b062 100644 --- a/packages/playwright-client/types/types.d.ts +++ b/packages/playwright-client/types/types.d.ts @@ -10088,6 +10088,11 @@ export interface Browser { */ isConnected(): boolean; + /** + * Indicates that the browser is remote, i.e. connected over a WebSocket. + */ + isRemote(): boolean; + /** * **NOTE** CDP Sessions are only supported on Chromium-based browsers. * diff --git a/packages/playwright-core/src/cli/daemon/program.ts b/packages/playwright-core/src/cli/daemon/program.ts index 5ce7ceefa1151..935ce9ebf405e 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.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..74c26dedfa44d 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'; @@ -54,6 +54,10 @@ export class Browser extends ChannelOwner implements ap this._closedPromise = new Promise(f => this.once(Events.Browser.Disconnected, f)); } + isRemote() { + return this._connection.isRemote(); + } + browserType(): BrowserType { return this._browserType; } @@ -130,7 +134,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 b841a21e8923b..9031e98bddb81 100644 --- a/packages/playwright-core/src/protocol/validator.ts +++ b/packages/playwright-core/src/protocol/validator.ts @@ -651,8 +651,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/playwright-core/types/types.d.ts b/packages/playwright-core/types/types.d.ts index c7b0375a83056..5b5ae09c6b062 100644 --- a/packages/playwright-core/types/types.d.ts +++ b/packages/playwright-core/types/types.d.ts @@ -10088,6 +10088,11 @@ export interface Browser { */ isConnected(): boolean; + /** + * Indicates that the browser is remote, i.e. connected over a WebSocket. + */ + isRemote(): boolean; + /** * **NOTE** CDP Sessions are only supported on Chromium-based browsers. * diff --git a/packages/protocol/src/channels.d.ts b/packages/protocol/src/channels.d.ts index 8222697e25f58..f190fd7d87ba0 100644 --- a/packages/protocol/src/channels.d.ts +++ b/packages/protocol/src/channels.d.ts @@ -1180,12 +1180,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 5f94b100ddbd9..e175243304430 100644 --- a/packages/protocol/src/protocol.yml +++ b/packages/protocol/src/protocol.yml @@ -1037,8 +1037,11 @@ Browser: title: Start server parameters: title: string + host: string? + port: int? wsPath: string? workspaceDir: string? + metadata: json? returns: wsEndpoint: string? pipeName: string?