chore: implement Puppeteer.connect for BiDi over CDP browser (#11350)

Co-authored-by: Maksim Sadym <sadym@google.com>
This commit is contained in:
Maksim Sadym 2023-11-13 10:35:40 +01:00 committed by Alex Rudenko
parent cdf2b4395c
commit 9f161ec76b
3 changed files with 101 additions and 54 deletions

View File

@ -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<CdpBrowser> {
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<BidiBrowser> {
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<string> {
const endpointURL = new URL('/json/version', browserURL);
@ -177,3 +179,51 @@ async function getWSEndpoint(browserURL: string): Promise<string> {
throw error;
}
}
/**
* Returns a CDP connection for the given options.
*/
async function getCdpConnection(
options: BrowserConnectOptions & ConnectOptions
): Promise<Connection> {
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');
}

View File

@ -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<Browser> {
if (options.protocol === 'webDriverBiDi') {
throw new Error('Not implemented');
return _connectToBiDiOverCdpBrowser(options);
} else {
return _connectToCdpBrowser(options);
}

View File

@ -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",