mirror of
https://github.com/microsoft/playwright.git
synced 2025-06-26 21:40:17 +00:00
feat: support extra http headers in browserType.connect() (#6301)
This commit is contained in:
parent
83758fa48c
commit
fd31ea8b0d
@ -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]>
|
||||
|
||||
@ -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
|
||||
});
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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,
|
||||
};
|
||||
|
||||
@ -415,6 +415,9 @@ BrowserType:
|
||||
parameters:
|
||||
sdkLanguage: string
|
||||
endpointURL: string
|
||||
headers:
|
||||
type: array?
|
||||
items: NameValue
|
||||
slowMo: number?
|
||||
timeout: number?
|
||||
returns:
|
||||
|
||||
@ -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),
|
||||
});
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@ -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();
|
||||
|
||||
|
||||
@ -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
10
types/types.d.ts
vendored
@ -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.
|
||||
|
||||
1
utils/testserver/index.d.ts
vendored
1
utils/testserver/index.d.ts
vendored
@ -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);
|
||||
|
||||
@ -293,6 +293,12 @@ class TestServer {
|
||||
}
|
||||
}
|
||||
|
||||
waitForWebSocketConnectionRequest() {
|
||||
return new Promise(fullfil => {
|
||||
this._wsServer.once('connection', (ws, req) => fullfil(req));
|
||||
});
|
||||
}
|
||||
|
||||
_onWebSocketConnection(ws) {
|
||||
ws.send('incoming');
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user