feat(port-forwarding): add playwrightclient support (#6786)
This commit is contained in:
parent
33c2f6c31e
commit
e91e49e533
|
|
@ -34,9 +34,6 @@ import { BrowserContext } from './server/browserContext';
|
||||||
import { CRBrowser } from './server/chromium/crBrowser';
|
import { CRBrowser } from './server/chromium/crBrowser';
|
||||||
import { CDPSessionDispatcher } from './dispatchers/cdpSessionDispatcher';
|
import { CDPSessionDispatcher } from './dispatchers/cdpSessionDispatcher';
|
||||||
import { PageDispatcher } from './dispatchers/pageDispatcher';
|
import { PageDispatcher } from './dispatchers/pageDispatcher';
|
||||||
import { BrowserServerPortForwardingServer } from './server/socksSocket';
|
|
||||||
import { SocksSocketDispatcher } from './dispatchers/socksSocketDispatcher';
|
|
||||||
import { SocksInterceptedSocketHandler } from './server/socksServer';
|
|
||||||
|
|
||||||
export class BrowserServerLauncherImpl implements BrowserServerLauncher {
|
export class BrowserServerLauncherImpl implements BrowserServerLauncher {
|
||||||
private _browserName: 'chromium' | 'firefox' | 'webkit';
|
private _browserName: 'chromium' | 'firefox' | 'webkit';
|
||||||
|
|
@ -47,14 +44,14 @@ export class BrowserServerLauncherImpl implements BrowserServerLauncher {
|
||||||
|
|
||||||
async launchServer(options: LaunchServerOptions = {}): Promise<BrowserServer> {
|
async launchServer(options: LaunchServerOptions = {}): Promise<BrowserServer> {
|
||||||
const playwright = createPlaywright();
|
const playwright = createPlaywright();
|
||||||
const portForwardingServer = new BrowserServerPortForwardingServer(playwright, !!options._acceptForwardedPorts);
|
if (options._acceptForwardedPorts)
|
||||||
|
await playwright._enablePortForwarding();
|
||||||
// 1. Pre-launch the browser
|
// 1. Pre-launch the browser
|
||||||
const browser = await playwright[this._browserName].launch(internalCallMetadata(), {
|
const browser = await playwright[this._browserName].launch(internalCallMetadata(), {
|
||||||
...options,
|
...options,
|
||||||
ignoreDefaultArgs: Array.isArray(options.ignoreDefaultArgs) ? options.ignoreDefaultArgs : undefined,
|
ignoreDefaultArgs: Array.isArray(options.ignoreDefaultArgs) ? options.ignoreDefaultArgs : undefined,
|
||||||
ignoreAllDefaultArgs: !!options.ignoreDefaultArgs && !Array.isArray(options.ignoreDefaultArgs),
|
ignoreAllDefaultArgs: !!options.ignoreDefaultArgs && !Array.isArray(options.ignoreDefaultArgs),
|
||||||
env: options.env ? envObjectToArray(options.env) : undefined,
|
env: options.env ? envObjectToArray(options.env) : undefined,
|
||||||
...portForwardingServer.browserLaunchOptions(),
|
|
||||||
}, toProtocolLogger(options.logger));
|
}, toProtocolLogger(options.logger));
|
||||||
|
|
||||||
// 2. Start the server
|
// 2. Start the server
|
||||||
|
|
@ -62,9 +59,9 @@ export class BrowserServerLauncherImpl implements BrowserServerLauncher {
|
||||||
path: '/' + createGuid(),
|
path: '/' + createGuid(),
|
||||||
allowMultipleClients: options._acceptForwardedPorts ? false : true,
|
allowMultipleClients: options._acceptForwardedPorts ? false : true,
|
||||||
onClose: () => {
|
onClose: () => {
|
||||||
portForwardingServer.stop();
|
playwright._disablePortForwarding();
|
||||||
},
|
},
|
||||||
onConnect: this._onConnect.bind(this, playwright, browser, portForwardingServer),
|
onConnect: this._onConnect.bind(this, playwright, browser),
|
||||||
};
|
};
|
||||||
const server = new PlaywrightServer(delegate);
|
const server = new PlaywrightServer(delegate);
|
||||||
const wsEndpoint = await server.listen(options.port);
|
const wsEndpoint = await server.listen(options.port);
|
||||||
|
|
@ -83,7 +80,7 @@ export class BrowserServerLauncherImpl implements BrowserServerLauncher {
|
||||||
return browserServer;
|
return browserServer;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _onConnect(playwright: Playwright, browser: Browser, portForwardingServer: BrowserServerPortForwardingServer, scope: DispatcherScope, forceDisconnect: () => void) {
|
private async _onConnect(playwright: Playwright, browser: Browser, scope: DispatcherScope, forceDisconnect: () => void) {
|
||||||
const selectors = new Selectors();
|
const selectors = new Selectors();
|
||||||
const selectorsDispatcher = new SelectorsDispatcher(scope, selectors);
|
const selectorsDispatcher = new SelectorsDispatcher(scope, selectors);
|
||||||
const browserDispatcher = new ConnectedBrowserDispatcher(scope, browser, selectors);
|
const browserDispatcher = new ConnectedBrowserDispatcher(scope, browser, selectors);
|
||||||
|
|
@ -91,16 +88,8 @@ export class BrowserServerLauncherImpl implements BrowserServerLauncher {
|
||||||
// Underlying browser did close for some reason - force disconnect the client.
|
// Underlying browser did close for some reason - force disconnect the client.
|
||||||
forceDisconnect();
|
forceDisconnect();
|
||||||
});
|
});
|
||||||
const playwrightDispatcher = new PlaywrightDispatcher(scope, playwright, selectorsDispatcher, browserDispatcher, (ports: number[]) => {
|
new PlaywrightDispatcher(scope, playwright, selectorsDispatcher, browserDispatcher);
|
||||||
portForwardingServer.enablePortForwarding(ports);
|
|
||||||
});
|
|
||||||
const incomingSocksSocketHandler = (socket: SocksInterceptedSocketHandler) => {
|
|
||||||
playwrightDispatcher._dispatchEvent('incomingSocksSocket', { socket: new SocksSocketDispatcher(playwrightDispatcher, socket) });
|
|
||||||
};
|
|
||||||
portForwardingServer.on('incomingSocksSocket', incomingSocksSocketHandler);
|
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
portForwardingServer.off('incomingSocksSocket', incomingSocksSocketHandler);
|
|
||||||
// Cleanup contexts upon disconnect.
|
// Cleanup contexts upon disconnect.
|
||||||
browserDispatcher.cleanupContexts().catch(e => {});
|
browserDispatcher.cleanupContexts().catch(e => {});
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,7 @@ export function runDriver() {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function runServer(port: number | undefined) {
|
export async function runServer(port: number | undefined) {
|
||||||
const wsEndpoint = await PlaywrightServer.startDefault(port);
|
const wsEndpoint = await PlaywrightServer.startDefault({port});
|
||||||
console.log('Listening on ' + wsEndpoint); // eslint-disable-line no-console
|
console.log('Listening on ' + wsEndpoint); // eslint-disable-line no-console
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -192,10 +192,7 @@ export class BrowserType extends ChannelOwner<channels.BrowserTypeChannel, chann
|
||||||
});
|
});
|
||||||
if (params._forwardPorts) {
|
if (params._forwardPorts) {
|
||||||
try {
|
try {
|
||||||
await playwright._channel.enablePortForwarding({
|
await playwright._enablePortForwarding(params._forwardPorts);
|
||||||
ports: params._forwardPorts,
|
|
||||||
});
|
|
||||||
playwright._forwardPorts = params._forwardPorts;
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
reject(err);
|
reject(err);
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,11 @@ export class Playwright extends ChannelOwner<channels.PlaywrightChannel, channel
|
||||||
this._channel.on('incomingSocksSocket', ({socket}) => SocksSocket.from(socket));
|
this._channel.on('incomingSocksSocket', ({socket}) => SocksSocket.from(socket));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async _enablePortForwarding(ports: number[]) {
|
||||||
|
this._forwardPorts = ports;
|
||||||
|
await this._channel.setForwardedPorts({ports});
|
||||||
|
}
|
||||||
|
|
||||||
_cleanup() {
|
_cleanup() {
|
||||||
this.selectors._removeChannel(this._selectorsOwner);
|
this.selectors._removeChannel(this._selectorsOwner);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,11 +22,11 @@ import { Dispatcher, DispatcherScope } from './dispatcher';
|
||||||
import { ElectronDispatcher } from './electronDispatcher';
|
import { ElectronDispatcher } from './electronDispatcher';
|
||||||
import { SelectorsDispatcher } from './selectorsDispatcher';
|
import { SelectorsDispatcher } from './selectorsDispatcher';
|
||||||
import * as types from '../server/types';
|
import * as types from '../server/types';
|
||||||
import { assert } from '../utils/utils';
|
import { SocksSocketDispatcher } from './socksSocketDispatcher';
|
||||||
|
import { SocksInterceptedSocketHandler } from '../server/socksServer';
|
||||||
|
|
||||||
export class PlaywrightDispatcher extends Dispatcher<Playwright, channels.PlaywrightInitializer> implements channels.PlaywrightChannel {
|
export class PlaywrightDispatcher extends Dispatcher<Playwright, channels.PlaywrightInitializer> implements channels.PlaywrightChannel {
|
||||||
private _portForwardingCallback: ((ports: number[]) => void) | undefined;
|
constructor(scope: DispatcherScope, playwright: Playwright, customSelectors?: channels.SelectorsChannel, preLaunchedBrowser?: channels.BrowserChannel) {
|
||||||
constructor(scope: DispatcherScope, playwright: Playwright, customSelectors?: channels.SelectorsChannel, preLaunchedBrowser?: channels.BrowserChannel, portForwardingCallback?: (ports: number[]) => void) {
|
|
||||||
const descriptors = require('../server/deviceDescriptors') as types.Devices;
|
const descriptors = require('../server/deviceDescriptors') as types.Devices;
|
||||||
const deviceDescriptors = Object.entries(descriptors)
|
const deviceDescriptors = Object.entries(descriptors)
|
||||||
.map(([name, descriptor]) => ({ name, descriptor }));
|
.map(([name, descriptor]) => ({ name, descriptor }));
|
||||||
|
|
@ -40,11 +40,12 @@ export class PlaywrightDispatcher extends Dispatcher<Playwright, channels.Playwr
|
||||||
selectors: customSelectors || new SelectorsDispatcher(scope, playwright.selectors),
|
selectors: customSelectors || new SelectorsDispatcher(scope, playwright.selectors),
|
||||||
preLaunchedBrowser,
|
preLaunchedBrowser,
|
||||||
}, false);
|
}, false);
|
||||||
this._portForwardingCallback = portForwardingCallback;
|
this._object.on('incomingSocksSocket', (socket: SocksInterceptedSocketHandler) => {
|
||||||
|
this._dispatchEvent('incomingSocksSocket', { socket: new SocksSocketDispatcher(this, socket) });
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async enablePortForwarding(params: channels.PlaywrightEnablePortForwardingParams): Promise<void> {
|
async setForwardedPorts(params: channels.PlaywrightSetForwardedPortsParams): Promise<void> {
|
||||||
assert(this._portForwardingCallback, 'Port forwarding is only supported when using connect()');
|
this._object._setForwardedPorts(params.ports);
|
||||||
this._portForwardingCallback(params.ports);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -180,18 +180,18 @@ export type PlaywrightInitializer = {
|
||||||
};
|
};
|
||||||
export interface PlaywrightChannel extends Channel {
|
export interface PlaywrightChannel extends Channel {
|
||||||
on(event: 'incomingSocksSocket', callback: (params: PlaywrightIncomingSocksSocketEvent) => void): this;
|
on(event: 'incomingSocksSocket', callback: (params: PlaywrightIncomingSocksSocketEvent) => void): this;
|
||||||
enablePortForwarding(params: PlaywrightEnablePortForwardingParams, metadata?: Metadata): Promise<PlaywrightEnablePortForwardingResult>;
|
setForwardedPorts(params: PlaywrightSetForwardedPortsParams, metadata?: Metadata): Promise<PlaywrightSetForwardedPortsResult>;
|
||||||
}
|
}
|
||||||
export type PlaywrightIncomingSocksSocketEvent = {
|
export type PlaywrightIncomingSocksSocketEvent = {
|
||||||
socket: SocksSocketChannel,
|
socket: SocksSocketChannel,
|
||||||
};
|
};
|
||||||
export type PlaywrightEnablePortForwardingParams = {
|
export type PlaywrightSetForwardedPortsParams = {
|
||||||
ports: number[],
|
ports: number[],
|
||||||
};
|
};
|
||||||
export type PlaywrightEnablePortForwardingOptions = {
|
export type PlaywrightSetForwardedPortsOptions = {
|
||||||
|
|
||||||
};
|
};
|
||||||
export type PlaywrightEnablePortForwardingResult = void;
|
export type PlaywrightSetForwardedPortsResult = void;
|
||||||
|
|
||||||
// ----------- Selectors -----------
|
// ----------- Selectors -----------
|
||||||
export type SelectorsInitializer = {};
|
export type SelectorsInitializer = {};
|
||||||
|
|
|
||||||
|
|
@ -368,7 +368,7 @@ Playwright:
|
||||||
|
|
||||||
commands:
|
commands:
|
||||||
|
|
||||||
enablePortForwarding:
|
setForwardedPorts:
|
||||||
parameters:
|
parameters:
|
||||||
ports:
|
ports:
|
||||||
type: array
|
type: array
|
||||||
|
|
|
||||||
|
|
@ -147,7 +147,7 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
|
||||||
})),
|
})),
|
||||||
value: tOptional(tType('SerializedValue')),
|
value: tOptional(tType('SerializedValue')),
|
||||||
});
|
});
|
||||||
scheme.PlaywrightEnablePortForwardingParams = tObject({
|
scheme.PlaywrightSetForwardedPortsParams = tObject({
|
||||||
ports: tArray(tNumber),
|
ports: tArray(tNumber),
|
||||||
});
|
});
|
||||||
scheme.SelectorsRegisterParams = tObject({
|
scheme.SelectorsRegisterParams = tObject({
|
||||||
|
|
|
||||||
|
|
@ -18,24 +18,43 @@ import WebSocket from 'ws';
|
||||||
import { Connection } from '../client/connection';
|
import { Connection } from '../client/connection';
|
||||||
import { Playwright } from '../client/playwright';
|
import { Playwright } from '../client/playwright';
|
||||||
|
|
||||||
|
export type PlaywrightClientConnectOptions = {
|
||||||
|
wsEndpoint: string;
|
||||||
|
forwardPorts?: number[];
|
||||||
|
timeout?: number
|
||||||
|
};
|
||||||
|
|
||||||
export class PlaywrightClient {
|
export class PlaywrightClient {
|
||||||
private _playwright: Playwright;
|
private _playwright: Playwright;
|
||||||
private _ws: WebSocket;
|
private _ws: WebSocket;
|
||||||
private _closePromise: Promise<void>;
|
private _closePromise: Promise<void>;
|
||||||
|
|
||||||
static async connect(wsEndpoint: string): Promise<PlaywrightClient> {
|
static async connect(options: PlaywrightClientConnectOptions): Promise<PlaywrightClient> {
|
||||||
|
const {wsEndpoint, forwardPorts, timeout = 30000} = options;
|
||||||
const connection = new Connection();
|
const connection = new Connection();
|
||||||
const ws = new WebSocket(wsEndpoint);
|
const ws = new WebSocket(wsEndpoint);
|
||||||
connection.onmessage = message => ws.send(JSON.stringify(message));
|
connection.onmessage = message => ws.send(JSON.stringify(message));
|
||||||
ws.on('message', message => connection.dispatch(JSON.parse(message.toString())));
|
ws.on('message', message => connection.dispatch(JSON.parse(message.toString())));
|
||||||
const errorPromise = new Promise((_, reject) => ws.on('error', error => reject(error)));
|
const errorPromise = new Promise((_, reject) => ws.on('error', error => reject(error)));
|
||||||
const closePromise = new Promise((_, reject) => ws.on('close', () => reject(new Error('Connection closed'))));
|
const closePromise = new Promise((_, reject) => ws.on('close', () => reject(new Error('Connection closed'))));
|
||||||
const playwright = await Promise.race([
|
const playwrightClientPromise = new Promise<PlaywrightClient>(async (resolve, reject) => {
|
||||||
connection.waitForObjectWithKnownName('Playwright'),
|
const playwright = await connection.waitForObjectWithKnownName('Playwright') as Playwright;
|
||||||
errorPromise,
|
if (forwardPorts)
|
||||||
closePromise
|
await playwright._enablePortForwarding(forwardPorts).catch(reject);
|
||||||
]);
|
resolve(new PlaywrightClient(playwright, ws));
|
||||||
return new PlaywrightClient(playwright as Playwright, ws);
|
});
|
||||||
|
let timer: NodeJS.Timeout;
|
||||||
|
try {
|
||||||
|
await Promise.race([
|
||||||
|
playwrightClientPromise,
|
||||||
|
errorPromise,
|
||||||
|
closePromise,
|
||||||
|
new Promise((_, reject) => timer = setTimeout(reject, timeout))
|
||||||
|
]);
|
||||||
|
return await playwrightClientPromise;
|
||||||
|
} finally {
|
||||||
|
clearTimeout(timer!);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(playwright: Playwright, ws: WebSocket) {
|
constructor(playwright: Playwright, ws: WebSocket) {
|
||||||
|
|
|
||||||
|
|
@ -28,16 +28,21 @@ const debugLog = debug('pw:server');
|
||||||
export interface PlaywrightServerDelegate {
|
export interface PlaywrightServerDelegate {
|
||||||
path: string;
|
path: string;
|
||||||
allowMultipleClients: boolean;
|
allowMultipleClients: boolean;
|
||||||
onConnect(rootScope: DispatcherScope, forceDisconnect: () => void): () => any;
|
onConnect(rootScope: DispatcherScope, forceDisconnect: () => void): Promise<() => any>;
|
||||||
onClose: () => any;
|
onClose: () => any;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type PlaywrightServerOptions = {
|
||||||
|
port?: number;
|
||||||
|
acceptForwardedPorts?: boolean
|
||||||
|
};
|
||||||
|
|
||||||
export class PlaywrightServer {
|
export class PlaywrightServer {
|
||||||
private _wsServer: ws.Server | undefined;
|
private _wsServer: ws.Server | undefined;
|
||||||
private _clientsCount = 0;
|
private _clientsCount = 0;
|
||||||
private _delegate: PlaywrightServerDelegate;
|
private _delegate: PlaywrightServerDelegate;
|
||||||
|
|
||||||
static async startDefault(port: number = 0): Promise<string> {
|
static async startDefault({port = 0, acceptForwardedPorts }: PlaywrightServerOptions): Promise<string> {
|
||||||
const cleanup = async () => {
|
const cleanup = async () => {
|
||||||
await gracefullyCloseAll().catch(e => {});
|
await gracefullyCloseAll().catch(e => {});
|
||||||
serverSelectors.unregisterAll();
|
serverSelectors.unregisterAll();
|
||||||
|
|
@ -46,9 +51,15 @@ export class PlaywrightServer {
|
||||||
path: '/ws',
|
path: '/ws',
|
||||||
allowMultipleClients: false,
|
allowMultipleClients: false,
|
||||||
onClose: cleanup,
|
onClose: cleanup,
|
||||||
onConnect: (rootScope: DispatcherScope) => {
|
onConnect: async (rootScope: DispatcherScope) => {
|
||||||
new PlaywrightDispatcher(rootScope, createPlaywright());
|
const playwright = createPlaywright();
|
||||||
return cleanup;
|
if (acceptForwardedPorts)
|
||||||
|
await playwright._enablePortForwarding();
|
||||||
|
new PlaywrightDispatcher(rootScope, playwright);
|
||||||
|
return () => {
|
||||||
|
cleanup();
|
||||||
|
playwright._disablePortForwarding();
|
||||||
|
};
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const server = new PlaywrightServer(delegate);
|
const server = new PlaywrightServer(delegate);
|
||||||
|
|
@ -66,12 +77,12 @@ export class PlaywrightServer {
|
||||||
server.on('error', error => debugLog(error));
|
server.on('error', error => debugLog(error));
|
||||||
|
|
||||||
const path = this._delegate.path;
|
const path = this._delegate.path;
|
||||||
const wsEndpoint = await new Promise<string>(resolve => {
|
const wsEndpoint = await new Promise<string>((resolve, reject) => {
|
||||||
server.listen(port, () => {
|
server.listen(port, () => {
|
||||||
const address = server.address();
|
const address = server.address();
|
||||||
const wsEndpoint = typeof address === 'string' ? `${address}${path}` : `ws://127.0.0.1:${address.port}${path}`;
|
const wsEndpoint = typeof address === 'string' ? `${address}${path}` : `ws://127.0.0.1:${address.port}${path}`;
|
||||||
resolve(wsEndpoint);
|
resolve(wsEndpoint);
|
||||||
});
|
}).on('error', reject);
|
||||||
});
|
});
|
||||||
|
|
||||||
debugLog('Listening at ' + wsEndpoint);
|
debugLog('Listening at ' + wsEndpoint);
|
||||||
|
|
@ -96,7 +107,7 @@ export class PlaywrightServer {
|
||||||
|
|
||||||
const forceDisconnect = () => socket.close();
|
const forceDisconnect = () => socket.close();
|
||||||
const scope = connection.rootDispatcher();
|
const scope = connection.rootDispatcher();
|
||||||
const onDisconnect = this._delegate.onConnect(scope, forceDisconnect);
|
let onDisconnect = () => {};
|
||||||
const disconnected = () => {
|
const disconnected = () => {
|
||||||
this._clientsCount--;
|
this._clientsCount--;
|
||||||
// Avoid sending any more messages over closed socket.
|
// Avoid sending any more messages over closed socket.
|
||||||
|
|
@ -111,6 +122,7 @@ export class PlaywrightServer {
|
||||||
debugLog('Client error ' + error);
|
debugLog('Client error ' + error);
|
||||||
disconnected();
|
disconnected();
|
||||||
});
|
});
|
||||||
|
onDisconnect = await this._delegate.onConnect(scope, forceDisconnect);
|
||||||
});
|
});
|
||||||
|
|
||||||
return wsEndpoint;
|
return wsEndpoint;
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ export interface BrowserProcess {
|
||||||
export type PlaywrightOptions = {
|
export type PlaywrightOptions = {
|
||||||
registry: registry.Registry,
|
registry: registry.Registry,
|
||||||
rootSdkObject: SdkObject,
|
rootSdkObject: SdkObject,
|
||||||
|
loopbackProxyOverride?: () => string,
|
||||||
};
|
};
|
||||||
|
|
||||||
export type BrowserOptions = PlaywrightOptions & {
|
export type BrowserOptions = PlaywrightOptions & {
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,7 @@ export abstract class BrowserType extends SdkObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
async launch(metadata: CallMetadata, options: types.LaunchOptions, protocolLogger?: types.ProtocolLogger): Promise<Browser> {
|
async launch(metadata: CallMetadata, options: types.LaunchOptions, protocolLogger?: types.ProtocolLogger): Promise<Browser> {
|
||||||
options = validateLaunchOptions(options);
|
options = validateLaunchOptions(options, this._playwrightOptions.loopbackProxyOverride?.());
|
||||||
const controller = new ProgressController(metadata, this);
|
const controller = new ProgressController(metadata, this);
|
||||||
controller.setLogName('browser');
|
controller.setLogName('browser');
|
||||||
const browser = await controller.run(progress => {
|
const browser = await controller.run(progress => {
|
||||||
|
|
@ -70,7 +70,7 @@ export abstract class BrowserType extends SdkObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
async launchPersistentContext(metadata: CallMetadata, userDataDir: string, options: types.LaunchPersistentOptions): Promise<BrowserContext> {
|
async launchPersistentContext(metadata: CallMetadata, userDataDir: string, options: types.LaunchPersistentOptions): Promise<BrowserContext> {
|
||||||
options = validateLaunchOptions(options);
|
options = validateLaunchOptions(options, this._playwrightOptions.loopbackProxyOverride?.());
|
||||||
const controller = new ProgressController(metadata, this);
|
const controller = new ProgressController(metadata, this);
|
||||||
const persistent: types.BrowserContextOptions = options;
|
const persistent: types.BrowserContextOptions = options;
|
||||||
controller.setLogName('browser');
|
controller.setLogName('browser');
|
||||||
|
|
@ -273,12 +273,14 @@ function copyTestHooks(from: object, to: object) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function validateLaunchOptions<Options extends types.LaunchOptions>(options: Options): Options {
|
function validateLaunchOptions<Options extends types.LaunchOptions>(options: Options, proxyOverride?: string): Options {
|
||||||
const { devtools = false } = options;
|
const { devtools = false } = options;
|
||||||
let { headless = !devtools, downloadsPath } = options;
|
let { headless = !devtools, downloadsPath, proxy } = options;
|
||||||
if (debugMode())
|
if (debugMode())
|
||||||
headless = false;
|
headless = false;
|
||||||
if (downloadsPath && !path.isAbsolute(downloadsPath))
|
if (downloadsPath && !path.isAbsolute(downloadsPath))
|
||||||
downloadsPath = path.join(process.cwd(), downloadsPath);
|
downloadsPath = path.join(process.cwd(), downloadsPath);
|
||||||
return { ...options, devtools, headless, downloadsPath };
|
if (proxyOverride)
|
||||||
|
proxy = { server: proxyOverride };
|
||||||
|
return { ...options, devtools, headless, downloadsPath, proxy };
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,10 +34,6 @@ import { CallMetadata } from '../instrumentation';
|
||||||
import { findChromiumChannel } from './findChromiumChannel';
|
import { findChromiumChannel } from './findChromiumChannel';
|
||||||
import http from 'http';
|
import http from 'http';
|
||||||
|
|
||||||
type LaunchServerOptions = {
|
|
||||||
_acceptForwardedPorts?: boolean,
|
|
||||||
};
|
|
||||||
|
|
||||||
export class Chromium extends BrowserType {
|
export class Chromium extends BrowserType {
|
||||||
private _devtools: CRDevTools | undefined;
|
private _devtools: CRDevTools | undefined;
|
||||||
|
|
||||||
|
|
@ -123,7 +119,7 @@ export class Chromium extends BrowserType {
|
||||||
transport.send(message);
|
transport.send(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
_defaultArgs(options: types.LaunchOptions & LaunchServerOptions, isPersistent: boolean, userDataDir: string): string[] {
|
_defaultArgs(options: types.LaunchOptions, isPersistent: boolean, userDataDir: string): string[] {
|
||||||
const { args = [], proxy } = options;
|
const { args = [], proxy } = options;
|
||||||
const userDataDirArg = args.find(arg => arg.startsWith('--user-data-dir'));
|
const userDataDirArg = args.find(arg => arg.startsWith('--user-data-dir'));
|
||||||
if (userDataDirArg)
|
if (userDataDirArg)
|
||||||
|
|
@ -161,7 +157,7 @@ export class Chromium extends BrowserType {
|
||||||
chromeArguments.push(`--proxy-server=${proxy.server}`);
|
chromeArguments.push(`--proxy-server=${proxy.server}`);
|
||||||
const proxyBypassRules = [];
|
const proxyBypassRules = [];
|
||||||
// https://source.chromium.org/chromium/chromium/src/+/master:net/docs/proxy.md;l=548;drc=71698e610121078e0d1a811054dcf9fd89b49578
|
// https://source.chromium.org/chromium/chromium/src/+/master:net/docs/proxy.md;l=548;drc=71698e610121078e0d1a811054dcf9fd89b49578
|
||||||
if (options._acceptForwardedPorts)
|
if (this._playwrightOptions.loopbackProxyOverride)
|
||||||
proxyBypassRules.push('<-loopback>');
|
proxyBypassRules.push('<-loopback>');
|
||||||
if (proxy.bypass)
|
if (proxy.bypass)
|
||||||
proxyBypassRules.push(...proxy.bypass.split(',').map(t => t.trim()).map(t => t.startsWith('.') ? '*' + t : t));
|
proxyBypassRules.push(...proxy.bypass.split(',').map(t => t.trim()).map(t => t.startsWith('.') ? '*' + t : t));
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,9 @@ import { WebKit } from './webkit/webkit';
|
||||||
import { Registry } from '../utils/registry';
|
import { Registry } from '../utils/registry';
|
||||||
import { CallMetadata, createInstrumentation, SdkObject } from './instrumentation';
|
import { CallMetadata, createInstrumentation, SdkObject } from './instrumentation';
|
||||||
import { debugLogger } from '../utils/debugLogger';
|
import { debugLogger } from '../utils/debugLogger';
|
||||||
|
import { PortForwardingServer } from './socksSocket';
|
||||||
|
import { SocksInterceptedSocketHandler } from './socksServer';
|
||||||
|
import { assert } from '../utils/utils';
|
||||||
|
|
||||||
export class Playwright extends SdkObject {
|
export class Playwright extends SdkObject {
|
||||||
readonly selectors: Selectors;
|
readonly selectors: Selectors;
|
||||||
|
|
@ -35,6 +38,7 @@ export class Playwright extends SdkObject {
|
||||||
readonly firefox: Firefox;
|
readonly firefox: Firefox;
|
||||||
readonly webkit: WebKit;
|
readonly webkit: WebKit;
|
||||||
readonly options: PlaywrightOptions;
|
readonly options: PlaywrightOptions;
|
||||||
|
private _portForwardingServer: PortForwardingServer | undefined;
|
||||||
|
|
||||||
constructor(isInternal: boolean) {
|
constructor(isInternal: boolean) {
|
||||||
super({ attribution: { isInternal }, instrumentation: createInstrumentation() } as any, undefined, 'Playwright');
|
super({ attribution: { isInternal }, instrumentation: createInstrumentation() } as any, undefined, 'Playwright');
|
||||||
|
|
@ -54,6 +58,27 @@ export class Playwright extends SdkObject {
|
||||||
this.android = new Android(new AdbBackend(), this.options);
|
this.android = new Android(new AdbBackend(), this.options);
|
||||||
this.selectors = serverSelectors;
|
this.selectors = serverSelectors;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async _enablePortForwarding() {
|
||||||
|
assert(!this._portForwardingServer);
|
||||||
|
this._portForwardingServer = await PortForwardingServer.create(this);
|
||||||
|
this.options.loopbackProxyOverride = () => this._portForwardingServer!.proxyServer();
|
||||||
|
this._portForwardingServer.on('incomingSocksSocket', (socket: SocksInterceptedSocketHandler) => {
|
||||||
|
this.emit('incomingSocksSocket', socket);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_disablePortForwarding() {
|
||||||
|
if (!this._portForwardingServer)
|
||||||
|
return;
|
||||||
|
this._portForwardingServer.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
_setForwardedPorts(ports: number[]) {
|
||||||
|
if (!this._portForwardingServer)
|
||||||
|
throw new Error(`Port forwarding needs to be enabled when launching the server via BrowserType.launchServer.`);
|
||||||
|
this._portForwardingServer.setForwardedPorts(ports);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createPlaywright(isInternal = false) {
|
export function createPlaywright(isInternal = false) {
|
||||||
|
|
|
||||||
|
|
@ -301,8 +301,8 @@ export class SocksProxyServer {
|
||||||
this.server = net.createServer(this._handleConnection.bind(this, incomingMessageHandler));
|
this.server = net.createServer(this._handleConnection.bind(this, incomingMessageHandler));
|
||||||
}
|
}
|
||||||
|
|
||||||
public listen(port: number, host?: string) {
|
public async listen(port: number, host?: string) {
|
||||||
this.server.listen(port, host);
|
await new Promise(resolve => this.server.listen(port, host, resolve));
|
||||||
}
|
}
|
||||||
|
|
||||||
async _handleConnection(incomingMessageHandler: IncomingProxyRequestHandler, socket: net.Socket) {
|
async _handleConnection(incomingMessageHandler: IncomingProxyRequestHandler, socket: net.Socket) {
|
||||||
|
|
|
||||||
|
|
@ -21,39 +21,31 @@ import { SdkObject } from './instrumentation';
|
||||||
import { debugLogger } from '../utils/debugLogger';
|
import { debugLogger } from '../utils/debugLogger';
|
||||||
import { isLocalIpAddress } from '../utils/utils';
|
import { isLocalIpAddress } from '../utils/utils';
|
||||||
import { SocksProxyServer, SocksConnectionInfo, SocksInterceptedSocketHandler } from './socksServer';
|
import { SocksProxyServer, SocksConnectionInfo, SocksInterceptedSocketHandler } from './socksServer';
|
||||||
import { LaunchOptions } from './types';
|
|
||||||
|
|
||||||
export class BrowserServerPortForwardingServer extends EventEmitter {
|
export class PortForwardingServer extends EventEmitter {
|
||||||
enabled: boolean;
|
|
||||||
private _forwardPorts: number[] = [];
|
private _forwardPorts: number[] = [];
|
||||||
private _parent: SdkObject;
|
private _parent: SdkObject;
|
||||||
private _server: SocksProxyServer;
|
private _server: SocksProxyServer;
|
||||||
constructor(parent: SdkObject, enabled: boolean) {
|
constructor(parent: SdkObject) {
|
||||||
super();
|
super();
|
||||||
this.setMaxListeners(0);
|
this.setMaxListeners(0);
|
||||||
this.enabled = enabled;
|
|
||||||
this._parent = parent;
|
this._parent = parent;
|
||||||
this._server = new SocksProxyServer(this._handler.bind(this));
|
this._server = new SocksProxyServer(this._handler.bind(this));
|
||||||
if (enabled) {
|
}
|
||||||
this._server.listen(0);
|
|
||||||
debugLogger.log('proxy', `initialized server on port ${this._port()})`);
|
static async create(parent: SdkObject) {
|
||||||
}
|
const server = new PortForwardingServer(parent);
|
||||||
|
await server._server.listen(0);
|
||||||
|
debugLogger.log('proxy', `starting server on port ${server._port()})`);
|
||||||
|
return server;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _port(): number {
|
private _port(): number {
|
||||||
if (!this.enabled)
|
|
||||||
return 0;
|
|
||||||
return (this._server.server.address() as net.AddressInfo).port;
|
return (this._server.server.address() as net.AddressInfo).port;
|
||||||
}
|
}
|
||||||
|
|
||||||
public browserLaunchOptions(): LaunchOptions | undefined {
|
public proxyServer() {
|
||||||
if (!this.enabled)
|
return `socks5://127.0.0.1:${this._port()}`;
|
||||||
return;
|
|
||||||
return {
|
|
||||||
proxy: {
|
|
||||||
server: `socks5://127.0.0.1:${this._port()}`
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _handler(info: SocksConnectionInfo, forward: () => void, intercept: (parent: SdkObject) => SocksInterceptedSocketHandler): void {
|
private _handler(info: SocksConnectionInfo, forward: () => void, intercept: (parent: SdkObject) => SocksInterceptedSocketHandler): void {
|
||||||
|
|
@ -67,16 +59,12 @@ export class BrowserServerPortForwardingServer extends EventEmitter {
|
||||||
this.emit('incomingSocksSocket', socket);
|
this.emit('incomingSocksSocket', socket);
|
||||||
}
|
}
|
||||||
|
|
||||||
public enablePortForwarding(ports: number[]): void {
|
public setForwardedPorts(ports: number[]): void {
|
||||||
if (!this.enabled)
|
|
||||||
throw new Error(`Port forwarding needs to be enabled when launching the server via BrowserType.launchServer.`);
|
|
||||||
debugLogger.log('proxy', `enable port forwarding on ports: ${ports}`);
|
debugLogger.log('proxy', `enable port forwarding on ports: ${ports}`);
|
||||||
this._forwardPorts = ports;
|
this._forwardPorts = ports;
|
||||||
}
|
}
|
||||||
|
|
||||||
public stop(): void {
|
public stop(): void {
|
||||||
if (!this.enabled)
|
|
||||||
return;
|
|
||||||
debugLogger.log('proxy', 'stopping server');
|
debugLogger.log('proxy', 'stopping server');
|
||||||
this._server.close();
|
this._server.close();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue