mirror of
https://github.com/microsoft/playwright.git
synced 2024-12-15 14:11:50 +03:00
chore: unify launching server between browser types (#2338)
This commit is contained in:
parent
3aca21c13b
commit
55d47fd48f
@ -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<Browser>;
|
||||
}
|
||||
|
||||
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<BrowserServer>;
|
||||
private async _launchServer(options: LaunchServerOptions, launchType: LaunchType, logger: RootLogger, deadline: number, userDataDir?: string): Promise<BrowserServer> {
|
||||
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<BrowserBase>;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
@ -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<BrowserServer> {
|
||||
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',
|
||||
|
@ -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<FFBrowser> {
|
||||
return FFBrowser.connect(transport, options);
|
||||
}
|
||||
|
||||
async _launchServer(options: LaunchServerOptions, launchType: LaunchType, logger: RootLogger, deadline: number, userDataDir?: string): Promise<BrowserServer> {
|
||||
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');
|
||||
|
@ -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,
|
||||
|
@ -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<WKBrowser> {
|
||||
return WKBrowser.connect(transport, options);
|
||||
}
|
||||
|
||||
async _launchServer(options: LaunchServerOptions, launchType: LaunchType, logger: RootLogger, deadline: number, userDataDir?: string): Promise<BrowserServer> {
|
||||
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();
|
||||
|
Loading…
Reference in New Issue
Block a user