feat: support extra http headers in browserType.connect() (#6301)

This commit is contained in:
Yury Semikhatsky 2021-04-23 14:52:27 -07:00 committed by GitHub
parent 83758fa48c
commit fd31ea8b0d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 110 additions and 11 deletions

View File

@ -75,6 +75,7 @@ This methods attaches Playwright to an existing browser instance.
* langs: js
- `params` <[Object]>
- `wsEndpoint` <[string]> A browser websocket endpoint to connect to.
- `headers` <[Object]<[string], [string]>> Additional HTTP headers to be sent with web socket connect request. Optional.
- `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.
- `logger` <[Logger]> Logger sink for Playwright logging. Optional.
@ -87,6 +88,12 @@ This methods attaches Playwright to an existing browser instance.
A browser websocket endpoint to connect to.
### param: BrowserType.connect.headers
* langs: java, python
- `headers` <[string]>
Additional HTTP headers to be sent with web socket connect request. Optional.
### option: BrowserType.connect.slowMo
* langs: java, python
- `slowMo` <[float]>
@ -117,6 +124,7 @@ Connecting over the Chrome DevTools Protocol is only supported for Chromium-base
* langs: js
- `params` <[Object]>
- `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`.
- `headers` <[Object]<[string], [string]>> Additional HTTP headers to be sent with connect request. Optional.
- `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.
- `logger` <[Logger]> Logger sink for Playwright logging. Optional.
@ -129,6 +137,12 @@ Connecting over the Chrome DevTools Protocol is only supported for Chromium-base
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`.
### option: BrowserType.connectOverCDP.headers
* langs: java, python
- `headers` <[Object]<[string], [string]>>
Additional HTTP headers to be sent with connect request. Optional.
### option: BrowserType.connectOverCDP.slowMo
* langs: java, python
- `slowMo` <[float]>

View File

@ -26,7 +26,7 @@ import { Events } from './events';
import { TimeoutSettings } from '../utils/timeoutSettings';
import { ChildProcess } from 'child_process';
import { envObjectToArray } from './clientHelper';
import { assert, makeWaitForNextTask } from '../utils/utils';
import { assert, headersObjectToArray, makeWaitForNextTask } from '../utils/utils';
import { kBrowserClosedError } from '../utils/errors';
import * as api from '../../types/types';
import type { Playwright } from './playwright';
@ -117,6 +117,7 @@ export class BrowserType extends ChannelOwner<channels.BrowserTypeChannel, chann
perMessageDeflate: false,
maxPayload: 256 * 1024 * 1024, // 256Mb,
handshakeTimeout: this._timeoutSettings.timeout(params),
headers: params.headers,
});
// The 'ws' module in node sometimes sends us multiple messages in a single task.
@ -133,9 +134,14 @@ export class BrowserType extends ChannelOwner<channels.BrowserTypeChannel, chann
ws.send(JSON.stringify(message));
};
ws.addEventListener('message', event => {
waitForNextTask(() => connection.dispatch(JSON.parse(event.data)));
waitForNextTask(() => {
try {
connection.dispatch(JSON.parse(event.data));
} catch (e) {
ws.close();
}
});
});
return await new Promise<Browser>(async (fulfill, reject) => {
if ((params as any).__testHookBeforeCreateBrowser) {
try {
@ -193,9 +199,11 @@ export class BrowserType extends ChannelOwner<channels.BrowserTypeChannel, chann
throw new Error('Connecting over CDP is only supported in Chromium.');
const logger = params.logger;
return this._wrapApiCall('browserType.connectOverCDP', async (channel: channels.BrowserTypeChannel) => {
const headers = params.headers ? headersObjectToArray(params.headers) : undefined;
const result = await channel.connectOverCDP({
sdkLanguage: 'javascript',
endpointURL: 'endpointURL' in params ? params.endpointURL : params.wsEndpoint,
headers,
slowMo: params.slowMo,
timeout: params.timeout
});

View File

@ -70,6 +70,7 @@ export type LaunchPersistentContextOptions = Omit<LaunchOptionsBase & BrowserCon
export type ConnectOptions = {
wsEndpoint: string,
headers?: { [key: string]: string; };
slowMo?: number,
timeout?: number,
logger?: Logger,

View File

@ -401,10 +401,12 @@ export type BrowserTypeLaunchPersistentContextResult = {
export type BrowserTypeConnectOverCDPParams = {
sdkLanguage: string,
endpointURL: string,
headers?: NameValue[],
slowMo?: number,
timeout?: number,
};
export type BrowserTypeConnectOverCDPOptions = {
headers?: NameValue[],
slowMo?: number,
timeout?: number,
};

View File

@ -415,6 +415,9 @@ BrowserType:
parameters:
sdkLanguage: string
endpointURL: string
headers:
type: array?
items: NameValue
slowMo: number?
timeout: number?
returns:

View File

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

View File

@ -25,7 +25,7 @@ import { ConnectionTransport, ProtocolRequest, WebSocketTransport } from '../tra
import { CRDevTools } from './crDevTools';
import { BrowserOptions, BrowserProcess, PlaywrightOptions } from '../browser';
import * as types from '../types';
import { debugMode } from '../../utils/utils';
import { debugMode, headersArrayToObject } from '../../utils/utils';
import { RecentLogsCollector } from '../../utils/debugLogger';
import { ProgressController } from '../progress';
import { TimeoutSettings } from '../../utils/timeoutSettings';
@ -50,12 +50,15 @@ export class Chromium extends BrowserType {
return super.executablePath(channel);
}
async connectOverCDP(metadata: CallMetadata, endpointURL: string, options: { slowMo?: number, sdkLanguage: string }, timeout?: number) {
async connectOverCDP(metadata: CallMetadata, endpointURL: string, options: { slowMo?: number, sdkLanguage: string, headers?: types.HeadersArray }, timeout?: number) {
const controller = new ProgressController(metadata, this);
controller.setLogName('browser');
const browserLogsCollector = new RecentLogsCollector();
return controller.run(async progress => {
const chromeTransport = await WebSocketTransport.connect(progress, await urlToWSEndpoint(endpointURL));
let headersMap: { [key: string]: string; } | undefined;
if (options.headers)
headersMap = headersArrayToObject(options.headers, false);
const chromeTransport = await WebSocketTransport.connect(progress, await urlToWSEndpoint(endpointURL), headersMap);
const browserProcess: BrowserProcess = {
close: async () => {
await chromeTransport.closeAndWait();

View File

@ -52,9 +52,9 @@ export class WebSocketTransport implements ConnectionTransport {
onclose?: () => void;
readonly wsEndpoint: string;
static async connect(progress: Progress, url: string): Promise<WebSocketTransport> {
static async connect(progress: Progress, url: string, headers?: { [key: string]: string; }): Promise<WebSocketTransport> {
progress.log(`<ws connecting> ${url}`);
const transport = new WebSocketTransport(progress, url);
const transport = new WebSocketTransport(progress, url, headers);
let success = false;
progress.cleanupWhenAborted(async () => {
if (!success)
@ -75,12 +75,13 @@ export class WebSocketTransport implements ConnectionTransport {
return transport;
}
constructor(progress: Progress, url: string) {
constructor(progress: Progress, url: string, headers?: { [key: string]: string; }) {
this.wsEndpoint = url;
this._ws = new WebSocket(url, [], {
perMessageDeflate: false,
maxPayload: 256 * 1024 * 1024, // 256Mb,
handshakeTimeout: progress.timeUntilDeadline(),
headers
});
this._progress = progress;
// The 'ws' module in node sometimes sends us multiple messages in a single task.
@ -91,8 +92,12 @@ export class WebSocketTransport implements ConnectionTransport {
this._ws.addEventListener('message', event => {
messageWrap(() => {
if (this.onmessage)
this.onmessage.call(null, JSON.parse(event.data));
try {
if (this.onmessage)
this.onmessage.call(null, JSON.parse(event.data));
} catch (e) {
this._ws.close();
}
});
});

View File

@ -60,6 +60,21 @@ test('should be able to connect two browsers at the same time', async ({browserT
await browser2.close();
});
test('should send extra headers with connect request', async ({browserType, startRemoteServer, server}) => {
const [request] = await Promise.all([
server.waitForWebSocketConnectionRequest(),
browserType.connect({
wsEndpoint: `ws://localhost:${server.PORT}/ws`,
headers: {
'User-Agent': 'Playwright',
'foo': 'bar',
}
}).catch(() => {})
]);
expect(request.headers['user-agent']).toBe('Playwright');
expect(request.headers['foo']).toBe('bar');
});
test('disconnected event should be emitted when browser is closed or server is closed', async ({browserType, startRemoteServer}) => {
const remoteServer = await startRemoteServer();

View File

@ -219,4 +219,34 @@ playwrightTest.describe('chromium', () => {
await browserServer.close();
}
});
playwrightTest('should send extra headers with connect request', async ({browserType, browserOptions, server}, testInfo) => {
{
const [request] = await Promise.all([
server.waitForWebSocketConnectionRequest(),
browserType.connectOverCDP({
wsEndpoint: `ws://localhost:${server.PORT}/ws`,
headers: {
'User-Agent': 'Playwright',
'foo': 'bar',
}
}).catch(() => {})
]);
expect(request.headers['user-agent']).toBe('Playwright');
expect(request.headers['foo']).toBe('bar');
}
{
const [request] = await Promise.all([
server.waitForWebSocketConnectionRequest(),
browserType.connectOverCDP({
endpointURL: `ws://localhost:${server.PORT}/ws`,
headers: {
'User-Agent': 'Playwright',
'foo': 'bar',
}
}).catch(() => {})
]);
expect(request.headers['user-agent']).toBe('Playwright');
expect(request.headers['foo']).toBe('bar');
}
});
});

10
types/types.d.ts vendored
View File

@ -10877,6 +10877,11 @@ export interface ConnectOverCDPOptions {
*/
endpointURL: string;
/**
* Additional HTTP headers to be sent with connect request. Optional.
*/
headers?: { [key: string]: string; };
/**
* Slows down Playwright operations by the specified amount of milliseconds. Useful so that you can see what is going on.
* Defaults to 0.
@ -10901,6 +10906,11 @@ export interface ConnectOptions {
*/
wsEndpoint: string;
/**
* Additional HTTP headers to be sent with web socket connect request. Optional.
*/
headers?: { [key: string]: string; };
/**
* Slows down Playwright operations by the specified amount of milliseconds. Useful so that you can see what is going on.
* Defaults to 0.

View File

@ -28,6 +28,7 @@ export class TestServer {
setRoute(path: string, handler: (message: IncomingMessage & {postBody: Buffer}, response: ServerResponse) => void);
setRedirect(from: string, to: string);
waitForRequest(path: string): Promise<IncomingMessage & {postBody: Buffer}>;
waitForWebSocketConnectionRequest(): Promise<IncomingMessage>;
reset();
serveFile(request: IncomingMessage, response: ServerResponse);
serveFile(request: IncomingMessage, response: ServerResponse, filePath: string);

View File

@ -293,6 +293,12 @@ class TestServer {
}
}
waitForWebSocketConnectionRequest() {
return new Promise(fullfil => {
this._wsServer.once('connection', (ws, req) => fullfil(req));
});
}
_onWebSocketConnection(ws) {
ws.send('incoming');
}