chore: refactor CRBrowserServer (#408)

This commit is contained in:
Dmitry Gozman 2020-01-07 14:13:55 -08:00 committed by Pavel Feldman
parent f7b0db2307
commit f15abadc9e
3 changed files with 82 additions and 40 deletions

View File

@ -577,7 +577,7 @@ Connects to the browser server and returns a <[Browser]> object.
- returns: <?[ChildProcess]> Spawned browser server process.
#### browserServer.wsEndpoint()
- returns: <[string]> Browser websocket url.
- returns: <?[string]> Browser websocket url.
Browser websocket endpoint which can be used as an argument to `playwright.connect`.

View File

@ -22,7 +22,6 @@ import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import * as util from 'util';
import { BrowserServer } from '../browser';
import { BrowserFetcher, BrowserFetcherOptions, BrowserFetcherRevisionInfo, OnProgressCallback } from '../browserFetcher';
import { DeviceDescriptors } from '../deviceDescriptors';
import * as Errors from '../errors';
@ -33,15 +32,21 @@ import { CRBrowser } from './crBrowser';
import * as platform from '../platform';
import { TimeoutError } from '../errors';
import { launchProcess, waitForLine } from '../processLauncher';
import { ChildProcess } from 'child_process';
import { CRConnection } from './crConnection';
export type LauncherChromeArgOptions = {
export type SlowMoOptions = {
slowMo?: number,
};
export type ChromeArgOptions = {
headless?: boolean,
args?: string[],
userDataDir?: string,
devtools?: boolean,
};
export type LauncherLaunchOptions = {
export type LaunchOptions = ChromeArgOptions & SlowMoOptions & {
executablePath?: string,
ignoreDefaultArgs?: boolean|string[],
handleSIGINT?: boolean,
@ -53,10 +58,46 @@ export type LauncherLaunchOptions = {
pipe?: boolean,
};
export type ConnectionOptions = {
slowMo?: number,
export type ConnectOptions = SlowMoOptions & {
browserWSEndpoint?: string;
browserURL?: string;
transport?: ConnectionTransport;
};
export class CRBrowserServer {
private _process: ChildProcess;
private _connectOptions: ConnectOptions;
constructor(process: ChildProcess, connectOptions: ConnectOptions) {
this._process = process;
this._connectOptions = connectOptions;
}
async connect(): Promise<CRBrowser> {
const transport = await createTransport(this._connectOptions);
return CRBrowser.create(transport);
}
process(): ChildProcess {
return this._process;
}
wsEndpoint(): string | null {
return this._connectOptions.browserWSEndpoint || null;
}
connectOptions(): ConnectOptions {
return this._connectOptions;
}
async close(): Promise<void> {
const transport = await createTransport(this._connectOptions);
const connection = new CRConnection(transport);
await connection.rootSession.send('Browser.close');
connection.dispose();
}
}
export class CRPlaywright {
private _projectRoot: string;
readonly _revision: string;
@ -73,12 +114,12 @@ export class CRPlaywright {
return revisionInfo;
}
async launch(options?: (LauncherLaunchOptions & LauncherChromeArgOptions & ConnectionOptions) | undefined): Promise<CRBrowser> {
async launch(options?: LaunchOptions): Promise<CRBrowser> {
const server = await this.launchServer(options);
return server.connect();
}
async launchServer(options: (LauncherLaunchOptions & LauncherChromeArgOptions & ConnectionOptions) = {}): Promise<BrowserServer<CRBrowser>> {
async launchServer(options: LaunchOptions = {}): Promise<CRBrowserServer> {
const {
ignoreDefaultArgs = false,
args = [],
@ -131,51 +172,36 @@ export class CRPlaywright {
pipe: usePipe,
tempDir: temporaryUserDataDir
}, () => {
if (temporaryUserDataDir || !browser)
if (temporaryUserDataDir || !server)
return Promise.reject();
return browser.close();
return server.close();
});
let browser: CRBrowser | undefined;
let server: CRBrowserServer | undefined;
try {
let transport: ConnectionTransport | null = null;
let connectOptions: ConnectOptions | undefined;
let browserWSEndpoint: string = '';
if (!usePipe) {
const timeoutError = new TimeoutError(`Timed out after ${timeout} ms while trying to connect to Chrome! The only Chrome revision guaranteed to work is r${this._revision}`);
const match = await waitForLine(launchedProcess, launchedProcess.stderr, /^DevTools listening on (ws:\/\/.*)$/, timeout, timeoutError);
browserWSEndpoint = match[1];
transport = await WebSocketTransport.create(browserWSEndpoint);
connectOptions = { browserWSEndpoint, slowMo };
} else {
transport = new PipeTransport(launchedProcess.stdio[3] as NodeJS.WritableStream, launchedProcess.stdio[4] as NodeJS.ReadableStream);
const transport = new PipeTransport(launchedProcess.stdio[3] as NodeJS.WritableStream, launchedProcess.stdio[4] as NodeJS.ReadableStream);
connectOptions = { slowMo, transport };
}
browser = await CRBrowser.create(SlowMoTransport.wrap(transport, slowMo));
return new BrowserServer(browser, launchedProcess, browserWSEndpoint);
server = new CRBrowserServer(launchedProcess, connectOptions);
return server;
} catch (e) {
if (browser)
await browser.close();
if (server)
await server.close();
throw e;
}
}
async connect(options: (ConnectionOptions & {
browserWSEndpoint?: string;
browserURL?: string;
transport?: ConnectionTransport; })): Promise<CRBrowser> {
assert(Number(!!options.browserWSEndpoint) + Number(!!options.browserURL) + Number(!!options.transport) === 1, 'Exactly one of browserWSEndpoint, browserURL or transport must be passed to playwright.connect');
let transport: ConnectionTransport | undefined;
let connectionURL: string = '';
if (options.transport) {
transport = options.transport;
} else if (options.browserWSEndpoint) {
connectionURL = options.browserWSEndpoint;
transport = await WebSocketTransport.create(options.browserWSEndpoint);
} else if (options.browserURL) {
connectionURL = await getWSEndpoint(options.browserURL);
transport = await WebSocketTransport.create(connectionURL);
}
return CRBrowser.create(SlowMoTransport.wrap(transport, options.slowMo));
async connect(options: ConnectOptions): Promise<CRBrowser> {
const transport = await createTransport(options);
return CRBrowser.create(transport);
}
executablePath(): string {
@ -190,7 +216,7 @@ export class CRPlaywright {
return Errors;
}
defaultArgs(options: LauncherChromeArgOptions = {}): string[] {
defaultArgs(options: ChromeArgOptions = {}): string[] {
const {
devtools = false,
headless = !devtools,
@ -332,3 +358,19 @@ function getWSEndpoint(browserURL: string): Promise<string> {
throw e;
});
}
async function createTransport(options: ConnectOptions): Promise<ConnectionTransport> {
assert(Number(!!options.browserWSEndpoint) + Number(!!options.browserURL) + Number(!!options.transport) === 1, 'Exactly one of browserWSEndpoint, browserURL or transport must be passed to playwright.connect');
let transport: ConnectionTransport | undefined;
let connectionURL: string = '';
if (options.transport) {
transport = options.transport;
} else if (options.browserWSEndpoint) {
connectionURL = options.browserWSEndpoint;
transport = await WebSocketTransport.create(options.browserWSEndpoint);
} else if (options.browserURL) {
connectionURL = await getWSEndpoint(options.browserURL);
transport = await WebSocketTransport.create(connectionURL);
}
return SlowMoTransport.wrap(transport, options.slowMo);
}

View File

@ -163,7 +163,7 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
const browserServer = await playwright.launchServer(options);
const browser = await browserServer.connect();
expect((await browser.defaultContext().pages()).length).toBe(1);
expect(browserServer.wsEndpoint()).toBe('');
expect(browserServer.wsEndpoint()).toBe(null);
const page = await browser.defaultContext().newPage();
expect(await page.evaluate('11 * 11')).toBe(121);
await page.close();
@ -174,7 +174,7 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
options.args = ['--remote-debugging-pipe'].concat(options.args || []);
const browserServer = await playwright.launchServer(options);
const browser = await browserServer.connect();
expect(browserServer.wsEndpoint()).toBe('');
expect(browserServer.wsEndpoint()).toBe(null);
const page = await browser.defaultContext().newPage();
expect(await page.evaluate('11 * 11')).toBe(121);
await page.close();