diff --git a/packages/puppeteer-core/src/cdp/BrowserConnector.ts b/packages/puppeteer-core/src/cdp/BrowserConnector.ts index c5228b5fb3a..9178b553ed7 100644 --- a/packages/puppeteer-core/src/cdp/BrowserConnector.ts +++ b/packages/puppeteer-core/src/cdp/BrowserConnector.ts @@ -18,6 +18,7 @@ import type { IsPageTargetCallback, TargetFilterCallback, } from '../api/Browser.js'; +import type {BidiBrowser} from '../bidi/Browser.js'; import type {ConnectionTransport} from '../common/ConnectionTransport.js'; import {getFetch} from '../common/fetch.js'; import {debugError} from '../common/util.js'; @@ -29,6 +30,9 @@ import {isErrorLike} from '../util/ErrorLike.js'; import {CdpBrowser} from './Browser.js'; import {Connection} from './Connection.js'; import type {ConnectOptions} from './ConnectOptions.js'; + +const DEFAULT_VIEWPORT = Object.freeze({width: 800, height: 600}); + /** * Generic browser options that can be passed when launching any browser or when * connecting to an existing browser instance. @@ -79,7 +83,7 @@ const getWebSocketTransportClass = async () => { /** * Users should never call this directly; it's called when calling - * `puppeteer.connect`. + * `puppeteer.connect` with `protocol: 'cdp'`. * * @internal */ @@ -87,51 +91,15 @@ export async function _connectToCdpBrowser( options: BrowserConnectOptions & ConnectOptions ): Promise { const { - browserWSEndpoint, - browserURL, ignoreHTTPSErrors = false, - defaultViewport = {width: 800, height: 600}, - transport, - headers = {}, - slowMo = 0, + defaultViewport = DEFAULT_VIEWPORT, targetFilter, _isPageTarget: isPageTarget, - protocolTimeout, } = options; - assert( - Number(!!browserWSEndpoint) + Number(!!browserURL) + Number(!!transport) === - 1, - 'Exactly one of browserWSEndpoint, browserURL or transport must be passed to puppeteer.connect' - ); + const connection = await getCdpConnection(options); - let connection!: Connection; - if (transport) { - connection = new Connection('', transport, slowMo, protocolTimeout); - } else if (browserWSEndpoint) { - const WebSocketClass = await getWebSocketTransportClass(); - const connectionTransport: ConnectionTransport = - await WebSocketClass.create(browserWSEndpoint, headers); - connection = new Connection( - browserWSEndpoint, - connectionTransport, - slowMo, - protocolTimeout - ); - } else if (browserURL) { - const connectionURL = await getWSEndpoint(browserURL); - const WebSocketClass = await getWebSocketTransportClass(); - const connectionTransport: ConnectionTransport = - await WebSocketClass.create(connectionURL); - connection = new Connection( - connectionURL, - connectionTransport, - slowMo, - protocolTimeout - ); - } const version = await connection.send('Browser.getVersion'); - const product = version.product.toLowerCase().includes('firefox') ? 'firefox' : 'chrome'; @@ -155,6 +123,40 @@ export async function _connectToCdpBrowser( return browser; } +/** + * Users should never call this directly; it's called when calling + * `puppeteer.connect` with `protocol: 'webDriverBiDi'`. + * + * @internal + */ +export async function _connectToBiDiOverCdpBrowser( + options: BrowserConnectOptions & ConnectOptions +): Promise { + const {ignoreHTTPSErrors = false, defaultViewport = DEFAULT_VIEWPORT} = + options; + + const connection = await getCdpConnection(options); + + const version = await connection.send('Browser.getVersion'); + if (version.product.toLowerCase().includes('firefox')) { + throw new Error('Firefox is not supported in BiDi over CDP mode.'); + } + + // TODO: use other options too. + const BiDi = await import(/* webpackIgnore: true */ '../bidi/bidi.js'); + const bidiConnection = await BiDi.connectBidiOverCdp(connection); + const bidiBrowser = await BiDi.BidiBrowser.create({ + connection: bidiConnection, + closeCallback: () => { + return connection.send('Browser.close').catch(debugError); + }, + process: undefined, + defaultViewport: defaultViewport, + ignoreHTTPSErrors: ignoreHTTPSErrors, + }); + return bidiBrowser; +} + async function getWSEndpoint(browserURL: string): Promise { const endpointURL = new URL('/json/version', browserURL); @@ -177,3 +179,51 @@ async function getWSEndpoint(browserURL: string): Promise { throw error; } } + +/** + * Returns a CDP connection for the given options. + */ +async function getCdpConnection( + options: BrowserConnectOptions & ConnectOptions +): Promise { + const { + browserWSEndpoint, + browserURL, + transport, + headers = {}, + slowMo = 0, + protocolTimeout, + } = options; + + assert( + Number(!!browserWSEndpoint) + Number(!!browserURL) + Number(!!transport) === + 1, + 'Exactly one of browserWSEndpoint, browserURL or transport must be passed to puppeteer.connect' + ); + + if (transport) { + return new Connection('', transport, slowMo, protocolTimeout); + } else if (browserWSEndpoint) { + const WebSocketClass = await getWebSocketTransportClass(); + const connectionTransport: ConnectionTransport = + await WebSocketClass.create(browserWSEndpoint, headers); + return new Connection( + browserWSEndpoint, + connectionTransport, + slowMo, + protocolTimeout + ); + } else if (browserURL) { + const connectionURL = await getWSEndpoint(browserURL); + const WebSocketClass = await getWebSocketTransportClass(); + const connectionTransport: ConnectionTransport = + await WebSocketClass.create(connectionURL); + return new Connection( + connectionURL, + connectionTransport, + slowMo, + protocolTimeout + ); + } + throw new Error('Invalid connection options'); +} diff --git a/packages/puppeteer-core/src/common/Puppeteer.ts b/packages/puppeteer-core/src/common/Puppeteer.ts index 60bb03add43..e8c81b176ac 100644 --- a/packages/puppeteer-core/src/common/Puppeteer.ts +++ b/packages/puppeteer-core/src/common/Puppeteer.ts @@ -15,7 +15,10 @@ */ import type {Browser} from '../api/Browser.js'; -import {_connectToCdpBrowser} from '../cdp/BrowserConnector.js'; +import { + _connectToBiDiOverCdpBrowser, + _connectToCdpBrowser, +} from '../cdp/BrowserConnector.js'; import type {ConnectOptions} from '../cdp/ConnectOptions.js'; import { @@ -129,7 +132,7 @@ export class Puppeteer { */ connect(options: ConnectOptions): Promise { if (options.protocol === 'webDriverBiDi') { - throw new Error('Not implemented'); + return _connectToBiDiOverCdpBrowser(options); } else { return _connectToCdpBrowser(options); } diff --git a/test/TestExpectations.json b/test/TestExpectations.json index 3894aa0d2bd..31220939449 100644 --- a/test/TestExpectations.json +++ b/test/TestExpectations.json @@ -383,12 +383,6 @@ "parameters": ["webDriverBiDi"], "expectations": ["FAIL"] }, - { - "testIdPattern": "[browser.spec] Browser specs Browser.process should not return child_process for remote browser", - "platforms": ["darwin", "linux", "win32"], - "parameters": ["webDriverBiDi"], - "expectations": ["FAIL"] - }, { "testIdPattern": "[browser.spec] Browser specs Browser.process should return child_process instance", "platforms": ["darwin", "linux", "win32"], @@ -645,13 +639,13 @@ "testIdPattern": "[launcher.spec] Launcher specs Puppeteer Browser.disconnect should reject navigation when browser closes", "platforms": ["darwin", "linux", "win32"], "parameters": ["webDriverBiDi"], - "expectations": ["FAIL", "PASS"] + "expectations": ["FAIL", "TIMEOUT"] }, { "testIdPattern": "[launcher.spec] Launcher specs Puppeteer Browser.disconnect should reject waitForSelector when browser closes", "platforms": ["darwin", "linux", "win32"], "parameters": ["webDriverBiDi"], - "expectations": ["FAIL", "PASS"] + "expectations": ["FAIL", "TIMEOUT"] }, { "testIdPattern": "[launcher.spec] Launcher specs Puppeteer Puppeteer.executablePath returns executablePath for channel", @@ -2031,19 +2025,19 @@ "testIdPattern": "[launcher.spec] Launcher specs Puppeteer Puppeteer.connect should be able to close remote browser", "platforms": ["darwin", "linux", "win32"], "parameters": ["chrome", "webDriverBiDi"], - "expectations": ["FAIL"] + "expectations": ["PASS"] }, { "testIdPattern": "[launcher.spec] Launcher specs Puppeteer Puppeteer.connect should be able to connect multiple times to the same browser", "platforms": ["darwin", "linux", "win32"], "parameters": ["chrome", "webDriverBiDi"], - "expectations": ["FAIL"] + "expectations": ["PASS"] }, { "testIdPattern": "[launcher.spec] Launcher specs Puppeteer Puppeteer.connect should be able to connect to a browser with no page targets", "platforms": ["darwin", "linux", "win32"], "parameters": ["chrome", "webDriverBiDi"], - "expectations": ["FAIL"] + "expectations": ["PASS"] }, { "testIdPattern": "[launcher.spec] Launcher specs Puppeteer Puppeteer.connect should be able to connect to a browser with no page targets", @@ -2061,7 +2055,7 @@ "testIdPattern": "[launcher.spec] Launcher specs Puppeteer Puppeteer.connect should be able to connect to the same page simultaneously", "platforms": ["darwin", "linux", "win32"], "parameters": ["chrome", "webDriverBiDi"], - "expectations": ["FAIL"] + "expectations": ["PASS"] }, { "testIdPattern": "[launcher.spec] Launcher specs Puppeteer Puppeteer.connect should be able to reconnect to a disconnected browser", @@ -2073,7 +2067,7 @@ "testIdPattern": "[launcher.spec] Launcher specs Puppeteer Puppeteer.connect should support ignoreHTTPSErrors option", "platforms": ["darwin", "linux", "win32"], "parameters": ["chrome", "webDriverBiDi"], - "expectations": ["FAIL"] + "expectations": ["SKIP"] }, { "testIdPattern": "[launcher.spec] Launcher specs Puppeteer Puppeteer.connect should support targetFilter option",