chore: explicit server mode control (#23215)
This commit is contained in:
parent
b814e8a5f1
commit
6cce93b697
|
|
@ -49,7 +49,7 @@ export class AndroidServerLauncherImpl {
|
||||||
const path = options.wsPath ? (options.wsPath.startsWith('/') ? options.wsPath : `/${options.wsPath}`) : `/${createGuid()}`;
|
const path = options.wsPath ? (options.wsPath.startsWith('/') ? options.wsPath : `/${options.wsPath}`) : `/${createGuid()}`;
|
||||||
|
|
||||||
// 2. Start the server
|
// 2. Start the server
|
||||||
const server = new PlaywrightServer({ path, maxConnections: 1, preLaunchedAndroidDevice: device });
|
const server = new PlaywrightServer({ mode: 'launchServer', path, maxConnections: 1, preLaunchedAndroidDevice: device });
|
||||||
const wsEndpoint = await server.listen(options.port);
|
const wsEndpoint = await server.listen(options.port);
|
||||||
|
|
||||||
// 3. Return the BrowserServer interface
|
// 3. Return the BrowserServer interface
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,7 @@ export class BrowserServerLauncherImpl implements BrowserServerLauncher {
|
||||||
const path = options.wsPath ? (options.wsPath.startsWith('/') ? options.wsPath : `/${options.wsPath}`) : `/${createGuid()}`;
|
const path = options.wsPath ? (options.wsPath.startsWith('/') ? options.wsPath : `/${options.wsPath}`) : `/${createGuid()}`;
|
||||||
|
|
||||||
// 2. Start the server
|
// 2. Start the server
|
||||||
const server = new PlaywrightServer({ path, maxConnections: Infinity, preLaunchedBrowser: browser, preLaunchedSocksProxy: socksProxy });
|
const server = new PlaywrightServer({ mode: 'launchServer', path, maxConnections: Infinity, preLaunchedBrowser: browser, preLaunchedSocksProxy: socksProxy });
|
||||||
const wsEndpoint = await server.listen(options.port);
|
const wsEndpoint = await server.listen(options.port);
|
||||||
|
|
||||||
// 3. Return the BrowserServer interface
|
// 3. Return the BrowserServer interface
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,7 @@ export function runDriver() {
|
||||||
export type RunServerOptions = {
|
export type RunServerOptions = {
|
||||||
port?: number,
|
port?: number,
|
||||||
path?: string,
|
path?: string,
|
||||||
|
extension?: boolean,
|
||||||
maxConnections?: number,
|
maxConnections?: number,
|
||||||
browserProxyMode?: 'client' | 'tether',
|
browserProxyMode?: 'client' | 'tether',
|
||||||
ownedByTetherClient?: boolean,
|
ownedByTetherClient?: boolean,
|
||||||
|
|
@ -64,8 +65,9 @@ export async function runServer(options: RunServerOptions) {
|
||||||
port,
|
port,
|
||||||
path = '/',
|
path = '/',
|
||||||
maxConnections = Infinity,
|
maxConnections = Infinity,
|
||||||
|
extension,
|
||||||
} = options;
|
} = options;
|
||||||
const server = new PlaywrightServer({ path, maxConnections });
|
const server = new PlaywrightServer({ mode: extension ? 'extension' : 'default', path, maxConnections });
|
||||||
const wsEndpoint = await server.listen(port);
|
const wsEndpoint = await server.listen(port);
|
||||||
process.on('exit', () => server.close().catch(console.error));
|
process.on('exit', () => server.close().catch(console.error));
|
||||||
console.log('Listening on ' + wsEndpoint); // eslint-disable-line no-console
|
console.log('Listening on ' + wsEndpoint); // eslint-disable-line no-console
|
||||||
|
|
|
||||||
|
|
@ -246,11 +246,13 @@ program
|
||||||
.option('--port <port>', 'Server port')
|
.option('--port <port>', 'Server port')
|
||||||
.option('--path <path>', 'Endpoint Path', '/')
|
.option('--path <path>', 'Endpoint Path', '/')
|
||||||
.option('--max-clients <maxClients>', 'Maximum clients')
|
.option('--max-clients <maxClients>', 'Maximum clients')
|
||||||
|
.option('--mode <mode>', 'Server mode, either "default" or "extension"')
|
||||||
.action(function(options) {
|
.action(function(options) {
|
||||||
runServer({
|
runServer({
|
||||||
port: options.port ? +options.port : undefined,
|
port: options.port ? +options.port : undefined,
|
||||||
path: options.path,
|
path: options.path,
|
||||||
maxConnections: options.maxClients ? +options.maxClients : Infinity,
|
maxConnections: options.maxClients ? +options.maxClients : Infinity,
|
||||||
|
extension: options.mode === 'extension' || !!process.env.PW_EXTENSION_MODE,
|
||||||
}).catch(logErrorAndExit);
|
}).catch(logErrorAndExit);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ import { startProfiling, stopProfiling } from '../utils';
|
||||||
import { monotonicTime } from '../utils';
|
import { monotonicTime } from '../utils';
|
||||||
import { debugLogger } from '../common/debugLogger';
|
import { debugLogger } from '../common/debugLogger';
|
||||||
|
|
||||||
export type ClientType = 'controller' | 'playwright' | 'launch-browser' | 'reuse-browser' | 'pre-launched-browser-or-android';
|
export type ClientType = 'controller' | 'launch-browser' | 'reuse-browser' | 'pre-launched-browser-or-android';
|
||||||
|
|
||||||
type Options = {
|
type Options = {
|
||||||
socksProxyPattern: string | undefined,
|
socksProxyPattern: string | undefined,
|
||||||
|
|
@ -102,24 +102,10 @@ export class PlaywrightConnection {
|
||||||
return this._preLaunched.browser ? await this._initPreLaunchedBrowserMode(scope) : await this._initPreLaunchedAndroidMode(scope);
|
return this._preLaunched.browser ? await this._initPreLaunchedBrowserMode(scope) : await this._initPreLaunchedAndroidMode(scope);
|
||||||
if (clientType === 'launch-browser')
|
if (clientType === 'launch-browser')
|
||||||
return await this._initLaunchBrowserMode(scope);
|
return await this._initLaunchBrowserMode(scope);
|
||||||
if (clientType === 'playwright')
|
|
||||||
return await this._initPlaywrightConnectMode(scope);
|
|
||||||
throw new Error('Unsupported client type: ' + clientType);
|
throw new Error('Unsupported client type: ' + clientType);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _initPlaywrightConnectMode(scope: RootDispatcher) {
|
|
||||||
debugLogger.log('server', `[${this._id}] engaged playwright.connect mode`);
|
|
||||||
const playwright = createPlaywright('javascript');
|
|
||||||
// Close all launched browsers on disconnect.
|
|
||||||
this._cleanups.push(async () => {
|
|
||||||
await Promise.all(playwright.allBrowsers().map(browser => browser.close()));
|
|
||||||
});
|
|
||||||
|
|
||||||
const ownedSocksProxy = await this._createOwnedSocksProxy(playwright);
|
|
||||||
return new PlaywrightDispatcher(scope, playwright, ownedSocksProxy);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _initLaunchBrowserMode(scope: RootDispatcher) {
|
private async _initLaunchBrowserMode(scope: RootDispatcher) {
|
||||||
debugLogger.log('server', `[${this._id}] engaged launch mode for "${this._options.browserName}"`);
|
debugLogger.log('server', `[${this._id}] engaged launch mode for "${this._options.browserName}"`);
|
||||||
const playwright = createPlaywright('javascript');
|
const playwright = createPlaywright('javascript');
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ const kConnectionSymbol = Symbol('kConnection');
|
||||||
type ServerOptions = {
|
type ServerOptions = {
|
||||||
path: string;
|
path: string;
|
||||||
maxConnections: number;
|
maxConnections: number;
|
||||||
|
mode: 'default' | 'launchServer' | 'extension';
|
||||||
preLaunchedBrowser?: Browser;
|
preLaunchedBrowser?: Browser;
|
||||||
preLaunchedAndroidDevice?: AndroidDevice;
|
preLaunchedAndroidDevice?: AndroidDevice;
|
||||||
preLaunchedSocksProxy?: SocksProxy;
|
preLaunchedSocksProxy?: SocksProxy;
|
||||||
|
|
@ -97,38 +98,35 @@ export class PlaywrightServer {
|
||||||
const proxyValue = url.searchParams.get('proxy') || (Array.isArray(proxyHeader) ? proxyHeader[0] : proxyHeader);
|
const proxyValue = url.searchParams.get('proxy') || (Array.isArray(proxyHeader) ? proxyHeader[0] : proxyHeader);
|
||||||
|
|
||||||
const launchOptionsHeader = request.headers['x-playwright-launch-options'] || '';
|
const launchOptionsHeader = request.headers['x-playwright-launch-options'] || '';
|
||||||
|
const launchOptionsHeaderValue = Array.isArray(launchOptionsHeader) ? launchOptionsHeader[0] : launchOptionsHeader;
|
||||||
|
const launchOptionsParam = url.searchParams.get('launch-options');
|
||||||
let launchOptions: LaunchOptions = {};
|
let launchOptions: LaunchOptions = {};
|
||||||
try {
|
try {
|
||||||
launchOptions = JSON.parse(Array.isArray(launchOptionsHeader) ? launchOptionsHeader[0] : launchOptionsHeader);
|
launchOptions = JSON.parse(launchOptionsParam || launchOptionsHeaderValue);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const id = String(++lastConnectionId);
|
const id = String(++lastConnectionId);
|
||||||
debugLogger.log('server', `[${id}] serving connection: ${request.url}`);
|
debugLogger.log('server', `[${id}] serving connection: ${request.url}`);
|
||||||
const isDebugControllerClient = !!request.headers['x-playwright-debug-controller'];
|
|
||||||
const shouldReuseBrowser = !!request.headers['x-playwright-reuse-context'];
|
|
||||||
|
|
||||||
// If we started in the legacy reuse-browser mode, create this._preLaunchedPlaywright.
|
// Instantiate playwright for the extension modes.
|
||||||
// If we get a debug-controller request, create this._preLaunchedPlaywright.
|
const isExtension = this._options.mode === 'extension';
|
||||||
if (isDebugControllerClient || shouldReuseBrowser) {
|
if (isExtension) {
|
||||||
if (!this._preLaunchedPlaywright)
|
if (!this._preLaunchedPlaywright)
|
||||||
this._preLaunchedPlaywright = createPlaywright('javascript');
|
this._preLaunchedPlaywright = createPlaywright('javascript');
|
||||||
}
|
}
|
||||||
|
|
||||||
let clientType: ClientType = 'playwright';
|
let clientType: ClientType = 'launch-browser';
|
||||||
let semaphore: Semaphore = browserSemaphore;
|
let semaphore: Semaphore = browserSemaphore;
|
||||||
if (isDebugControllerClient) {
|
if (isExtension && url.searchParams.has('debug-controller')) {
|
||||||
clientType = 'controller';
|
clientType = 'controller';
|
||||||
semaphore = controllerSemaphore;
|
semaphore = controllerSemaphore;
|
||||||
} else if (shouldReuseBrowser) {
|
} else if (isExtension) {
|
||||||
clientType = 'reuse-browser';
|
clientType = 'reuse-browser';
|
||||||
semaphore = reuseBrowserSemaphore;
|
semaphore = reuseBrowserSemaphore;
|
||||||
} else if (this._options.preLaunchedBrowser || this._options.preLaunchedAndroidDevice) {
|
} else if (this._options.mode === 'launchServer') {
|
||||||
clientType = 'pre-launched-browser-or-android';
|
clientType = 'pre-launched-browser-or-android';
|
||||||
semaphore = browserSemaphore;
|
semaphore = browserSemaphore;
|
||||||
} else if (browserName) {
|
|
||||||
clientType = 'launch-browser';
|
|
||||||
semaphore = browserSemaphore;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const connection = new PlaywrightConnection(
|
const connection = new PlaywrightConnection(
|
||||||
|
|
|
||||||
|
|
@ -409,7 +409,7 @@ ${colors.dim('Waiting for file changes. Press')} ${colors.bold('enter')} ${color
|
||||||
async function toggleShowBrowser(config: FullConfigInternal, originalWorkers: number) {
|
async function toggleShowBrowser(config: FullConfigInternal, originalWorkers: number) {
|
||||||
if (!showBrowserServer) {
|
if (!showBrowserServer) {
|
||||||
config.config.workers = 1;
|
config.config.workers = 1;
|
||||||
showBrowserServer = new PlaywrightServer({ path: '/' + createGuid(), maxConnections: 1 });
|
showBrowserServer = new PlaywrightServer({ mode: 'extension', path: '/' + createGuid(), maxConnections: 1 });
|
||||||
const wsEndpoint = await showBrowserServer.listen();
|
const wsEndpoint = await showBrowserServer.listen();
|
||||||
process.env.PW_TEST_REUSE_CONTEXT = '1';
|
process.env.PW_TEST_REUSE_CONTEXT = '1';
|
||||||
process.env.PW_TEST_CONNECT_WS_ENDPOINT = wsEndpoint;
|
process.env.PW_TEST_CONNECT_WS_ENDPOINT = wsEndpoint;
|
||||||
|
|
|
||||||
|
|
@ -132,7 +132,7 @@ const test = baseTest.extend<BrowserTestTestFixtures, BrowserTestWorkerFixtures>
|
||||||
server = remoteServer;
|
server = remoteServer;
|
||||||
} else {
|
} else {
|
||||||
const runServer = new RunServer();
|
const runServer = new RunServer();
|
||||||
await runServer._start(childProcess);
|
await runServer.start(childProcess);
|
||||||
server = runServer;
|
server = runServer;
|
||||||
}
|
}
|
||||||
return server;
|
return server;
|
||||||
|
|
|
||||||
|
|
@ -115,9 +115,7 @@ export class Backend extends EventEmitter {
|
||||||
}
|
}
|
||||||
|
|
||||||
async connect(wsEndpoint: string) {
|
async connect(wsEndpoint: string) {
|
||||||
this._transport = await WebSocketTransport.connect(wsEndpoint, {
|
this._transport = await WebSocketTransport.connect(wsEndpoint + '?debug-controller');
|
||||||
'x-playwright-debug-controller': 'true'
|
|
||||||
});
|
|
||||||
this._transport.onmessage = (message: any) => {
|
this._transport.onmessage = (message: any) => {
|
||||||
if (!message.id) {
|
if (!message.id) {
|
||||||
this.emit(message.method, message.params);
|
this.emit(message.method, message.params);
|
||||||
|
|
|
||||||
|
|
@ -27,9 +27,12 @@ export class RunServer implements PlaywrightServer {
|
||||||
private _process: TestChildProcess;
|
private _process: TestChildProcess;
|
||||||
_wsEndpoint: string;
|
_wsEndpoint: string;
|
||||||
|
|
||||||
async _start(childProcess: CommonFixtures['childProcess']) {
|
async start(childProcess: CommonFixtures['childProcess'], mode?: 'extension' | 'default') {
|
||||||
|
const command = ['node', path.join(__dirname, '..', '..', 'packages', 'playwright-core', 'lib', 'cli', 'cli.js'), 'run-server'];
|
||||||
|
if (mode === 'extension')
|
||||||
|
command.push('--mode=extension');
|
||||||
this._process = childProcess({
|
this._process = childProcess({
|
||||||
command: ['node', path.join(__dirname, '..', '..', 'packages', 'playwright-core', 'lib', 'cli', 'cli.js'), 'run-server'],
|
command,
|
||||||
env: {
|
env: {
|
||||||
...process.env,
|
...process.env,
|
||||||
PWTEST_UNDER_TEST: '1',
|
PWTEST_UNDER_TEST: '1',
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ type Fixtures = {
|
||||||
const test = baseTest.extend<Fixtures>({
|
const test = baseTest.extend<Fixtures>({
|
||||||
wsEndpoint: async ({ }, use) => {
|
wsEndpoint: async ({ }, use) => {
|
||||||
process.env.PW_DEBUG_CONTROLLER_HEADLESS = '1';
|
process.env.PW_DEBUG_CONTROLLER_HEADLESS = '1';
|
||||||
const server = new PlaywrightServer({ path: '/' + createGuid(), maxConnections: Number.MAX_VALUE, enableSocksProxy: false });
|
const server = new PlaywrightServer({ mode: 'extension', path: '/' + createGuid(), maxConnections: Number.MAX_VALUE, enableSocksProxy: false });
|
||||||
const wsEndpoint = await server.listen();
|
const wsEndpoint = await server.listen();
|
||||||
await use(wsEndpoint);
|
await use(wsEndpoint);
|
||||||
await server.close();
|
await server.close();
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ const test = baseTest.extend<{ runServer: () => Promise<PlaywrightServer> }>({
|
||||||
let server: PlaywrightServer | undefined;
|
let server: PlaywrightServer | undefined;
|
||||||
await use(async () => {
|
await use(async () => {
|
||||||
const runServer = new RunServer();
|
const runServer = new RunServer();
|
||||||
await runServer._start(childProcess);
|
await runServer.start(childProcess, 'extension');
|
||||||
server = runServer;
|
server = runServer;
|
||||||
return server;
|
return server;
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue