diff --git a/packages/playwright-core/src/browserServerImpl.ts b/packages/playwright-core/src/browserServerImpl.ts index 494cecf2ba..8b6d4314ff 100644 --- a/packages/playwright-core/src/browserServerImpl.ts +++ b/packages/playwright-core/src/browserServerImpl.ts @@ -54,7 +54,7 @@ export class BrowserServerLauncherImpl implements BrowserServerLauncher { path = options.wsPath.startsWith('/') ? options.wsPath : `/${options.wsPath}`; // 2. Start the server - const server = new PlaywrightServer('use-pre-launched-browser', { path, maxConcurrentConnections: Infinity, maxIncomingConnections: Infinity, enableSocksProxy: false, preLaunchedBrowser: browser }); + const server = new PlaywrightServer({ path, maxConnections: Infinity, enableSocksProxy: false, preLaunchedBrowser: browser }); const wsEndpoint = await server.listen(options.port); // 3. Return the BrowserServer interface diff --git a/packages/playwright-core/src/cli/driver.ts b/packages/playwright-core/src/cli/driver.ts index f19c9ba2c1..5b2a7237a6 100644 --- a/packages/playwright-core/src/cli/driver.ts +++ b/packages/playwright-core/src/cli/driver.ts @@ -49,10 +49,8 @@ export function runDriver() { }; } -export async function runServer(port: number | undefined, path = '/', maxClients = Infinity, enableSocksProxy = true, reuseBrowser = false) { - const maxIncomingConnections = maxClients; - const maxConcurrentConnections = reuseBrowser ? 1 : maxClients; - const server = new PlaywrightServer(reuseBrowser ? 'reuse-browser' : 'auto', { path, maxIncomingConnections, maxConcurrentConnections, enableSocksProxy }); +export async function runServer(port: number | undefined, path = '/', maxConnections = Infinity, enableSocksProxy = true, reuseBrowser = false) { + const server = new PlaywrightServer({ path, maxConnections, enableSocksProxy }); const wsEndpoint = await server.listen(port); process.on('exit', () => server.close().catch(console.error)); console.log('Listening on ' + wsEndpoint); // eslint-disable-line no-console @@ -86,7 +84,6 @@ class ProtocolHandler { this._controller = playwright.debugController; this._controller.setAutoCloseAllowed(true); this._controller.setTrackHierarcy(true); - this._controller.setReuseBrowser(true); this._controller.on(DebugController.Events.BrowsersChanged, browsers => { process.send!({ method: 'browsersChanged', params: { browsers } }); }); diff --git a/packages/playwright-core/src/grid/gridBrowserWorker.ts b/packages/playwright-core/src/grid/gridBrowserWorker.ts index 4656f1a8dc..19d92d190c 100644 --- a/packages/playwright-core/src/grid/gridBrowserWorker.ts +++ b/packages/playwright-core/src/grid/gridBrowserWorker.ts @@ -23,7 +23,7 @@ function launchGridBrowserWorker(gridURL: string, agentId: string, workerId: str const log = debug(`pw:grid:worker:${workerId}`); log('created'); const ws = new WebSocket(gridURL.replace('http://', 'ws://') + `/registerWorker?agentId=${agentId}&workerId=${workerId}`); - new PlaywrightConnection(Promise.resolve(), 'auto', ws, false, { enableSocksProxy: true, browserName, launchOptions: {} }, { playwright: null, browser: null }, log, async () => { + new PlaywrightConnection(Promise.resolve(), 'launch-browser', ws, { enableSocksProxy: true, browserName, launchOptions: {} }, { playwright: null, browser: null }, log, async () => { log('exiting process'); setTimeout(() => process.exit(0), 30000); // Meanwhile, try to gracefully close all browsers. diff --git a/packages/playwright-core/src/protocol/validator.ts b/packages/playwright-core/src/protocol/validator.ts index c7efd8053f..c8654fbeb8 100644 --- a/packages/playwright-core/src/protocol/validator.ts +++ b/packages/playwright-core/src/protocol/validator.ts @@ -350,10 +350,6 @@ scheme.DebugControllerSetTrackHierarchyParams = tObject({ enabled: tBoolean, }); scheme.DebugControllerSetTrackHierarchyResult = tOptional(tObject({})); -scheme.DebugControllerSetReuseBrowserParams = tObject({ - enabled: tBoolean, -}); -scheme.DebugControllerSetReuseBrowserResult = tOptional(tObject({})); scheme.DebugControllerResetForReuseParams = tOptional(tObject({})); scheme.DebugControllerResetForReuseResult = tOptional(tObject({})); scheme.DebugControllerNavigateAllParams = tObject({ diff --git a/packages/playwright-core/src/remote/playwrightConnection.ts b/packages/playwright-core/src/remote/playwrightConnection.ts index 5b9cf2e200..1096ecabdc 100644 --- a/packages/playwright-core/src/remote/playwrightConnection.ts +++ b/packages/playwright-core/src/remote/playwrightConnection.ts @@ -21,11 +21,12 @@ import { Browser } from '../server/browser'; import { serverSideCallMetadata } from '../server/instrumentation'; import { gracefullyCloseAll } from '../utils/processLauncher'; import { SocksProxy } from '../common/socksProxy'; -import type { Mode } from './playwrightServer'; import { assert } from '../utils'; import type { LaunchOptions } from '../server/types'; import { DebugControllerDispatcher } from '../server/dispatchers/debugControllerDispatcher'; +export type ClientType = 'controller' | 'playwright' | 'launch-browser' | 'reuse-browser' | 'pre-launched-browser'; + type Options = { enableSocksProxy: boolean, browserName: string | null, @@ -48,13 +49,13 @@ export class PlaywrightConnection { private _options: Options; private _root: DispatcherScope; - constructor(lock: Promise, mode: Mode, ws: WebSocket, isDebugControllerClient: boolean, options: Options, preLaunched: PreLaunched, log: (m: string) => void, onClose: () => void) { + constructor(lock: Promise, clientType: ClientType, ws: WebSocket, options: Options, preLaunched: PreLaunched, log: (m: string) => void, onClose: () => void) { this._ws = ws; this._preLaunched = preLaunched; this._options = options; - if (mode === 'reuse-browser' || mode === 'use-pre-launched-browser') + if (clientType === 'reuse-browser' || clientType === 'pre-launched-browser') assert(preLaunched.playwright); - if (mode === 'use-pre-launched-browser') + if (clientType === 'pre-launched-browser') assert(preLaunched.browser); this._onClose = onClose; this._debugLog = log; @@ -73,19 +74,21 @@ export class PlaywrightConnection { ws.on('close', () => this._onDisconnect()); ws.on('error', error => this._onDisconnect(error)); - if (isDebugControllerClient) { + if (clientType === 'controller') { this._root = this._initDebugControllerMode(); return; } this._root = new RootDispatcher(this._dispatcherConnection, async scope => { - if (mode === 'reuse-browser') + if (clientType === 'reuse-browser') return await this._initReuseBrowsersMode(scope); - if (mode === 'use-pre-launched-browser') + if (clientType === 'pre-launched-browser') return await this._initPreLaunchedBrowserMode(scope); - if (!options.browserName) + if (clientType === 'launch-browser') + return await this._initLaunchBrowserMode(scope); + if (clientType === 'playwright') return await this._initPlaywrightConnectMode(scope); - return await this._initLaunchBrowserMode(scope); + throw new Error('Unsupported client type: ' + clientType); }); } diff --git a/packages/playwright-core/src/remote/playwrightServer.ts b/packages/playwright-core/src/remote/playwrightServer.ts index 2183b4bb1e..c0d34f061b 100644 --- a/packages/playwright-core/src/remote/playwrightServer.ts +++ b/packages/playwright-core/src/remote/playwrightServer.ts @@ -21,7 +21,7 @@ import type { Browser } from '../server/browser'; import type { Playwright } from '../server/playwright'; import { createPlaywright } from '../server/playwright'; import { PlaywrightConnection } from './playwrightConnection'; -import { assert } from '../utils'; +import type { ClientType } from './playwrightConnection'; import type { LaunchOptions } from '../server/types'; import { ManualPromise } from '../utils/manualPromise'; @@ -35,13 +35,9 @@ function newLogger() { return (message: string) => debugLog(`[id=${id}] ${message}`); } -// TODO: replace 'reuse-browser' with 'allow-reuse' in 1.27. -export type Mode = 'use-pre-launched-browser' | 'reuse-browser' | 'auto'; - type ServerOptions = { path: string; - maxIncomingConnections: number; - maxConcurrentConnections: number; + maxConnections: number; enableSocksProxy: boolean; preLaunchedBrowser?: Browser }; @@ -49,16 +45,12 @@ type ServerOptions = { export class PlaywrightServer { private _preLaunchedPlaywright: Playwright | null = null; private _wsServer: WebSocketServer | undefined; - private _mode: Mode; private _options: ServerOptions; - constructor(mode: Mode, options: ServerOptions) { - this._mode = mode; + constructor(options: ServerOptions) { this._options = options; - if (mode === 'use-pre-launched-browser') { - assert(options.preLaunchedBrowser); + if (options.preLaunchedBrowser) this._preLaunchedPlaywright = options.preLaunchedBrowser.options.rootSdkObject as Playwright; - } } preLaunchedPlaywright(): Playwright { @@ -95,13 +87,10 @@ export class PlaywrightServer { debugLog('Listening at ' + wsEndpoint); this._wsServer = new wsServer({ server, path: this._options.path }); - const browserSemaphore = new Semaphore(this._options.maxConcurrentConnections); + const browserSemaphore = new Semaphore(this._options.maxConnections); const controllerSemaphore = new Semaphore(1); + const reuseBrowserSemaphore = new Semaphore(1); this._wsServer.on('connection', (ws, request) => { - if (browserSemaphore.requested() >= this._options.maxIncomingConnections) { - ws.close(1013, 'Playwright Server is busy'); - return; - } const url = new URL('http://localhost' + (request.url || '')); const browserHeader = request.headers['x-playwright-browser']; const browserName = url.searchParams.get('browser') || (Array.isArray(browserHeader) ? browserHeader[0] : browserHeader) || null; @@ -119,26 +108,27 @@ export class PlaywrightServer { const log = newLogger(); log(`serving connection: ${request.url}`); const isDebugControllerClient = !!request.headers['x-playwright-debug-controller']; - const semaphore = isDebugControllerClient ? controllerSemaphore : browserSemaphore; + const shouldReuseBrowser = !!request.headers['x-playwright-reuse-context']; + const semaphore = isDebugControllerClient ? controllerSemaphore : (shouldReuseBrowser ? reuseBrowserSemaphore : browserSemaphore); // If we started in the legacy reuse-browser mode, create this._preLaunchedPlaywright. // If we get a reuse-controller request, create this._preLaunchedPlaywright. - if (isDebugControllerClient || (this._mode === 'reuse-browser') && !this._preLaunchedPlaywright) + if (isDebugControllerClient || shouldReuseBrowser) this.preLaunchedPlaywright(); - // If we have a playwright to reuse, consult controller for reuse mode. - let mode = this._mode; - if (mode === 'auto' && this._preLaunchedPlaywright?.debugController.reuseBrowser()) - mode = 'reuse-browser'; - - if (mode === 'reuse-browser') - semaphore.setMax(1); - else - semaphore.setMax(this._options.maxConcurrentConnections); + let clientType: ClientType = 'playwright'; + if (isDebugControllerClient) + clientType = 'controller'; + else if (shouldReuseBrowser) + clientType = 'reuse-browser'; + else if (this._options.preLaunchedBrowser) + clientType = 'pre-launched-browser'; + else if (browserName) + clientType = 'launch-browser'; const connection = new PlaywrightConnection( semaphore.aquire(), - mode, ws, isDebugControllerClient, + clientType, ws, { enableSocksProxy, browserName, launchOptions }, { playwright: this._preLaunchedPlaywright, browser: this._options.preLaunchedBrowser || null }, log, () => semaphore.release()); @@ -192,10 +182,6 @@ export class Semaphore { return lock; } - requested() { - return this._aquired + this._queue.length; - } - release() { --this._aquired; this._flush(); diff --git a/packages/playwright-core/src/server/debugController.ts b/packages/playwright-core/src/server/debugController.ts index 3c01a5ea68..b8105c88c6 100644 --- a/packages/playwright-core/src/server/debugController.ts +++ b/packages/playwright-core/src/server/debugController.ts @@ -55,7 +55,6 @@ export class DebugController extends SdkObject { dispose() { this.setTrackHierarcy(false); this.setAutoCloseAllowed(false); - this.setReuseBrowser(false); } setTrackHierarcy(enabled: boolean) { @@ -72,14 +71,6 @@ export class DebugController extends SdkObject { } } - reuseBrowser(): boolean { - return this._reuseBrowser; - } - - setReuseBrowser(enabled: boolean) { - this._reuseBrowser = enabled; - } - async resetForReuse() { const contexts = new Set(); for (const page of this._playwright.allPages()) diff --git a/packages/playwright-core/src/server/dispatchers/debugControllerDispatcher.ts b/packages/playwright-core/src/server/dispatchers/debugControllerDispatcher.ts index d820996e55..4db2702767 100644 --- a/packages/playwright-core/src/server/dispatchers/debugControllerDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/debugControllerDispatcher.ts @@ -40,10 +40,6 @@ export class DebugControllerDispatcher extends Dispatcher({ headless: [({ launchOptions }, use) => use(launchOptions.headless ?? true), { scope: 'worker', option: true }], channel: [({ launchOptions }, use) => use(launchOptions.channel), { scope: 'worker', option: true }], launchOptions: [{}, { scope: 'worker', option: true }], - connectOptions: [process.env.PW_TEST_CONNECT_WS_ENDPOINT ? { - wsEndpoint: process.env.PW_TEST_CONNECT_WS_ENDPOINT, - headers: process.env.PW_TEST_CONNECT_HEADERS ? JSON.parse(process.env.PW_TEST_CONNECT_HEADERS) : undefined, - } : undefined, { scope: 'worker', option: true }], + connectOptions: [({}, use) => { + const wsEndpoint = process.env.PW_TEST_CONNECT_WS_ENDPOINT; + if (!wsEndpoint) + return use(undefined); + let headers = process.env.PW_TEST_CONNECT_HEADERS ? JSON.parse(process.env.PW_TEST_CONNECT_HEADERS) : undefined; + if (process.env.PW_TEST_REUSE_CONTEXT) { + headers = { + ...headers, + 'x-playwright-reuse-context': '1', + }; + } + return use({ + wsEndpoint, + headers + }); + }, { scope: 'worker', option: true }], screenshot: ['off', { scope: 'worker', option: true }], video: ['off', { scope: 'worker', option: true }], trace: ['off', { scope: 'worker', option: true }], diff --git a/packages/protocol/src/channels.ts b/packages/protocol/src/channels.ts index b550b98d46..f164d2fd10 100644 --- a/packages/protocol/src/channels.ts +++ b/packages/protocol/src/channels.ts @@ -599,7 +599,6 @@ export interface DebugControllerEventTarget { export interface DebugControllerChannel extends DebugControllerEventTarget, Channel { _type_DebugController: boolean; setTrackHierarchy(params: DebugControllerSetTrackHierarchyParams, metadata?: Metadata): Promise; - setReuseBrowser(params: DebugControllerSetReuseBrowserParams, metadata?: Metadata): Promise; resetForReuse(params?: DebugControllerResetForReuseParams, metadata?: Metadata): Promise; navigateAll(params: DebugControllerNavigateAllParams, metadata?: Metadata): Promise; setRecorderMode(params: DebugControllerSetRecorderModeParams, metadata?: Metadata): Promise; @@ -629,13 +628,6 @@ export type DebugControllerSetTrackHierarchyOptions = { }; export type DebugControllerSetTrackHierarchyResult = void; -export type DebugControllerSetReuseBrowserParams = { - enabled: boolean, -}; -export type DebugControllerSetReuseBrowserOptions = { - -}; -export type DebugControllerSetReuseBrowserResult = void; export type DebugControllerResetForReuseParams = {}; export type DebugControllerResetForReuseOptions = {}; export type DebugControllerResetForReuseResult = void; diff --git a/packages/protocol/src/protocol.yml b/packages/protocol/src/protocol.yml index a8b7e8e8ce..5fc91070df 100644 --- a/packages/protocol/src/protocol.yml +++ b/packages/protocol/src/protocol.yml @@ -664,10 +664,6 @@ DebugController: parameters: enabled: boolean - setReuseBrowser: - parameters: - enabled: boolean - resetForReuse: navigateAll: