feat(reuse): retain different browser types when reusing (#16269)
This commit is contained in:
parent
b3d30a808f
commit
3aa5710b49
|
|
@ -30,16 +30,16 @@ export function launchGridAgent(agentId: string, gridURL: string, runId: string
|
||||||
const ws = new WebSocket(gridURL.replace('http://', 'ws://') + `/registerAgent?` + params.toString());
|
const ws = new WebSocket(gridURL.replace('http://', 'ws://') + `/registerAgent?` + params.toString());
|
||||||
ws.on('message', (message: string) => {
|
ws.on('message', (message: string) => {
|
||||||
log('worker requested ' + message);
|
log('worker requested ' + message);
|
||||||
const { workerId, browserAlias } = JSON.parse(message);
|
const { workerId, browserName } = JSON.parse(message);
|
||||||
if (!workerId) {
|
if (!workerId) {
|
||||||
log('workerId not specified');
|
log('workerId not specified');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!browserAlias) {
|
if (!browserName) {
|
||||||
log('browserAlias not specified');
|
log('browserName not specified');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
fork(require.resolve('./gridBrowserWorker.js'), [gridURL, agentId, workerId, browserAlias], { detached: true });
|
fork(require.resolve('./gridBrowserWorker.js'), [gridURL, agentId, workerId, browserName], { detached: true });
|
||||||
});
|
});
|
||||||
ws.on('close', () => process.exit(0));
|
ws.on('close', () => process.exit(0));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,11 +19,11 @@ import { ws as WebSocket } from '../utilsBundle';
|
||||||
import { PlaywrightConnection } from '../remote/playwrightConnection';
|
import { PlaywrightConnection } from '../remote/playwrightConnection';
|
||||||
import { gracefullyCloseAll } from '../utils/processLauncher';
|
import { gracefullyCloseAll } from '../utils/processLauncher';
|
||||||
|
|
||||||
function launchGridBrowserWorker(gridURL: string, agentId: string, workerId: string, browserAlias: string) {
|
function launchGridBrowserWorker(gridURL: string, agentId: string, workerId: string, browserName: string) {
|
||||||
const log = debug(`pw:grid:worker:${workerId}`);
|
const log = debug(`pw:grid:worker:${workerId}`);
|
||||||
log('created');
|
log('created');
|
||||||
const ws = new WebSocket(gridURL.replace('http://', 'ws://') + `/registerWorker?agentId=${agentId}&workerId=${workerId}`);
|
const ws = new WebSocket(gridURL.replace('http://', 'ws://') + `/registerWorker?agentId=${agentId}&workerId=${workerId}`);
|
||||||
new PlaywrightConnection(Promise.resolve(), 'auto', ws, { enableSocksProxy: true, browserAlias, launchOptions: {} }, { playwright: null, browser: null }, log, async () => {
|
new PlaywrightConnection(Promise.resolve(), 'auto', ws, { enableSocksProxy: true, browserName, launchOptions: {} }, { playwright: null, browser: null }, log, async () => {
|
||||||
log('exiting process');
|
log('exiting process');
|
||||||
setTimeout(() => process.exit(0), 30000);
|
setTimeout(() => process.exit(0), 30000);
|
||||||
// Meanwhile, try to gracefully close all browsers.
|
// Meanwhile, try to gracefully close all browsers.
|
||||||
|
|
|
||||||
|
|
@ -64,8 +64,7 @@ const WSErrors = {
|
||||||
|
|
||||||
|
|
||||||
type GridWorkerParams = {
|
type GridWorkerParams = {
|
||||||
browserAlias?: string;
|
browserName?: string;
|
||||||
headless?: boolean;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class GridWorker extends EventEmitter {
|
class GridWorker extends EventEmitter {
|
||||||
|
|
@ -284,8 +283,7 @@ export class GridServer {
|
||||||
}
|
}
|
||||||
|
|
||||||
agent.createWorker(ws, {
|
agent.createWorker(ws, {
|
||||||
browserAlias: request.headers['x-playwright-browser'] as string | undefined,
|
browserName: request.headers['x-playwright-browser'] as string,
|
||||||
headless: request.headers['x-playwright-headless'] !== '0',
|
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,12 +15,11 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { WebSocket } from '../utilsBundle';
|
import type { WebSocket } from '../utilsBundle';
|
||||||
import type { Playwright, DispatcherScope, Executable } from '../server';
|
import type { Playwright, DispatcherScope } from '../server';
|
||||||
import { createPlaywright, DispatcherConnection, Root, PlaywrightDispatcher } from '../server';
|
import { createPlaywright, DispatcherConnection, Root, PlaywrightDispatcher } from '../server';
|
||||||
import { Browser } from '../server/browser';
|
import { Browser } from '../server/browser';
|
||||||
import { serverSideCallMetadata } from '../server/instrumentation';
|
import { serverSideCallMetadata } from '../server/instrumentation';
|
||||||
import { gracefullyCloseAll } from '../utils/processLauncher';
|
import { gracefullyCloseAll } from '../utils/processLauncher';
|
||||||
import { registry } from '../server';
|
|
||||||
import { SocksProxy } from '../common/socksProxy';
|
import { SocksProxy } from '../common/socksProxy';
|
||||||
import type { Mode } from './playwrightServer';
|
import type { Mode } from './playwrightServer';
|
||||||
import { assert } from '../utils';
|
import { assert } from '../utils';
|
||||||
|
|
@ -28,7 +27,7 @@ import type { LaunchOptions } from '../server/types';
|
||||||
|
|
||||||
type Options = {
|
type Options = {
|
||||||
enableSocksProxy: boolean,
|
enableSocksProxy: boolean,
|
||||||
browserAlias: string | null,
|
browserName: string | null,
|
||||||
launchOptions: LaunchOptions,
|
launchOptions: LaunchOptions,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -78,7 +77,7 @@ export class PlaywrightConnection {
|
||||||
return await this._initReuseBrowsersMode(scope);
|
return await this._initReuseBrowsersMode(scope);
|
||||||
if (mode === 'use-pre-launched-browser')
|
if (mode === 'use-pre-launched-browser')
|
||||||
return await this._initPreLaunchedBrowserMode(scope);
|
return await this._initPreLaunchedBrowserMode(scope);
|
||||||
if (!options.browserAlias)
|
if (!options.browserName)
|
||||||
return await this._initPlaywrightConnectMode(scope);
|
return await this._initPlaywrightConnectMode(scope);
|
||||||
return await this._initLaunchBrowserMode(scope);
|
return await this._initLaunchBrowserMode(scope);
|
||||||
});
|
});
|
||||||
|
|
@ -95,15 +94,11 @@ export class PlaywrightConnection {
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _initLaunchBrowserMode(scope: DispatcherScope) {
|
private async _initLaunchBrowserMode(scope: DispatcherScope) {
|
||||||
this._debugLog(`engaged launch mode for "${this._options.browserAlias}"`);
|
this._debugLog(`engaged launch mode for "${this._options.browserName}"`);
|
||||||
const executable = this._executableForBrowerAlias(this._options.browserAlias!);
|
|
||||||
|
|
||||||
const playwright = createPlaywright('javascript');
|
const playwright = createPlaywright('javascript');
|
||||||
const socksProxy = this._options.enableSocksProxy ? await this._enableSocksProxy(playwright) : undefined;
|
const socksProxy = this._options.enableSocksProxy ? await this._enableSocksProxy(playwright) : undefined;
|
||||||
const browser = await playwright[executable.browserName!].launch(serverSideCallMetadata(), {
|
const browser = await playwright[this._options.browserName as 'chromium'].launch(serverSideCallMetadata(), this._options.launchOptions);
|
||||||
channel: executable.type === 'browser' ? undefined : executable.name,
|
|
||||||
headless: this._options.launchOptions?.headless,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Close the browser on disconnect.
|
// Close the browser on disconnect.
|
||||||
// TODO: it is technically possible to launch more browsers over protocol.
|
// TODO: it is technically possible to launch more browsers over protocol.
|
||||||
|
|
@ -132,20 +127,26 @@ export class PlaywrightConnection {
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _initReuseBrowsersMode(scope: DispatcherScope) {
|
private async _initReuseBrowsersMode(scope: DispatcherScope) {
|
||||||
this._debugLog(`engaged reuse browsers mode for ${this._options.browserAlias}`);
|
this._debugLog(`engaged reuse browsers mode for ${this._options.browserName}`);
|
||||||
const executable = this._executableForBrowerAlias(this._options.browserAlias!);
|
|
||||||
const playwright = this._preLaunched.playwright!;
|
const playwright = this._preLaunched.playwright!;
|
||||||
const requestedOptions = launchOptionsHash(this._options.launchOptions);
|
const requestedOptions = launchOptionsHash(this._options.launchOptions);
|
||||||
let browser = playwright.allBrowsers().find(b => {
|
let browser = playwright.allBrowsers().find(b => {
|
||||||
|
if (b.options.name !== this._options.browserName)
|
||||||
|
return false;
|
||||||
const existingOptions = launchOptionsHash(b.options.originalLaunchOptions);
|
const existingOptions = launchOptionsHash(b.options.originalLaunchOptions);
|
||||||
return existingOptions === requestedOptions;
|
return existingOptions === requestedOptions;
|
||||||
});
|
});
|
||||||
const remaining = playwright.allBrowsers().filter(b => b !== browser);
|
|
||||||
for (const r of remaining)
|
// Close remaining browsers of this type+channel. Keep different browser types for the speed.
|
||||||
await r.close();
|
for (const b of playwright.allBrowsers()) {
|
||||||
|
if (b === browser)
|
||||||
|
continue;
|
||||||
|
if (b.options.name === this._options.browserName && b.options.channel === this._options.launchOptions.channel)
|
||||||
|
await b.close();
|
||||||
|
}
|
||||||
|
|
||||||
if (!browser) {
|
if (!browser) {
|
||||||
browser = await playwright[executable.browserName!].launch(serverSideCallMetadata(), {
|
browser = await playwright[this._options.browserName as 'chromium'].launch(serverSideCallMetadata(), {
|
||||||
...this._options.launchOptions,
|
...this._options.launchOptions,
|
||||||
headless: false,
|
headless: false,
|
||||||
});
|
});
|
||||||
|
|
@ -187,13 +188,6 @@ export class PlaywrightConnection {
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _executableForBrowerAlias(browserAlias: string): Executable {
|
|
||||||
const executable = registry.findExecutable(browserAlias);
|
|
||||||
if (!executable || !executable.browserName)
|
|
||||||
throw new Error(`Unsupported browser "${browserAlias}`);
|
|
||||||
return executable;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function launchOptionsHash(options: LaunchOptions) {
|
function launchOptionsHash(options: LaunchOptions) {
|
||||||
|
|
|
||||||
|
|
@ -120,7 +120,7 @@ export class PlaywrightServer {
|
||||||
}
|
}
|
||||||
const url = new URL('http://localhost' + (request.url || ''));
|
const url = new URL('http://localhost' + (request.url || ''));
|
||||||
const browserHeader = request.headers['x-playwright-browser'];
|
const browserHeader = request.headers['x-playwright-browser'];
|
||||||
const browserAlias = url.searchParams.get('browser') || (Array.isArray(browserHeader) ? browserHeader[0] : browserHeader) || null;
|
const browserName = url.searchParams.get('browser') || (Array.isArray(browserHeader) ? browserHeader[0] : browserHeader) || null;
|
||||||
const proxyHeader = request.headers['x-playwright-proxy'];
|
const proxyHeader = request.headers['x-playwright-proxy'];
|
||||||
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 enableSocksProxy = this._options.enableSocksProxy && proxyValue === '*';
|
const enableSocksProxy = this._options.enableSocksProxy && proxyValue === '*';
|
||||||
|
|
@ -132,17 +132,12 @@ export class PlaywrightServer {
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const headlessHeader = request.headers['x-playwright-headless'];
|
|
||||||
const headlessValue = url.searchParams.get('headless') || (Array.isArray(headlessHeader) ? headlessHeader[0] : headlessHeader);
|
|
||||||
if (headlessValue && headlessValue !== '0')
|
|
||||||
launchOptions.headless = true;
|
|
||||||
|
|
||||||
const log = newLogger();
|
const log = newLogger();
|
||||||
log(`serving connection: ${request.url}`);
|
log(`serving connection: ${request.url}`);
|
||||||
const connection = new PlaywrightConnection(
|
const connection = new PlaywrightConnection(
|
||||||
semaphore.aquire(),
|
semaphore.aquire(),
|
||||||
this._mode, ws,
|
this._mode, ws,
|
||||||
{ enableSocksProxy, browserAlias, launchOptions },
|
{ enableSocksProxy, browserName, launchOptions },
|
||||||
{ playwright: this._preLaunchedPlaywright, browser: this._options.preLaunchedBrowser || null },
|
{ playwright: this._preLaunchedPlaywright, browser: this._options.preLaunchedBrowser || null },
|
||||||
log, () => semaphore.release());
|
log, () => semaphore.release());
|
||||||
(ws as any)[kConnectionSymbol] = connection;
|
(ws as any)[kConnectionSymbol] = connection;
|
||||||
|
|
|
||||||
|
|
@ -115,8 +115,7 @@ export const test = _baseTest.extend<TestFixtures, WorkerFixtures>({
|
||||||
throw new Error(`Unexpected browserName "${browserName}", must be one of "chromium", "firefox" or "webkit"`);
|
throw new Error(`Unexpected browserName "${browserName}", must be one of "chromium", "firefox" or "webkit"`);
|
||||||
const browser = await playwright[browserName].connect(connectOptions.wsEndpoint, {
|
const browser = await playwright[browserName].connect(connectOptions.wsEndpoint, {
|
||||||
headers: {
|
headers: {
|
||||||
'x-playwright-browser': channel || browserName,
|
'x-playwright-browser': browserName,
|
||||||
'x-playwright-headless': headless ? '1' : '0',
|
|
||||||
'x-playwright-launch-options': JSON.stringify(launchOptions),
|
'x-playwright-launch-options': JSON.stringify(launchOptions),
|
||||||
...connectOptions.headers,
|
...connectOptions.headers,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -66,7 +66,8 @@ const it = contextTest.extend<{ pageFactory: (redirectPortForTest?: number) => P
|
||||||
const server = new OutOfProcessPlaywrightServer(0, 3200 + testInfo.workerIndex);
|
const server = new OutOfProcessPlaywrightServer(0, 3200 + testInfo.workerIndex);
|
||||||
playwrightServers.push(server);
|
playwrightServers.push(server);
|
||||||
const browser = await browserType.connect({
|
const browser = await browserType.connect({
|
||||||
wsEndpoint: await server.wsEndpoint() + '?proxy=*&browser=' + (channel || browserName),
|
wsEndpoint: await server.wsEndpoint() + '?proxy=*&browser=' + browserName,
|
||||||
|
headers: { 'x-playwright-launch-options': JSON.stringify({ channel }) },
|
||||||
__testHookRedirectPortForwarding: redirectPortForTest,
|
__testHookRedirectPortForwarding: redirectPortForTest,
|
||||||
} as any);
|
} as any);
|
||||||
browsers.push(browser);
|
browsers.push(browser);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue