diff --git a/packages/playwright-core/src/client/browserType.ts b/packages/playwright-core/src/client/browserType.ts index c4b782f5c3189..527a3b1bd707e 100644 --- a/packages/playwright-core/src/client/browserType.ts +++ b/packages/playwright-core/src/client/browserType.ts @@ -49,6 +49,7 @@ export class BrowserType extends ChannelOwner imple // Instrumentation. _defaultContextOptions?: BrowserContextOptions; _defaultLaunchOptions?: LaunchOptions; + _defaultConnectOptions?: ConnectOptions; _onDidCreateContext?: (context: BrowserContext) => Promise; _onWillCloseContext?: (context: BrowserContext) => Promise; @@ -67,6 +68,9 @@ export class BrowserType extends ChannelOwner imple } async launch(options: LaunchOptions = {}): Promise { + if (this._defaultConnectOptions) + return await this._connectInsteadOfLaunching(); + const logger = options.logger || this._defaultLaunchOptions?.logger; assert(!(options as any).userDataDir, 'userDataDir option is not supported in `browserType.launch`. Use `browserType.launchPersistentContext` instead'); assert(!(options as any).port, 'Cannot specify a port without launching as a server.'); @@ -83,6 +87,18 @@ export class BrowserType extends ChannelOwner imple return browser; } + private async _connectInsteadOfLaunching(): Promise { + const connectOptions = this._defaultConnectOptions!; + return this._connect(connectOptions.wsEndpoint, { + headers: { + 'x-playwright-browser': this.name(), + 'x-playwright-launch-options': JSON.stringify(this._defaultLaunchOptions || {}), + ...connectOptions.headers, + }, + timeout: connectOptions.timeout ?? 3 * 60 * 1000, // 3 minutes + }); + } + async launchServer(options: LaunchServerOptions = {}): Promise { if (!this._serverLauncher) throw new Error('Launching server is not supported'); diff --git a/packages/playwright-test/src/index.ts b/packages/playwright-test/src/index.ts index b1b8c18b361af..aadc12f9a6360 100644 --- a/packages/playwright-test/src/index.ts +++ b/packages/playwright-test/src/index.ts @@ -16,7 +16,7 @@ import * as fs from 'fs'; import * as path from 'path'; -import type { LaunchOptions, BrowserContextOptions, Page, Browser, BrowserContext, Video, APIRequestContext, Tracing } from 'playwright-core'; +import type { LaunchOptions, BrowserContextOptions, Page, BrowserContext, Video, APIRequestContext, Tracing } from 'playwright-core'; import type { TestType, PlaywrightTestArgs, PlaywrightTestOptions, PlaywrightWorkerArgs, PlaywrightWorkerOptions, TestInfo, VideoMode, TraceMode } from '../types/test'; import { rootTestType } from './testType'; import { createGuid, debugMode } from 'playwright-core/lib/utils'; @@ -48,7 +48,6 @@ type TestFixtures = PlaywrightTestArgs & PlaywrightTestOptions & { _contextFactory: (options?: BrowserContextOptions) => Promise; }; type WorkerFixtures = PlaywrightWorkerArgs & PlaywrightWorkerOptions & { - _connectedBrowser: Browser | undefined, _browserOptions: LaunchOptions; _artifactsDir: () => string; _snapshotSuffix: string; @@ -91,7 +90,7 @@ export const test = _baseTest.extend({ await removeFolders([dir]); }, { scope: 'worker', _title: 'playwright configuration' } as any], - _browserOptions: [async ({ playwright, headless, channel, launchOptions }, use) => { + _browserOptions: [async ({ playwright, headless, channel, launchOptions, connectOptions }, use) => { const options: LaunchOptions = { handleSIGINT: false, timeout: 0, @@ -102,38 +101,18 @@ export const test = _baseTest.extend({ if (channel !== undefined) options.channel = channel; - for (const browserType of [playwright.chromium, playwright.firefox, playwright.webkit]) + for (const browserType of [playwright.chromium, playwright.firefox, playwright.webkit]) { (browserType as any)._defaultLaunchOptions = options; + (browserType as any)._defaultConnectOptions = connectOptions; + } await use(options); - for (const browserType of [playwright.chromium, playwright.firefox, playwright.webkit]) + for (const browserType of [playwright.chromium, playwright.firefox, playwright.webkit]) { (browserType as any)._defaultLaunchOptions = undefined; - }, { scope: 'worker', auto: true }], - - _connectedBrowser: [async ({ playwright, browserName, connectOptions, _browserOptions }, use) => { - if (!connectOptions) { - await use(undefined); - return; - } - if (!['chromium', 'firefox', 'webkit'].includes(browserName)) - throw new Error(`Unexpected browserName "${browserName}", must be one of "chromium", "firefox" or "webkit"`); - const browser = await playwright[browserName].connect(connectOptions.wsEndpoint, { - headers: { - 'x-playwright-browser': browserName, - 'x-playwright-launch-options': JSON.stringify(_browserOptions), - ...connectOptions.headers, - }, - timeout: connectOptions.timeout ?? 3 * 60 * 1000, // 3 minutes - }); - await use(browser); - await browser.close(); - }, { scope: 'worker', timeout: 0, _title: 'remote connection' } as any], - - browser: [async ({ playwright, browserName, _connectedBrowser }, use) => { - if (_connectedBrowser) { - await use(_connectedBrowser); - return; + (browserType as any)._defaultConnectOptions = undefined; } + }, { scope: 'worker', auto: true }], + browser: [async ({ playwright, browserName }, use) => { if (!['chromium', 'firefox', 'webkit'].includes(browserName)) throw new Error(`Unexpected browserName "${browserName}", must be one of "chromium", "firefox" or "webkit"`); const browser = await playwright[browserName].launch(); diff --git a/tests/library/browsertype-connect.spec.ts b/tests/library/browsertype-connect.spec.ts index 9822a85a40108..dcc0edb67325e 100644 --- a/tests/library/browsertype-connect.spec.ts +++ b/tests/library/browsertype-connect.spec.ts @@ -627,4 +627,16 @@ test('should upload large file', async ({ browserType, startRemoteServer, server await Promise.all([uploadFile, file1.filepath].map(fs.promises.unlink)); }); +test('should connect when launching', async ({ browserType, startRemoteServer, httpsServer, mode }) => { + const remoteServer = await startRemoteServer(); + (browserType as any)._defaultConnectOptions = { + wsEndpoint: remoteServer.wsEndpoint() + }; + + const browser = await browserType.launch(); + await Promise.all([ + new Promise(f => browser.on('disconnected', f)), + remoteServer.close(), + ]); +}); diff --git a/tests/playwright-test/playwright.connect.spec.ts b/tests/playwright-test/playwright.connect.spec.ts index 143a2db7907f7..48a12e4b5e529 100644 --- a/tests/playwright-test/playwright.connect.spec.ts +++ b/tests/playwright-test/playwright.connect.spec.ts @@ -70,7 +70,8 @@ test('should throw with bad connectOptions', async ({ runInlineTest }) => { }); expect(result.exitCode).toBe(1); expect(result.passed).toBe(0); - expect(result.output).toContain('browserType.connect:'); + expect(result.output).toContain('browserType.launch:'); + expect(result.output).toContain('does-not-exist-bad-domain'); }); test('should respect connectOptions.timeout', async ({ runInlineTest }) => { @@ -93,5 +94,5 @@ test('should respect connectOptions.timeout', async ({ runInlineTest }) => { }); expect(result.exitCode).toBe(1); expect(result.passed).toBe(0); - expect(result.output).toContain('browserType.connect: Timeout 1ms exceeded.'); + expect(result.output).toContain('browserType.launch: Timeout 1ms exceeded.'); });