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
|
* langs: js
|
||||||
- `params` <[Object]>
|
- `params` <[Object]>
|
||||||
- `wsEndpoint` <[string]> A browser websocket endpoint to connect to.
|
- `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
|
- `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.
|
||||||
@ -87,6 +88,12 @@ This methods attaches Playwright to an existing browser instance.
|
|||||||
|
|
||||||
A browser websocket endpoint to connect to.
|
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
|
### option: BrowserType.connect.slowMo
|
||||||
* langs: java, python
|
* langs: java, python
|
||||||
- `slowMo` <[float]>
|
- `slowMo` <[float]>
|
||||||
@ -117,6 +124,7 @@ Connecting over the Chrome DevTools Protocol is only supported for Chromium-base
|
|||||||
* langs: js
|
* langs: js
|
||||||
- `params` <[Object]>
|
- `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`.
|
- `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
|
- `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.
|
||||||
@ -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`.
|
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
|
### option: BrowserType.connectOverCDP.slowMo
|
||||||
* langs: java, python
|
* langs: java, python
|
||||||
- `slowMo` <[float]>
|
- `slowMo` <[float]>
|
||||||
|
|||||||
@ -26,7 +26,7 @@ import { Events } from './events';
|
|||||||
import { TimeoutSettings } from '../utils/timeoutSettings';
|
import { TimeoutSettings } from '../utils/timeoutSettings';
|
||||||
import { ChildProcess } from 'child_process';
|
import { ChildProcess } from 'child_process';
|
||||||
import { envObjectToArray } from './clientHelper';
|
import { envObjectToArray } from './clientHelper';
|
||||||
import { assert, makeWaitForNextTask } from '../utils/utils';
|
import { assert, headersObjectToArray, makeWaitForNextTask } from '../utils/utils';
|
||||||
import { kBrowserClosedError } from '../utils/errors';
|
import { kBrowserClosedError } from '../utils/errors';
|
||||||
import * as api from '../../types/types';
|
import * as api from '../../types/types';
|
||||||
import type { Playwright } from './playwright';
|
import type { Playwright } from './playwright';
|
||||||
@ -117,6 +117,7 @@ export class BrowserType extends ChannelOwner<channels.BrowserTypeChannel, chann
|
|||||||
perMessageDeflate: false,
|
perMessageDeflate: false,
|
||||||
maxPayload: 256 * 1024 * 1024, // 256Mb,
|
maxPayload: 256 * 1024 * 1024, // 256Mb,
|
||||||
handshakeTimeout: this._timeoutSettings.timeout(params),
|
handshakeTimeout: this._timeoutSettings.timeout(params),
|
||||||
|
headers: params.headers,
|
||||||
});
|
});
|
||||||
|
|
||||||
// The 'ws' module in node sometimes sends us multiple messages in a single task.
|
// 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.send(JSON.stringify(message));
|
||||||
};
|
};
|
||||||
ws.addEventListener('message', event => {
|
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) => {
|
return await new Promise<Browser>(async (fulfill, reject) => {
|
||||||
if ((params as any).__testHookBeforeCreateBrowser) {
|
if ((params as any).__testHookBeforeCreateBrowser) {
|
||||||
try {
|
try {
|
||||||
@ -193,9 +199,11 @@ export class BrowserType extends ChannelOwner<channels.BrowserTypeChannel, chann
|
|||||||
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 headers = params.headers ? headersObjectToArray(params.headers) : undefined;
|
||||||
const result = await channel.connectOverCDP({
|
const result = await channel.connectOverCDP({
|
||||||
sdkLanguage: 'javascript',
|
sdkLanguage: 'javascript',
|
||||||
endpointURL: 'endpointURL' in params ? params.endpointURL : params.wsEndpoint,
|
endpointURL: 'endpointURL' in params ? params.endpointURL : params.wsEndpoint,
|
||||||
|
headers,
|
||||||
slowMo: params.slowMo,
|
slowMo: params.slowMo,
|
||||||
timeout: params.timeout
|
timeout: params.timeout
|
||||||
});
|
});
|
||||||
|
|||||||
@ -70,6 +70,7 @@ export type LaunchPersistentContextOptions = Omit<LaunchOptionsBase & BrowserCon
|
|||||||
|
|
||||||
export type ConnectOptions = {
|
export type ConnectOptions = {
|
||||||
wsEndpoint: string,
|
wsEndpoint: string,
|
||||||
|
headers?: { [key: string]: string; };
|
||||||
slowMo?: number,
|
slowMo?: number,
|
||||||
timeout?: number,
|
timeout?: number,
|
||||||
logger?: Logger,
|
logger?: Logger,
|
||||||
|
|||||||
@ -401,10 +401,12 @@ export type BrowserTypeLaunchPersistentContextResult = {
|
|||||||
export type BrowserTypeConnectOverCDPParams = {
|
export type BrowserTypeConnectOverCDPParams = {
|
||||||
sdkLanguage: string,
|
sdkLanguage: string,
|
||||||
endpointURL: string,
|
endpointURL: string,
|
||||||
|
headers?: NameValue[],
|
||||||
slowMo?: number,
|
slowMo?: number,
|
||||||
timeout?: number,
|
timeout?: number,
|
||||||
};
|
};
|
||||||
export type BrowserTypeConnectOverCDPOptions = {
|
export type BrowserTypeConnectOverCDPOptions = {
|
||||||
|
headers?: NameValue[],
|
||||||
slowMo?: number,
|
slowMo?: number,
|
||||||
timeout?: number,
|
timeout?: number,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -415,6 +415,9 @@ BrowserType:
|
|||||||
parameters:
|
parameters:
|
||||||
sdkLanguage: string
|
sdkLanguage: string
|
||||||
endpointURL: string
|
endpointURL: string
|
||||||
|
headers:
|
||||||
|
type: array?
|
||||||
|
items: NameValue
|
||||||
slowMo: number?
|
slowMo: number?
|
||||||
timeout: number?
|
timeout: number?
|
||||||
returns:
|
returns:
|
||||||
|
|||||||
@ -249,6 +249,7 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
|
|||||||
scheme.BrowserTypeConnectOverCDPParams = tObject({
|
scheme.BrowserTypeConnectOverCDPParams = tObject({
|
||||||
sdkLanguage: tString,
|
sdkLanguage: tString,
|
||||||
endpointURL: tString,
|
endpointURL: tString,
|
||||||
|
headers: tOptional(tArray(tType('NameValue'))),
|
||||||
slowMo: tOptional(tNumber),
|
slowMo: tOptional(tNumber),
|
||||||
timeout: tOptional(tNumber),
|
timeout: tOptional(tNumber),
|
||||||
});
|
});
|
||||||
|
|||||||
@ -25,7 +25,7 @@ import { ConnectionTransport, ProtocolRequest, WebSocketTransport } from '../tra
|
|||||||
import { CRDevTools } from './crDevTools';
|
import { CRDevTools } from './crDevTools';
|
||||||
import { BrowserOptions, BrowserProcess, PlaywrightOptions } from '../browser';
|
import { BrowserOptions, BrowserProcess, PlaywrightOptions } from '../browser';
|
||||||
import * as types from '../types';
|
import * as types from '../types';
|
||||||
import { debugMode } from '../../utils/utils';
|
import { debugMode, headersArrayToObject } from '../../utils/utils';
|
||||||
import { RecentLogsCollector } from '../../utils/debugLogger';
|
import { RecentLogsCollector } from '../../utils/debugLogger';
|
||||||
import { ProgressController } from '../progress';
|
import { ProgressController } from '../progress';
|
||||||
import { TimeoutSettings } from '../../utils/timeoutSettings';
|
import { TimeoutSettings } from '../../utils/timeoutSettings';
|
||||||
@ -50,12 +50,15 @@ export class Chromium extends BrowserType {
|
|||||||
return super.executablePath(channel);
|
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);
|
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, 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 = {
|
const browserProcess: BrowserProcess = {
|
||||||
close: async () => {
|
close: async () => {
|
||||||
await chromeTransport.closeAndWait();
|
await chromeTransport.closeAndWait();
|
||||||
|
|||||||
@ -52,9 +52,9 @@ export class WebSocketTransport implements ConnectionTransport {
|
|||||||
onclose?: () => void;
|
onclose?: () => void;
|
||||||
readonly wsEndpoint: string;
|
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}`);
|
progress.log(`<ws connecting> ${url}`);
|
||||||
const transport = new WebSocketTransport(progress, url);
|
const transport = new WebSocketTransport(progress, url, headers);
|
||||||
let success = false;
|
let success = false;
|
||||||
progress.cleanupWhenAborted(async () => {
|
progress.cleanupWhenAborted(async () => {
|
||||||
if (!success)
|
if (!success)
|
||||||
@ -75,12 +75,13 @@ export class WebSocketTransport implements ConnectionTransport {
|
|||||||
return transport;
|
return transport;
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(progress: Progress, url: string) {
|
constructor(progress: Progress, url: string, headers?: { [key: string]: string; }) {
|
||||||
this.wsEndpoint = url;
|
this.wsEndpoint = url;
|
||||||
this._ws = new WebSocket(url, [], {
|
this._ws = new WebSocket(url, [], {
|
||||||
perMessageDeflate: false,
|
perMessageDeflate: false,
|
||||||
maxPayload: 256 * 1024 * 1024, // 256Mb,
|
maxPayload: 256 * 1024 * 1024, // 256Mb,
|
||||||
handshakeTimeout: progress.timeUntilDeadline(),
|
handshakeTimeout: progress.timeUntilDeadline(),
|
||||||
|
headers
|
||||||
});
|
});
|
||||||
this._progress = progress;
|
this._progress = progress;
|
||||||
// The 'ws' module in node sometimes sends us multiple messages in a single task.
|
// 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 => {
|
this._ws.addEventListener('message', event => {
|
||||||
messageWrap(() => {
|
messageWrap(() => {
|
||||||
|
try {
|
||||||
if (this.onmessage)
|
if (this.onmessage)
|
||||||
this.onmessage.call(null, JSON.parse(event.data));
|
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();
|
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}) => {
|
test('disconnected event should be emitted when browser is closed or server is closed', async ({browserType, startRemoteServer}) => {
|
||||||
const remoteServer = await startRemoteServer();
|
const remoteServer = await startRemoteServer();
|
||||||
|
|
||||||
|
|||||||
@ -219,4 +219,34 @@ playwrightTest.describe('chromium', () => {
|
|||||||
await browserServer.close();
|
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;
|
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.
|
* Slows down Playwright operations by the specified amount of milliseconds. Useful so that you can see what is going on.
|
||||||
* Defaults to 0.
|
* Defaults to 0.
|
||||||
@ -10901,6 +10906,11 @@ export interface ConnectOptions {
|
|||||||
*/
|
*/
|
||||||
wsEndpoint: string;
|
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.
|
* Slows down Playwright operations by the specified amount of milliseconds. Useful so that you can see what is going on.
|
||||||
* Defaults to 0.
|
* 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);
|
setRoute(path: string, handler: (message: IncomingMessage & {postBody: Buffer}, response: ServerResponse) => void);
|
||||||
setRedirect(from: string, to: string);
|
setRedirect(from: string, to: string);
|
||||||
waitForRequest(path: string): Promise<IncomingMessage & {postBody: Buffer}>;
|
waitForRequest(path: string): Promise<IncomingMessage & {postBody: Buffer}>;
|
||||||
|
waitForWebSocketConnectionRequest(): Promise<IncomingMessage>;
|
||||||
reset();
|
reset();
|
||||||
serveFile(request: IncomingMessage, response: ServerResponse);
|
serveFile(request: IncomingMessage, response: ServerResponse);
|
||||||
serveFile(request: IncomingMessage, response: ServerResponse, filePath: string);
|
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) {
|
_onWebSocketConnection(ws) {
|
||||||
ws.send('incoming');
|
ws.send('incoming');
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user