feat(cdp): replace wsEndpoint with protocol neutral endpointURL (#6141)

This commit is contained in:
Joel Einbinder 2021-04-08 14:55:28 -07:00 committed by GitHub
parent 53d50f9b72
commit 63d0d466e3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 111 additions and 71 deletions

View File

@ -116,7 +116,7 @@ Connecting over the Chrome DevTools Protocol is only supported for Chromium-base
### param: BrowserType.connectOverCDP.params ### param: BrowserType.connectOverCDP.params
* langs: js * langs: js
- `params` <[Object]> - `params` <[Object]>
- `wsEndpoint` <[string]> A CDP websocket endpoint to connect to. - `endpointURL` <[string]> A CDP websocket endpoint or http url to connect to. For example `http://localhost:9222/` or `ws://127.0.0.1:9222/devtools/browser/387adf4c-243f-4051-a181-46798f4a46f4`.
- `slowMo` <[float]> Slows down Playwright operations by the specified amount of milliseconds. Useful so that you - `slowMo` <[float]> Slows down Playwright operations by the specified amount of milliseconds. Useful so that you
can see what is going on. Defaults to 0. can see what is going on. Defaults to 0.
- `logger` <[Logger]> Logger sink for Playwright logging. Optional. - `logger` <[Logger]> Logger sink for Playwright logging. Optional.

View File

@ -58,11 +58,11 @@ page.navigate("https://www.w3.org/");
playwright.close(); playwright.close();
``` ```
### param: BrowserType.connectOverCDP.wsEndpoint ### param: BrowserType.connectOverCDP.endpointURL
* langs: java * langs: java
- `wsEndpoint` <[string]> - `endpointURL` <[string]>
A CDP websocket endpoint to connect to. A CDP websocket endpoint or http url to connect to. For example `http://localhost:9222/` or `ws://127.0.0.1:9222/devtools/browser/387adf4c-243f-4051-a181-46798f4a46f4`.
### param: BrowserContext.waitForPage.callback = %%-java-wait-for-event-callback-%% ### param: BrowserContext.waitForPage.callback = %%-java-wait-for-event-callback-%%

View File

@ -187,14 +187,16 @@ export class BrowserType extends ChannelOwner<channels.BrowserTypeChannel, chann
}, logger); }, logger);
} }
async connectOverCDP(params: ConnectOptions): Promise<Browser> { async connectOverCDP(params: api.ConnectOverCDPOptions): Promise<Browser>
async connectOverCDP(params: api.ConnectOptions): Promise<Browser>
async connectOverCDP(params: api.ConnectOverCDPOptions | api.ConnectOptions): Promise<Browser> {
if (this.name() !== 'chromium') if (this.name() !== 'chromium')
throw new Error('Connecting over CDP is only supported in Chromium.'); throw new Error('Connecting over CDP is only supported in Chromium.');
const logger = params.logger; const logger = params.logger;
return this._wrapApiCall('browserType.connectOverCDP', async (channel: channels.BrowserTypeChannel) => { return this._wrapApiCall('browserType.connectOverCDP', async (channel: channels.BrowserTypeChannel) => {
const result = await channel.connectOverCDP({ const result = await channel.connectOverCDP({
sdkLanguage: 'javascript', sdkLanguage: 'javascript',
wsEndpoint: params.wsEndpoint, endpointURL: 'endpointURL' in params ? params.endpointURL : params.wsEndpoint,
slowMo: params.slowMo, slowMo: params.slowMo,
timeout: params.timeout timeout: params.timeout
}); });

View File

@ -40,7 +40,7 @@ export class BrowserTypeDispatcher extends Dispatcher<BrowserType, channels.Brow
} }
async connectOverCDP(params: channels.BrowserTypeConnectOverCDPParams, metadata: CallMetadata): Promise<channels.BrowserTypeConnectOverCDPResult> { async connectOverCDP(params: channels.BrowserTypeConnectOverCDPParams, metadata: CallMetadata): Promise<channels.BrowserTypeConnectOverCDPResult> {
const browser = await this._object.connectOverCDP(metadata, params.wsEndpoint, params, params.timeout); const browser = await this._object.connectOverCDP(metadata, params.endpointURL, params, params.timeout);
return { return {
browser: new BrowserDispatcher(this._scope, browser), browser: new BrowserDispatcher(this._scope, browser),
defaultContext: browser._defaultContext ? new BrowserContextDispatcher(this._scope, browser._defaultContext) : undefined, defaultContext: browser._defaultContext ? new BrowserContextDispatcher(this._scope, browser._defaultContext) : undefined,

View File

@ -408,7 +408,7 @@ export type BrowserTypeLaunchPersistentContextResult = {
}; };
export type BrowserTypeConnectOverCDPParams = { export type BrowserTypeConnectOverCDPParams = {
sdkLanguage: string, sdkLanguage: string,
wsEndpoint: string, endpointURL: string,
slowMo?: number, slowMo?: number,
timeout?: number, timeout?: number,
}; };

View File

@ -422,7 +422,7 @@ BrowserType:
connectOverCDP: connectOverCDP:
parameters: parameters:
sdkLanguage: string sdkLanguage: string
wsEndpoint: string endpointURL: string
slowMo: number? slowMo: number?
timeout: number? timeout: number?
returns: returns:

View File

@ -249,7 +249,7 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
}); });
scheme.BrowserTypeConnectOverCDPParams = tObject({ scheme.BrowserTypeConnectOverCDPParams = tObject({
sdkLanguage: tString, sdkLanguage: tString,
wsEndpoint: tString, endpointURL: tString,
slowMo: tOptional(tNumber), slowMo: tOptional(tNumber),
timeout: tOptional(tNumber), timeout: tOptional(tNumber),
}); });

View File

@ -248,7 +248,7 @@ export abstract class BrowserType extends SdkObject {
return { browserProcess, downloadsPath, transport }; return { browserProcess, downloadsPath, transport };
} }
async connectOverCDP(metadata: CallMetadata, wsEndpoint: string, options: { slowMo?: number, sdkLanguage: string }, timeout?: number): Promise<Browser> { async connectOverCDP(metadata: CallMetadata, endpointURL: string, options: { slowMo?: number, sdkLanguage: string }, timeout?: number): Promise<Browser> {
throw new Error('CDP connections are only supported by Chromium'); throw new Error('CDP connections are only supported by Chromium');
} }

View File

@ -32,6 +32,7 @@ import { TimeoutSettings } from '../../utils/timeoutSettings';
import { helper } from '../helper'; import { helper } from '../helper';
import { CallMetadata } from '../instrumentation'; import { CallMetadata } from '../instrumentation';
import { findChromiumChannel } from './findChromiumChannel'; import { findChromiumChannel } from './findChromiumChannel';
import http from 'http';
export class Chromium extends BrowserType { export class Chromium extends BrowserType {
private _devtools: CRDevTools | undefined; private _devtools: CRDevTools | undefined;
@ -49,12 +50,12 @@ export class Chromium extends BrowserType {
return super.executablePath(options); return super.executablePath(options);
} }
async connectOverCDP(metadata: CallMetadata, wsEndpoint: string, options: { slowMo?: number, sdkLanguage: string }, timeout?: number) { async connectOverCDP(metadata: CallMetadata, endpointURL: string, options: { slowMo?: number, sdkLanguage: string }, timeout?: number) {
const controller = new ProgressController(metadata, this); const controller = new ProgressController(metadata, this);
controller.setLogName('browser'); controller.setLogName('browser');
const browserLogsCollector = new RecentLogsCollector(); const browserLogsCollector = new RecentLogsCollector();
return controller.run(async progress => { return controller.run(async progress => {
const chromeTransport = await WebSocketTransport.connect(progress, wsEndpoint); const chromeTransport = await WebSocketTransport.connect(progress, await urlToWSEndpoint(endpointURL));
const browserProcess: BrowserProcess = { const browserProcess: BrowserProcess = {
close: async () => { close: async () => {
await chromeTransport.closeAndWait(); await chromeTransport.closeAndWait();
@ -192,3 +193,17 @@ const DEFAULT_ARGS = [
'--password-store=basic', '--password-store=basic',
'--use-mock-keychain', '--use-mock-keychain',
]; ];
async function urlToWSEndpoint(endpointURL: string) {
if (endpointURL.startsWith('ws'))
return endpointURL;
const httpURL = endpointURL.endsWith('/') ? `${endpointURL}json/version/` : `${endpointURL}/json/version/`;
const json = await new Promise<string>((resolve, reject) => {
http.get(httpURL, resp => {
let data = '';
resp.on('data', chunk => data += chunk);
resp.on('end', () => resolve(data));
}).on('error', reject);
});
return JSON.parse(json).webSocketDebuggerUrl;
}

View File

@ -33,6 +33,6 @@ test('browserType.name should work', async ({browserType, browserName}) => {
test('should throw when trying to connect with not-chromium', async ({ browserType, browserName }) => { test('should throw when trying to connect with not-chromium', async ({ browserType, browserName }) => {
test.skip(browserName === 'chromium'); test.skip(browserName === 'chromium');
const error = await browserType.connectOverCDP({wsEndpoint: 'foo'}).catch(e => e); const error = await browserType.connectOverCDP({endpointURL: 'ws://foo'}).catch(e => e);
expect(error.message).toBe('Connecting over CDP is only supported in Chromium.'); expect(error.message).toBe('Connecting over CDP is only supported in Chromium.');
}); });

View File

@ -112,15 +112,8 @@ playwrightTest.describe('chromium', () => {
args: ['--remote-debugging-port=' + port] args: ['--remote-debugging-port=' + port]
}); });
try { try {
const json = await new Promise<string>((resolve, reject) => {
http.get(`http://localhost:${port}/json/version/`, resp => {
let data = '';
resp.on('data', chunk => data += chunk);
resp.on('end', () => resolve(data));
}).on('error', reject);
});
const cdpBrowser = await browserType.connectOverCDP({ const cdpBrowser = await browserType.connectOverCDP({
wsEndpoint: JSON.parse(json).webSocketDebuggerUrl, endpointURL: `http://localhost:${port}/`,
}); });
const contexts = cdpBrowser.contexts(); const contexts = cdpBrowser.contexts();
expect(contexts.length).toBe(1); expect(contexts.length).toBe(1);
@ -137,18 +130,11 @@ playwrightTest.describe('chromium', () => {
args: ['--remote-debugging-port=' + port] args: ['--remote-debugging-port=' + port]
}); });
try { try {
const json = await new Promise<string>((resolve, reject) => {
http.get(`http://localhost:${port}/json/version/`, resp => {
let data = '';
resp.on('data', chunk => data += chunk);
resp.on('end', () => resolve(data));
}).on('error', reject);
});
const cdpBrowser1 = await browserType.connectOverCDP({ const cdpBrowser1 = await browserType.connectOverCDP({
wsEndpoint: JSON.parse(json).webSocketDebuggerUrl, endpointURL: `http://localhost:${port}/`,
}); });
const cdpBrowser2 = await browserType.connectOverCDP({ const cdpBrowser2 = await browserType.connectOverCDP({
wsEndpoint: JSON.parse(json).webSocketDebuggerUrl, endpointURL: `http://localhost:${port}/`,
}); });
const contexts1 = cdpBrowser1.contexts(); const contexts1 = cdpBrowser1.contexts();
expect(contexts1.length).toBe(1); expect(contexts1.length).toBe(1);
@ -179,15 +165,8 @@ playwrightTest.describe('chromium', () => {
args: ['--remote-debugging-port=' + port] args: ['--remote-debugging-port=' + port]
}); });
try { try {
const json = await new Promise<string>((resolve, reject) => {
http.get(`http://localhost:${port}/json/version/`, resp => {
let data = '';
resp.on('data', chunk => data += chunk);
resp.on('end', () => resolve(data));
}).on('error', reject);
});
const cdpBrowser1 = await browserType.connectOverCDP({ const cdpBrowser1 = await browserType.connectOverCDP({
wsEndpoint: JSON.parse(json).webSocketDebuggerUrl, endpointURL: `http://localhost:${port}`,
}); });
const context = cdpBrowser1.contexts()[0]; const context = cdpBrowser1.contexts()[0];
const page = await cdpBrowser1.contexts()[0].newPage(); const page = await cdpBrowser1.contexts()[0].newPage();
@ -199,7 +178,7 @@ playwrightTest.describe('chromium', () => {
await cdpBrowser1.close(); await cdpBrowser1.close();
const cdpBrowser2 = await browserType.connectOverCDP({ const cdpBrowser2 = await browserType.connectOverCDP({
wsEndpoint: JSON.parse(json).webSocketDebuggerUrl, endpointURL: `http://localhost:${port}`,
}); });
const context2 = cdpBrowser2.contexts()[0]; const context2 = cdpBrowser2.contexts()[0];
expect(context2.serviceWorkers().length).toBe(1); expect(context2.serviceWorkers().length).toBe(1);
@ -208,4 +187,36 @@ playwrightTest.describe('chromium', () => {
await browserServer.close(); await browserServer.close();
} }
}); });
playwrightTest('should connect over a ws endpoint', async ({browserType, browserOptions, server}, testInfo) => {
const port = 9339 + testInfo.workerIndex;
const browserServer = await browserType.launch({
...browserOptions,
args: ['--remote-debugging-port=' + port]
});
try {
const json = await new Promise<string>((resolve, reject) => {
http.get(`http://localhost:${port}/json/version/`, resp => {
let data = '';
resp.on('data', chunk => data += chunk);
resp.on('end', () => resolve(data));
}).on('error', reject);
});
const cdpBrowser = await browserType.connectOverCDP({
endpointURL: JSON.parse(json).webSocketDebuggerUrl,
});
const contexts = cdpBrowser.contexts();
expect(contexts.length).toBe(1);
await cdpBrowser.close();
// also connect with the depercreated wsEndpoint option
const cdpBrowser2 = await browserType.connectOverCDP({
wsEndpoint: JSON.parse(json).webSocketDebuggerUrl,
});
const contexts2 = cdpBrowser.contexts();
expect(contexts2.length).toBe(1);
await cdpBrowser2.close();
} finally {
await browserServer.close();
}
});
}); });

58
types/types.d.ts vendored
View File

@ -6460,13 +6460,6 @@ export interface ElementHandle<T=Node> extends JSHandle<T> {
* *
*/ */
export interface BrowserType<Unused = {}> { export interface BrowserType<Unused = {}> {
/**
* This methods attaches Playwright to an existing browser instance.
* @param params
*/
connect(params: ConnectOptions): Promise<Browser>;
/** /**
* This methods attaches Playwright to an existing browser instance using the Chrome DevTools Protocol. * This methods attaches Playwright to an existing browser instance using the Chrome DevTools Protocol.
* *
@ -6476,29 +6469,17 @@ export interface BrowserType<Unused = {}> {
* > NOTE: Connecting over the Chrome DevTools Protocol is only supported for Chromium-based browsers. * > NOTE: Connecting over the Chrome DevTools Protocol is only supported for Chromium-based browsers.
* @param params * @param params
*/ */
connectOverCDP(params: { connectOverCDP(options: ConnectOverCDPOptions): Promise<Browser>;
/** /**
* A CDP websocket endpoint to connect to. * Option `wsEndpoint` is deprecated. Instead use `endpointURL`.
* @deprecated
*/ */
wsEndpoint: string; connectOverCDP(options: ConnectOptions): Promise<Browser>;
/** /**
* Slows down Playwright operations by the specified amount of milliseconds. Useful so that you can see what is going on. * This methods attaches Playwright to an existing browser instance.
* Defaults to 0. * @param params
*/ */
slowMo?: number; connect(params: ConnectOptions): Promise<Browser>;
/**
* Logger sink for Playwright logging. Optional.
*/
logger?: Logger;
/**
* Maximum time in milliseconds to wait for the connection to be established. Defaults to `30000` (30 seconds). Pass `0` to
* disable timeout.
*/
timeout?: number;
}): Promise<Browser>;
/** /**
* A path where Playwright expects to find a bundled browser executable. * A path where Playwright expects to find a bundled browser executable.
@ -10687,6 +10668,31 @@ export interface LaunchOptions {
timeout?: number; timeout?: number;
} }
export interface ConnectOverCDPOptions {
/**
* A CDP websocket endpoint or http url to connect to. For example `http://localhost:9222/` or
* `ws://127.0.0.1:9222/devtools/browser/387adf4c-243f-4051-a181-46798f4a46f4`.
*/
endpointURL: string;
/**
* Slows down Playwright operations by the specified amount of milliseconds. Useful so that you can see what is going on.
* Defaults to 0.
*/
slowMo?: number;
/**
* Logger sink for Playwright logging. Optional.
*/
logger?: Logger;
/**
* Maximum time in milliseconds to wait for the connection to be established. Defaults to `30000` (30 seconds). Pass `0` to
* disable timeout.
*/
timeout?: number;
}
export interface ConnectOptions { export interface ConnectOptions {
/** /**
* A browser websocket endpoint to connect to. * A browser websocket endpoint to connect to.

View File

@ -1,6 +1,7 @@
{ {
"BrowserTypeLaunchOptions": "LaunchOptions", "BrowserTypeLaunchOptions": "LaunchOptions",
"BrowserTypeConnectParams": "ConnectOptions", "BrowserTypeConnectParams": "ConnectOptions",
"BrowserTypeConnectOverCDPParams": "ConnectOverCDPOptions",
"BrowserContextCookies": "Cookie", "BrowserContextCookies": "Cookie",
"BrowserNewContextOptions": "BrowserContextOptions", "BrowserNewContextOptions": "BrowserContextOptions",
"BrowserNewContextOptionsViewport": "ViewportSize", "BrowserNewContextOptionsViewport": "ViewportSize",

View File

@ -141,7 +141,12 @@ export interface ElementHandle<T=Node> extends JSHandle<T> {
} }
export interface BrowserType<Unused = {}> { export interface BrowserType<Unused = {}> {
connectOverCDP(options: ConnectOverCDPOptions): Promise<Browser>;
/**
* Option `wsEndpoint` is deprecated. Instead use `endpointURL`.
* @deprecated
*/
connectOverCDP(options: ConnectOptions): Promise<Browser>;
} }
export interface CDPSession { export interface CDPSession {