From 55d47fd48fc4bb2d63c854a878fc5cbe07b158c0 Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Fri, 22 May 2020 07:03:42 -0700 Subject: [PATCH] chore: unify launching server between browser types (#2338) --- src/server/browserType.ts | 110 +++++++++++++++++++++++++++--- src/server/chromium.ts | 100 +++++----------------------- src/server/firefox.ts | 122 ++++++++-------------------------- src/server/processLauncher.ts | 4 +- src/server/webkit.ts | 99 ++++++--------------------- 5 files changed, 173 insertions(+), 262 deletions(-) diff --git a/src/server/browserType.ts b/src/server/browserType.ts index cc9557bed1..a7c63a29fb 100644 --- a/src/server/browserType.ts +++ b/src/server/browserType.ts @@ -14,14 +14,22 @@ * limitations under the License. */ +import * as fs from 'fs'; +import * as os from 'os'; +import * as path from 'path'; +import * as util from 'util'; import { BrowserContext, PersistentContextOptions, validatePersistentContextOptions } from '../browserContext'; -import { BrowserServer } from './browserServer'; +import { BrowserServer, WebSocketWrapper } from './browserServer'; import * as browserPaths from '../install/browserPaths'; -import { Logger, RootLogger } from '../logger'; +import { Logger, RootLogger, InnerLogger } from '../logger'; import { ConnectionTransport, WebSocketTransport } from '../transport'; import { BrowserBase, BrowserOptions, Browser } from '../browser'; import { assert, helper } from '../helper'; import { TimeoutSettings } from '../timeoutSettings'; +import { launchProcess, Env, waitForLine } from './processLauncher'; +import { Events } from '../events'; +import { TimeoutError } from '../errors'; +import { PipeTransport } from './pipeTransport'; export type BrowserArgOptions = { headless?: boolean, @@ -37,7 +45,7 @@ type LaunchOptionsBase = BrowserArgOptions & { handleSIGHUP?: boolean, timeout?: number, logger?: Logger, - env?: {[key: string]: string|number|boolean} + env?: Env, }; export function processBrowserArgOptions(options: LaunchOptionsBase): { devtools: boolean, headless: boolean } { @@ -45,7 +53,7 @@ export function processBrowserArgOptions(options: LaunchOptionsBase): { devtools return { devtools, headless }; } -export type ConnectOptions = { +type ConnectOptions = { wsEndpoint: string, slowMo?: number, logger?: Logger, @@ -53,7 +61,7 @@ export type ConnectOptions = { }; export type LaunchType = 'local' | 'server' | 'persistent'; export type LaunchOptions = LaunchOptionsBase & { slowMo?: number }; -export type LaunchServerOptions = LaunchOptionsBase & { port?: number }; +type LaunchServerOptions = LaunchOptionsBase & { port?: number }; export interface BrowserType { executablePath(): string; @@ -64,16 +72,20 @@ export interface BrowserType { connect(options: ConnectOptions): Promise; } +const mkdtempAsync = util.promisify(fs.mkdtemp); + export abstract class BrowserTypeBase implements BrowserType { private _name: string; private _executablePath: string | undefined; + private _webSocketRegexNotPipe: RegExp | null; readonly _browserPath: string; - constructor(packagePath: string, browser: browserPaths.BrowserDescriptor) { + constructor(packagePath: string, browser: browserPaths.BrowserDescriptor, webSocketRegexNotPipe: RegExp | null) { this._name = browser.name; const browsersPath = browserPaths.browsersPath(packagePath); this._browserPath = browserPaths.browserDirectory(browsersPath, browser); this._executablePath = browserPaths.executablePath(this._browserPath, browser); + this._webSocketRegexNotPipe = webSocketRegexNotPipe; } executablePath(): string { @@ -183,6 +195,88 @@ export abstract class BrowserTypeBase implements BrowserType { return this._connectToTransport(transport, { slowMo: options.slowMo, logger }); } - abstract _launchServer(options: LaunchServerOptions, launchType: LaunchType, logger: RootLogger, deadline: number, userDataDir?: string): Promise; + private async _launchServer(options: LaunchServerOptions, launchType: LaunchType, logger: RootLogger, deadline: number, userDataDir?: string): Promise { + const { + ignoreDefaultArgs = false, + args = [], + executablePath = null, + env = process.env, + handleSIGINT = true, + handleSIGTERM = true, + handleSIGHUP = true, + port = 0, + } = options; + assert(!port || launchType === 'server', 'Cannot specify a port without launching as a server.'); + + let temporaryUserDataDir: string | null = null; + if (!userDataDir) { + userDataDir = await mkdtempAsync(path.join(os.tmpdir(), `playwright_${this._name}dev_profile-`)); + temporaryUserDataDir = userDataDir; + } + + const browserArguments = []; + if (!ignoreDefaultArgs) + browserArguments.push(...this._defaultArgs(options, launchType, userDataDir)); + else if (Array.isArray(ignoreDefaultArgs)) + browserArguments.push(...this._defaultArgs(options, launchType, userDataDir).filter(arg => ignoreDefaultArgs.indexOf(arg) === -1)); + else + browserArguments.push(...args); + + const executable = executablePath || this.executablePath(); + if (!executable) + throw new Error(`No executable path is specified. Pass "executablePath" option directly.`); + + // Note: it is important to define these variables before launchProcess, so that we don't get + // "Cannot access 'browserServer' before initialization" if something went wrong. + let transport: ConnectionTransport | undefined = undefined; + let browserServer: BrowserServer | undefined = undefined; + const { launchedProcess, gracefullyClose, downloadsPath } = await launchProcess({ + executablePath: executable, + args: browserArguments, + env: this._amendEnvironment(env, userDataDir, executable, browserArguments), + handleSIGINT, + handleSIGTERM, + handleSIGHUP, + logger, + pipe: !this._webSocketRegexNotPipe, + tempDir: temporaryUserDataDir || undefined, + attemptToGracefullyClose: async () => { + if ((options as any).__testHookGracefullyClose) + await (options as any).__testHookGracefullyClose(); + // We try to gracefully close to prevent crash reporting and core dumps. + // Note that it's fine to reuse the pipe transport, since + // our connection ignores kBrowserCloseMessageId. + this._attemptToGracefullyCloseBrowser(transport!); + }, + onkill: (exitCode, signal) => { + if (browserServer) + browserServer.emit(Events.BrowserServer.Close, exitCode, signal); + }, + }); + + try { + if (this._webSocketRegexNotPipe) { + const timeoutError = new TimeoutError(`Timed out while trying to connect to the browser!`); + const match = await waitForLine(launchedProcess, launchedProcess.stdout, this._webSocketRegexNotPipe, helper.timeUntilDeadline(deadline), timeoutError); + const innerEndpoint = match[1]; + transport = await WebSocketTransport.connect(innerEndpoint, logger, deadline); + } else { + const stdio = launchedProcess.stdio as unknown as [NodeJS.ReadableStream, NodeJS.WritableStream, NodeJS.WritableStream, NodeJS.WritableStream, NodeJS.ReadableStream]; + transport = new PipeTransport(stdio[3], stdio[4], logger); + } + } catch (e) { + // If we can't establish a connection, kill the process and exit. + helper.killProcess(launchedProcess); + throw e; + } + browserServer = new BrowserServer(options, launchedProcess, gracefullyClose, transport, downloadsPath, + launchType === 'server' ? this._wrapTransportWithWebSocket(transport, logger, port) : null); + return browserServer; + } + + abstract _defaultArgs(options: BrowserArgOptions, launchType: LaunchType, userDataDir: string): string[]; abstract _connectToTransport(transport: ConnectionTransport, options: BrowserOptions): Promise; -} \ No newline at end of file + abstract _wrapTransportWithWebSocket(transport: ConnectionTransport, logger: InnerLogger, port: number): WebSocketWrapper; + abstract _amendEnvironment(env: Env, userDataDir: string, executable: string, browserArguments: string[]): Env; + abstract _attemptToGracefullyCloseBrowser(transport: ConnectionTransport): void; +} diff --git a/src/server/chromium.ts b/src/server/chromium.ts index 954b466df5..10a721fbab 100644 --- a/src/server/chromium.ts +++ b/src/server/chromium.ts @@ -15,21 +15,16 @@ * limitations under the License. */ -import * as fs from 'fs'; -import * as os from 'os'; import * as path from 'path'; -import * as util from 'util'; import { helper, assert, isDebugMode } from '../helper'; import { CRBrowser } from '../chromium/crBrowser'; import * as ws from 'ws'; -import { launchProcess } from './processLauncher'; +import { Env } from './processLauncher'; import { kBrowserCloseMessageId } from '../chromium/crConnection'; -import { PipeTransport } from './pipeTransport'; -import { BrowserArgOptions, LaunchServerOptions, BrowserTypeBase, processBrowserArgOptions, LaunchType } from './browserType'; -import { BrowserServer, WebSocketWrapper } from './browserServer'; -import { Events } from '../events'; +import { BrowserArgOptions, BrowserTypeBase, processBrowserArgOptions, LaunchType } from './browserType'; +import { WebSocketWrapper } from './browserServer'; import { ConnectionTransport, ProtocolRequest } from '../transport'; -import { InnerLogger, logError, RootLogger } from '../logger'; +import { InnerLogger, logError } from '../logger'; import { BrowserDescriptor } from '../install/browserPaths'; import { CRDevTools } from '../chromium/crDevTools'; import { BrowserOptions } from '../browser'; @@ -38,7 +33,7 @@ export class Chromium extends BrowserTypeBase { private _devtools: CRDevTools | undefined; constructor(packagePath: string, browser: BrowserDescriptor) { - super(packagePath, browser); + super(packagePath, browser, null /* use pipe not websocket */); if (isDebugMode()) this._devtools = this._createDevTools(); } @@ -56,77 +51,22 @@ export class Chromium extends BrowserTypeBase { return CRBrowser.connect(transport, options, devtools); } - async _launchServer(options: LaunchServerOptions, launchType: LaunchType, logger: RootLogger, deadline: number, userDataDir?: string): Promise { - const { - ignoreDefaultArgs = false, - args = [], - executablePath = null, - env = process.env, - handleSIGINT = true, - handleSIGTERM = true, - handleSIGHUP = true, - port = 0, - } = options; - assert(!port || launchType === 'server', 'Cannot specify a port without launching as a server.'); - - let temporaryUserDataDir: string | null = null; - if (!userDataDir) { - userDataDir = await mkdtempAsync(CHROMIUM_PROFILE_PATH); - temporaryUserDataDir = userDataDir; - } - + _amendEnvironment(env: Env, userDataDir: string, executable: string, browserArguments: string[]): Env { const runningAsRoot = process.geteuid && process.geteuid() === 0; - assert(!runningAsRoot || args.includes('--no-sandbox'), 'Cannot launch Chromium as root without --no-sandbox. See https://crbug.com/638180.'); - - const chromeArguments = []; - if (!ignoreDefaultArgs) - chromeArguments.push(...this._defaultArgs(options, launchType, userDataDir)); - else if (Array.isArray(ignoreDefaultArgs)) - chromeArguments.push(...this._defaultArgs(options, launchType, userDataDir).filter(arg => ignoreDefaultArgs.indexOf(arg) === -1)); - else - chromeArguments.push(...args); - - const chromeExecutable = executablePath || this.executablePath(); - if (!chromeExecutable) - throw new Error(`No executable path is specified. Pass "executablePath" option directly.`); - - // Note: it is important to define these variables before launchProcess, so that we don't get - // "Cannot access 'browserServer' before initialization" if something went wrong. - let transport: PipeTransport | undefined = undefined; - let browserServer: BrowserServer | undefined = undefined; - const { launchedProcess, gracefullyClose, downloadsPath } = await launchProcess({ - executablePath: chromeExecutable, - args: chromeArguments, - env, - handleSIGINT, - handleSIGTERM, - handleSIGHUP, - logger, - pipe: true, - tempDir: temporaryUserDataDir || undefined, - attemptToGracefullyClose: async () => { - if ((options as any).__testHookGracefullyClose) - await (options as any).__testHookGracefullyClose(); - // We try to gracefully close to prevent crash reporting and core dumps. - // Note that it's fine to reuse the pipe transport, since - // our connection ignores kBrowserCloseMessageId. - const t = transport!; - const message: ProtocolRequest = { method: 'Browser.close', id: kBrowserCloseMessageId, params: {} }; - t.send(message); - }, - onkill: (exitCode, signal) => { - if (browserServer) - browserServer.emit(Events.BrowserServer.Close, exitCode, signal); - }, - }); - - const stdio = launchedProcess.stdio as unknown as [NodeJS.ReadableStream, NodeJS.WritableStream, NodeJS.WritableStream, NodeJS.WritableStream, NodeJS.ReadableStream]; - transport = new PipeTransport(stdio[3], stdio[4], logger); - browserServer = new BrowserServer(options, launchedProcess, gracefullyClose, transport, downloadsPath, launchType === 'server' ? wrapTransportWithWebSocket(transport, logger, port) : null); - return browserServer; + assert(!runningAsRoot || browserArguments.includes('--no-sandbox'), 'Cannot launch Chromium as root without --no-sandbox. See https://crbug.com/638180.'); + return env; } - private _defaultArgs(options: BrowserArgOptions = {}, launchType: LaunchType, userDataDir: string): string[] { + _attemptToGracefullyCloseBrowser(transport: ConnectionTransport): void { + const message: ProtocolRequest = { method: 'Browser.close', id: kBrowserCloseMessageId, params: {} }; + transport.send(message); + } + + _wrapTransportWithWebSocket(transport: ConnectionTransport, logger: InnerLogger, port: number): WebSocketWrapper { + return wrapTransportWithWebSocket(transport, logger, port); + } + + _defaultArgs(options: BrowserArgOptions, launchType: LaunchType, userDataDir: string): string[] { const { devtools, headless } = processBrowserArgOptions(options); const { args = [] } = options; const userDataDirArg = args.find(arg => arg.startsWith('--user-data-dir')); @@ -301,10 +241,6 @@ function wrapTransportWithWebSocket(transport: ConnectionTransport, logger: Inne } -const mkdtempAsync = util.promisify(fs.mkdtemp); - -const CHROMIUM_PROFILE_PATH = path.join(os.tmpdir(), 'playwright_dev_profile-'); - const DEFAULT_ARGS = [ '--disable-background-networking', '--enable-features=NetworkService,NetworkServiceInProcess', diff --git a/src/server/firefox.ts b/src/server/firefox.ts index 702fae2134..2157afaa50 100644 --- a/src/server/firefox.ts +++ b/src/server/firefox.ts @@ -15,112 +15,48 @@ * limitations under the License. */ -import * as fs from 'fs'; import * as os from 'os'; import * as path from 'path'; -import * as util from 'util'; import * as ws from 'ws'; -import { TimeoutError } from '../errors'; -import { Events } from '../events'; import { FFBrowser } from '../firefox/ffBrowser'; import { kBrowserCloseMessageId } from '../firefox/ffConnection'; -import { helper, assert } from '../helper'; -import { BrowserServer, WebSocketWrapper } from './browserServer'; -import { BrowserArgOptions, LaunchServerOptions, BrowserTypeBase, processBrowserArgOptions, LaunchType } from './browserType'; -import { launchProcess, waitForLine } from './processLauncher'; -import { ConnectionTransport, SequenceNumberMixer, WebSocketTransport } from '../transport'; -import { InnerLogger, logError, RootLogger } from '../logger'; +import { helper } from '../helper'; +import { WebSocketWrapper } from './browserServer'; +import { BrowserArgOptions, BrowserTypeBase, processBrowserArgOptions, LaunchType } from './browserType'; +import { Env } from './processLauncher'; +import { ConnectionTransport, SequenceNumberMixer } from '../transport'; +import { InnerLogger, logError } from '../logger'; import { BrowserOptions } from '../browser'; - -const mkdtempAsync = util.promisify(fs.mkdtemp); +import { BrowserDescriptor } from '../install/browserPaths'; export class Firefox extends BrowserTypeBase { + constructor(packagePath: string, browser: BrowserDescriptor) { + const websocketRegex = /^Juggler listening on (ws:\/\/.*)$/; + super(packagePath, browser, websocketRegex /* use websocket not pipe */); + } + _connectToTransport(transport: ConnectionTransport, options: BrowserOptions): Promise { return FFBrowser.connect(transport, options); } - async _launchServer(options: LaunchServerOptions, launchType: LaunchType, logger: RootLogger, deadline: number, userDataDir?: string): Promise { - const { - ignoreDefaultArgs = false, - args = [], - executablePath = null, - env = process.env, - handleSIGHUP = true, - handleSIGINT = true, - handleSIGTERM = true, - timeout = 30000, - port = 0, - } = options; - assert(!port || launchType === 'server', 'Cannot specify a port without launching as a server.'); - - let temporaryProfileDir = null; - if (!userDataDir) { - userDataDir = await mkdtempAsync(path.join(os.tmpdir(), 'playwright_dev_firefox_profile-')); - temporaryProfileDir = userDataDir; - } - - const firefoxArguments = []; - if (!ignoreDefaultArgs) - firefoxArguments.push(...this._defaultArgs(options, launchType, userDataDir, 0)); - else if (Array.isArray(ignoreDefaultArgs)) - firefoxArguments.push(...this._defaultArgs(options, launchType, userDataDir, 0).filter(arg => !ignoreDefaultArgs.includes(arg))); - else - firefoxArguments.push(...args); - - const firefoxExecutable = executablePath || this.executablePath(); - if (!firefoxExecutable) - throw new Error(`No executable path is specified. Pass "executablePath" option directly.`); - - // Note: it is important to define these variables before launchProcess, so that we don't get - // "Cannot access 'transport' before initialization" if something went wrong. - let browserServer: BrowserServer | undefined = undefined; - let transport: ConnectionTransport | undefined = undefined; - const { launchedProcess, gracefullyClose, downloadsPath } = await launchProcess({ - executablePath: firefoxExecutable, - args: firefoxArguments, - env: os.platform() === 'linux' ? { - ...env, - // On linux Juggler ships the libstdc++ it was linked against. - LD_LIBRARY_PATH: `${path.dirname(firefoxExecutable)}:${process.env.LD_LIBRARY_PATH}`, - } : env, - handleSIGINT, - handleSIGTERM, - handleSIGHUP, - logger, - pipe: false, - tempDir: temporaryProfileDir || undefined, - attemptToGracefullyClose: async () => { - if ((options as any).__testHookGracefullyClose) - await (options as any).__testHookGracefullyClose(); - - // We try to gracefully close to prevent crash reporting and core dumps. - const message = { method: 'Browser.close', params: {}, id: kBrowserCloseMessageId }; - transport!.send(message); - }, - onkill: (exitCode, signal) => { - if (browserServer) - browserServer.emit(Events.BrowserServer.Close, exitCode, signal); - }, - }); - - const timeoutError = new TimeoutError(`Timed out after ${timeout} ms while trying to connect to Firefox!`); - const match = await waitForLine(launchedProcess, launchedProcess.stdout, /^Juggler listening on (ws:\/\/.*)$/, timeout, timeoutError); - const innerEndpoint = match[1]; - - try { - // If we can't communicate with Firefox on start, kill the process and exit. - transport = await WebSocketTransport.connect(innerEndpoint, logger, deadline); - } catch (e) { - helper.killProcess(launchedProcess); - throw e; - } - - const webSocketWrapper = launchType === 'server' ? wrapTransportWithWebSocket(transport, logger, port) : null; - browserServer = new BrowserServer(options, launchedProcess, gracefullyClose, transport, downloadsPath, webSocketWrapper); - return browserServer; + _amendEnvironment(env: Env, userDataDir: string, executable: string, browserArguments: string[]): Env { + return os.platform() === 'linux' ? { + ...env, + // On linux Juggler ships the libstdc++ it was linked against. + LD_LIBRARY_PATH: `${path.dirname(executable)}:${process.env.LD_LIBRARY_PATH}`, + } : env; } - private _defaultArgs(options: BrowserArgOptions = {}, launchType: LaunchType, userDataDir: string, port: number): string[] { + _attemptToGracefullyCloseBrowser(transport: ConnectionTransport): void { + const message = { method: 'Browser.close', params: {}, id: kBrowserCloseMessageId }; + transport.send(message); + } + + _wrapTransportWithWebSocket(transport: ConnectionTransport, logger: InnerLogger, port: number): WebSocketWrapper { + return wrapTransportWithWebSocket(transport, logger, port); + } + + _defaultArgs(options: BrowserArgOptions, launchType: LaunchType, userDataDir: string): string[] { const { devtools, headless } = processBrowserArgOptions(options); const { args = [] } = options; if (devtools) @@ -139,7 +75,7 @@ export class Firefox extends BrowserTypeBase { firefoxArguments.push('-foreground'); } firefoxArguments.push(`-profile`, userDataDir); - firefoxArguments.push('-juggler', String(port)); + firefoxArguments.push('-juggler', '0'); firefoxArguments.push(...args); if (launchType === 'persistent') firefoxArguments.push('about:blank'); diff --git a/src/server/processLauncher.ts b/src/server/processLauncher.ts index cfb06c860d..73c283ed98 100644 --- a/src/server/processLauncher.ts +++ b/src/server/processLauncher.ts @@ -44,10 +44,12 @@ const browserStdErrLog: Log = { severity: 'warning' }; +export type Env = {[key: string]: string | number | boolean | undefined}; + export type LaunchProcessOptions = { executablePath: string, args: string[], - env?: {[key: string]: string | number | boolean | undefined}, + env?: Env, handleSIGINT?: boolean, handleSIGTERM?: boolean, diff --git a/src/server/webkit.ts b/src/server/webkit.ts index 04768bc4e5..da093a8dfa 100644 --- a/src/server/webkit.ts +++ b/src/server/webkit.ts @@ -16,93 +16,40 @@ */ import { WKBrowser } from '../webkit/wkBrowser'; -import { PipeTransport } from './pipeTransport'; -import { launchProcess } from './processLauncher'; -import * as fs from 'fs'; +import { Env } from './processLauncher'; import * as path from 'path'; -import * as os from 'os'; -import * as util from 'util'; -import { helper, assert } from '../helper'; +import { helper } from '../helper'; import { kBrowserCloseMessageId } from '../webkit/wkConnection'; -import { BrowserArgOptions, LaunchServerOptions, BrowserTypeBase, processBrowserArgOptions, LaunchType } from './browserType'; +import { BrowserArgOptions, BrowserTypeBase, processBrowserArgOptions, LaunchType } from './browserType'; import { ConnectionTransport, SequenceNumberMixer } from '../transport'; import * as ws from 'ws'; -import { BrowserServer, WebSocketWrapper } from './browserServer'; -import { Events } from '../events'; -import { InnerLogger, logError, RootLogger } from '../logger'; +import { WebSocketWrapper } from './browserServer'; +import { InnerLogger, logError } from '../logger'; import { BrowserOptions } from '../browser'; +import { BrowserDescriptor } from '../install/browserPaths'; export class WebKit extends BrowserTypeBase { + constructor(packagePath: string, browser: BrowserDescriptor) { + super(packagePath, browser, null /* use pipe not websocket */); + } + _connectToTransport(transport: ConnectionTransport, options: BrowserOptions): Promise { return WKBrowser.connect(transport, options); } - async _launchServer(options: LaunchServerOptions, launchType: LaunchType, logger: RootLogger, deadline: number, userDataDir?: string): Promise { - const { - ignoreDefaultArgs = false, - args = [], - executablePath = null, - env = process.env, - handleSIGINT = true, - handleSIGTERM = true, - handleSIGHUP = true, - port = 0, - } = options; - assert(!port || launchType === 'server', 'Cannot specify a port without launching as a server.'); - - let temporaryUserDataDir: string | null = null; - if (!userDataDir) { - userDataDir = await mkdtempAsync(WEBKIT_PROFILE_PATH); - temporaryUserDataDir = userDataDir; - } - - const webkitArguments = []; - if (!ignoreDefaultArgs) - webkitArguments.push(...this._defaultArgs(options, launchType, userDataDir, port)); - else if (Array.isArray(ignoreDefaultArgs)) - webkitArguments.push(...this._defaultArgs(options, launchType, userDataDir, port).filter(arg => ignoreDefaultArgs.indexOf(arg) === -1)); - else - webkitArguments.push(...args); - - const webkitExecutable = executablePath || this.executablePath(); - if (!webkitExecutable) - throw new Error(`No executable path is specified.`); - - // Note: it is important to define these variables before launchProcess, so that we don't get - // "Cannot access 'browserServer' before initialization" if something went wrong. - let transport: ConnectionTransport | undefined = undefined; - let browserServer: BrowserServer | undefined = undefined; - const { launchedProcess, gracefullyClose, downloadsPath } = await launchProcess({ - executablePath: webkitExecutable, - args: webkitArguments, - env: { ...env, CURL_COOKIE_JAR_PATH: path.join(userDataDir, 'cookiejar.db') }, - handleSIGINT, - handleSIGTERM, - handleSIGHUP, - logger, - pipe: true, - tempDir: temporaryUserDataDir || undefined, - attemptToGracefullyClose: async () => { - if ((options as any).__testHookGracefullyClose) - await (options as any).__testHookGracefullyClose(); - // We try to gracefully close to prevent crash reporting and core dumps. - // Note that it's fine to reuse the pipe transport, since - // our connection ignores kBrowserCloseMessageId. - transport!.send({method: 'Playwright.close', params: {}, id: kBrowserCloseMessageId}); - }, - onkill: (exitCode, signal) => { - if (browserServer) - browserServer.emit(Events.BrowserServer.Close, exitCode, signal); - }, - }); - - const stdio = launchedProcess.stdio as unknown as [NodeJS.ReadableStream, NodeJS.WritableStream, NodeJS.WritableStream, NodeJS.WritableStream, NodeJS.ReadableStream]; - transport = new PipeTransport(stdio[3], stdio[4], logger); - browserServer = new BrowserServer(options, launchedProcess, gracefullyClose, transport, downloadsPath, launchType === 'server' ? wrapTransportWithWebSocket(transport, logger, port || 0) : null); - return browserServer; + _amendEnvironment(env: Env, userDataDir: string, executable: string, browserArguments: string[]): Env { + return { ...env, CURL_COOKIE_JAR_PATH: path.join(userDataDir, 'cookiejar.db') }; } - _defaultArgs(options: BrowserArgOptions = {}, launchType: LaunchType, userDataDir: string, port: number): string[] { + _attemptToGracefullyCloseBrowser(transport: ConnectionTransport): void { + transport.send({method: 'Playwright.close', params: {}, id: kBrowserCloseMessageId}); + } + + _wrapTransportWithWebSocket(transport: ConnectionTransport, logger: InnerLogger, port: number): WebSocketWrapper { + return wrapTransportWithWebSocket(transport, logger, port); + } + + _defaultArgs(options: BrowserArgOptions, launchType: LaunchType, userDataDir: string): string[] { const { devtools, headless } = processBrowserArgOptions(options); const { args = [] } = options; if (devtools) @@ -126,10 +73,6 @@ export class WebKit extends BrowserTypeBase { } } -const mkdtempAsync = util.promisify(fs.mkdtemp); - -const WEBKIT_PROFILE_PATH = path.join(os.tmpdir(), 'playwright_dev_profile-'); - function wrapTransportWithWebSocket(transport: ConnectionTransport, logger: InnerLogger, port: number): WebSocketWrapper { const server = new ws.Server({ port }); const guid = helper.guid();