Skip to content

Commit ac2ba3c

Browse files
authored
fix(api): BrowserServer -> BrowserApp, resuse it between browsers (#599)
1 parent b4209e9 commit ac2ba3c

23 files changed

+263
-391
lines changed

docs/api.md

Lines changed: 50 additions & 113 deletions
Large diffs are not rendered by default.

src/api.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ export { FFBrowser as FirefoxBrowser } from './firefox/ffBrowser';
3636
export { WKBrowser as WebKitBrowser } from './webkit/wkBrowser';
3737

3838
export { Playwright } from './server/playwright';
39-
export { CRPlaywright as ChromiumPlaywright, CRBrowserServer as ChromiumBrowserServer } from './server/crPlaywright';
40-
export { FFPlaywright as FirefoxPlaywright, FFBrowserServer as FirefoxBrowserServer } from './server/ffPlaywright';
41-
export { WKPlaywright as WebKitPlaywright, WKBrowserServer as WebKitBrowserServer } from './server/wkPlaywright';
39+
export { BrowserApp } from './server/browserApp';
40+
export { CRPlaywright as ChromiumPlaywright } from './server/crPlaywright';
41+
export { FFPlaywright as FirefoxPlaywright } from './server/ffPlaywright';
42+
export { WKPlaywright as WebKitPlaywright } from './server/wkPlaywright';

src/browser.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@
1515
*/
1616

1717
import { BrowserContext, BrowserContextOptions } from './browserContext';
18+
import { ConnectionTransport, SlowMoTransport } from './transport';
1819
import * as platform from './platform';
20+
import { assert } from './helper';
1921

2022
export interface Browser extends platform.EventEmitterType {
2123
newContext(options?: BrowserContextOptions): Promise<BrowserContext>;
@@ -26,3 +28,19 @@ export interface Browser extends platform.EventEmitterType {
2628
isConnected(): boolean;
2729
close(): Promise<void>;
2830
}
31+
32+
export type ConnectOptions = {
33+
slowMo?: number,
34+
browserWSEndpoint?: string;
35+
transport?: ConnectionTransport;
36+
};
37+
38+
export async function createTransport(options: ConnectOptions): Promise<ConnectionTransport> {
39+
assert(Number(!!options.browserWSEndpoint) + Number(!!options.transport) === 1, 'Exactly one of browserWSEndpoint or transport must be passed to connect');
40+
let transport: ConnectionTransport | undefined;
41+
if (options.transport)
42+
transport = options.transport;
43+
else if (options.browserWSEndpoint)
44+
transport = await platform.createWebSocketTransport(options.browserWSEndpoint);
45+
return SlowMoTransport.wrap(transport!, options.slowMo);
46+
}

src/chromium/crBrowser.ts

Lines changed: 2 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -24,20 +24,12 @@ import { Page, Worker } from '../page';
2424
import { CRTarget } from './crTarget';
2525
import { Protocol } from './protocol';
2626
import { CRPage } from './crPage';
27-
import { Browser } from '../browser';
27+
import { Browser, createTransport, ConnectOptions } from '../browser';
2828
import * as network from '../network';
2929
import * as types from '../types';
3030
import * as platform from '../platform';
31-
import { ConnectionTransport, SlowMoTransport } from '../transport';
3231
import { readProtocolStream } from './crProtocolHelper';
3332

34-
export type CRConnectOptions = {
35-
slowMo?: number,
36-
browserWSEndpoint?: string;
37-
browserURL?: string;
38-
transport?: ConnectionTransport;
39-
};
40-
4133
export class CRBrowser extends platform.EventEmitter implements Browser {
4234
_connection: CRConnection;
4335
_client: CRSession;
@@ -49,7 +41,7 @@ export class CRBrowser extends platform.EventEmitter implements Browser {
4941
private _tracingPath: string | null = '';
5042
private _tracingClient: CRSession | undefined;
5143

52-
static async connect(options: CRConnectOptions): Promise<CRBrowser> {
44+
static async connect(options: ConnectOptions): Promise<CRBrowser> {
5345
const transport = await createTransport(options);
5446
const connection = new CRConnection(transport);
5547
const { browserContextIds } = await connection.rootSession.send('Target.getBrowserContexts');
@@ -309,24 +301,3 @@ export class CRBrowser extends platform.EventEmitter implements Browser {
309301
return !this._connection._closed;
310302
}
311303
}
312-
313-
export async function createTransport(options: CRConnectOptions): Promise<ConnectionTransport> {
314-
assert(Number(!!options.browserWSEndpoint) + Number(!!options.browserURL) + Number(!!options.transport) === 1, 'Exactly one of browserWSEndpoint, browserURL or transport must be passed to connect');
315-
let transport: ConnectionTransport | undefined;
316-
if (options.transport) {
317-
transport = options.transport;
318-
} else if (options.browserWSEndpoint) {
319-
transport = await platform.createWebSocketTransport(options.browserWSEndpoint);
320-
} else if (options.browserURL) {
321-
let connectionURL: string;
322-
try {
323-
const data = await platform.fetchUrl(new URL('/json/version', options.browserURL).href);
324-
connectionURL = JSON.parse(data).webSocketDebuggerUrl;
325-
} catch (e) {
326-
e.message = `Failed to fetch browser webSocket url from ${options.browserURL}: ` + e.message;
327-
throw e;
328-
}
329-
transport = await platform.createWebSocketTransport(connectionURL);
330-
}
331-
return SlowMoTransport.wrap(transport!, options.slowMo);
332-
}

src/firefox/ffBrowser.ts

Lines changed: 2 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -15,33 +15,26 @@
1515
* limitations under the License.
1616
*/
1717

18-
import { Browser } from '../browser';
18+
import { Browser, createTransport, ConnectOptions } from '../browser';
1919
import { BrowserContext, BrowserContextOptions } from '../browserContext';
2020
import { Events } from '../events';
2121
import { assert, helper, RegisteredListener } from '../helper';
2222
import * as network from '../network';
2323
import * as types from '../types';
2424
import { Page } from '../page';
25-
import { ConnectionTransport, SlowMoTransport } from '../transport';
2625
import { ConnectionEvents, FFConnection, FFSessionEvents } from './ffConnection';
2726
import { FFPage } from './ffPage';
2827
import * as platform from '../platform';
2928
import { Protocol } from './protocol';
3029

31-
export type FFConnectOptions = {
32-
slowMo?: number,
33-
browserWSEndpoint?: string;
34-
transport?: ConnectionTransport;
35-
};
36-
3730
export class FFBrowser extends platform.EventEmitter implements Browser {
3831
_connection: FFConnection;
3932
_targets: Map<string, Target>;
4033
private _defaultContext: BrowserContext;
4134
private _contexts: Map<string, BrowserContext>;
4235
private _eventListeners: RegisteredListener[];
4336

44-
static async connect(options: FFConnectOptions): Promise<FFBrowser> {
37+
static async connect(options: ConnectOptions): Promise<FFBrowser> {
4538
const transport = await createTransport(options);
4639
const connection = new FFConnection(transport);
4740
const {browserContextIds} = await connection.send('Target.getBrowserContexts');
@@ -292,13 +285,3 @@ class Target {
292285
return this._browser;
293286
}
294287
}
295-
296-
export async function createTransport(options: FFConnectOptions): Promise<ConnectionTransport> {
297-
assert(Number(!!options.browserWSEndpoint) + Number(!!options.transport) === 1, 'Exactly one of browserWSEndpoint or transport must be passed to connect');
298-
let transport: ConnectionTransport | undefined;
299-
if (options.transport)
300-
transport = options.transport;
301-
else if (options.browserWSEndpoint)
302-
transport = await platform.createWebSocketTransport(options.browserWSEndpoint);
303-
return SlowMoTransport.wrap(transport!, options.slowMo);
304-
}

src/server/browserApp.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/**
2+
* Copyright (c) Microsoft Corporation.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import { ChildProcess } from 'child_process';
18+
import { ConnectOptions } from '../browser';
19+
20+
export class BrowserApp {
21+
private _process: ChildProcess;
22+
private _gracefullyClose: () => Promise<void>;
23+
private _connectOptions: ConnectOptions;
24+
25+
constructor(process: ChildProcess, gracefullyClose: () => Promise<void>, connectOptions: ConnectOptions) {
26+
this._process = process;
27+
this._gracefullyClose = gracefullyClose;
28+
this._connectOptions = connectOptions;
29+
}
30+
31+
process(): ChildProcess {
32+
return this._process;
33+
}
34+
35+
wsEndpoint(): string | null {
36+
return this._connectOptions.browserWSEndpoint || null;
37+
}
38+
39+
connectOptions(): ConnectOptions {
40+
return this._connectOptions;
41+
}
42+
43+
async close(): Promise<void> {
44+
await this._gracefullyClose();
45+
}
46+
}

src/server/crPlaywright.ts

Lines changed: 23 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,15 @@ import { BrowserFetcher, BrowserFetcherOptions } from '../server/browserFetcher'
2323
import { DeviceDescriptors } from '../deviceDescriptors';
2424
import * as types from '../types';
2525
import { assert } from '../helper';
26-
import { CRBrowser, CRConnectOptions, createTransport } from '../chromium/crBrowser';
26+
import { CRBrowser } from '../chromium/crBrowser';
2727
import * as platform from '../platform';
2828
import { TimeoutError } from '../errors';
2929
import { launchProcess, waitForLine } from '../server/processLauncher';
30-
import { ChildProcess } from 'child_process';
3130
import { CRConnection } from '../chromium/crConnection';
3231
import { PipeTransport } from './pipeTransport';
3332
import { Playwright } from './playwright';
33+
import { createTransport, ConnectOptions } from '../browser';
34+
import { BrowserApp } from './browserApp';
3435

3536
export type SlowMoOptions = {
3637
slowMo?: number,
@@ -55,34 +56,6 @@ export type LaunchOptions = ChromiumArgOptions & SlowMoOptions & {
5556
pipe?: boolean,
5657
};
5758

58-
export class CRBrowserServer {
59-
private _process: ChildProcess;
60-
private _gracefullyClose: () => Promise<void>;
61-
private _connectOptions: CRConnectOptions;
62-
63-
constructor(process: ChildProcess, gracefullyClose: () => Promise<void>, connectOptions: CRConnectOptions) {
64-
this._process = process;
65-
this._gracefullyClose = gracefullyClose;
66-
this._connectOptions = connectOptions;
67-
}
68-
69-
process(): ChildProcess {
70-
return this._process;
71-
}
72-
73-
wsEndpoint(): string | null {
74-
return this._connectOptions.browserWSEndpoint || null;
75-
}
76-
77-
connectOptions(): CRConnectOptions {
78-
return this._connectOptions;
79-
}
80-
81-
async close(): Promise<void> {
82-
await this._gracefullyClose();
83-
}
84-
}
85-
8659
export class CRPlaywright implements Playwright {
8760
private _projectRoot: string;
8861
readonly _revision: string;
@@ -93,14 +66,14 @@ export class CRPlaywright implements Playwright {
9366
}
9467

9568
async launch(options?: LaunchOptions): Promise<CRBrowser> {
96-
const server = await this.launchServer(options);
97-
const browser = await CRBrowser.connect(server.connectOptions());
69+
const app = await this.launchBrowserApp(options);
70+
const browser = await CRBrowser.connect(app.connectOptions());
9871
// Hack: for typical launch scenario, ensure that close waits for actual process termination.
99-
browser.close = () => server.close();
72+
browser.close = () => app.close();
10073
return browser;
10174
}
10275

103-
async launchServer(options: LaunchOptions = {}): Promise<CRBrowserServer> {
76+
async launchBrowserApp(options: LaunchOptions = {}): Promise<BrowserApp> {
10477
const {
10578
ignoreDefaultArgs = false,
10679
args = [],
@@ -165,7 +138,7 @@ export class CRPlaywright implements Playwright {
165138
},
166139
});
167140

168-
let connectOptions: CRConnectOptions | undefined;
141+
let connectOptions: ConnectOptions | undefined;
169142
if (!usePipe) {
170143
const timeoutError = new TimeoutError(`Timed out after ${timeout} ms while trying to connect to Chromium! The only Chromium revision guaranteed to work is r${this._revision}`);
171144
const match = await waitForLine(launchedProcess, launchedProcess.stderr, /^DevTools listening on (ws:\/\/.*)$/, timeout, timeoutError);
@@ -175,10 +148,23 @@ export class CRPlaywright implements Playwright {
175148
const transport = new PipeTransport(launchedProcess.stdio[3] as NodeJS.WritableStream, launchedProcess.stdio[4] as NodeJS.ReadableStream);
176149
connectOptions = { slowMo, transport };
177150
}
178-
return new CRBrowserServer(launchedProcess, gracefullyClose, connectOptions);
151+
return new BrowserApp(launchedProcess, gracefullyClose, connectOptions);
179152
}
180153

181-
async connect(options: CRConnectOptions): Promise<CRBrowser> {
154+
async connect(options: ConnectOptions & { browserURL?: string }): Promise<CRBrowser> {
155+
if (options.browserURL) {
156+
assert(!options.browserWSEndpoint && !options.transport, 'Exactly one of browserWSEndpoint, browserURL or transport must be passed to connect');
157+
let connectionURL: string;
158+
try {
159+
const data = await platform.fetchUrl(new URL('/json/version', options.browserURL).href);
160+
connectionURL = JSON.parse(data).webSocketDebuggerUrl;
161+
} catch (e) {
162+
e.message = `Failed to fetch browser webSocket url from ${options.browserURL}: ` + e.message;
163+
throw e;
164+
}
165+
const transport = await platform.createWebSocketTransport(connectionURL);
166+
options = { ...options, transport };
167+
}
182168
return CRBrowser.connect(options);
183169
}
184170

0 commit comments

Comments
 (0)