Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(api): make pipe connection the default, expose webSocket launch option #562

Merged
merged 1 commit into from
Jan 24, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 11 additions & 11 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ An example of launching a browser executable and connecting to a [Browser] later
const playwright = require('playwright').webkit; // Or 'chromium' or 'firefox'.

(async () => {
const browserApp = await playwright.launchBrowserApp();
const browserApp = await playwright.launchBrowserApp({ webSocket: true });
const connectOptions = browserApp.connectOptions();
// Use connect options later to establish a connection.
const browser = await playwright.connect(connectOptions);
Expand Down Expand Up @@ -229,7 +229,7 @@ Closes the browser gracefully and makes sure the process is terminated.

#### browserApp.connectOptions()
- returns: <[Object]>
- `browserWSEndpoint` <?[string]> a [browser websocket endpoint](#browserwsendpoint) to connect to.
- `browserWSEndpoint` <?[string]> a browser websocket endpoint to connect to.
- `slowMo` <[number]>
- `transport` <[ConnectionTransport]> **Experimental** A custom transport object which should be used to connect.

Expand All @@ -243,8 +243,6 @@ This options object can be passed to [chromiumPlaywright.connect(options)](#chro

Browser websocket endpoint which can be used as an argument to [chromiumPlaywright.connect(options)](#chromiumplaywrightconnectoptions), [firefoxPlaywright.connect(options)](#firefoxplaywrightconnectoptions) or [webkitPlaywright.connect(options)](#webkitplaywrightconnectoptions) to establish connection to the browser.

Learn more about [Chromium devtools protocol](https://chromedevtools.github.io/devtools-protocol) and the [browser endpoint](https://chromedevtools.github.io/devtools-protocol/#how-do-i-access-the-browser-target).

### class: BrowserContext

* extends: [EventEmitter](https://nodejs.org/api/events.html#events_class_eventemitter)
Expand Down Expand Up @@ -3294,7 +3292,7 @@ If the function passed to the `worker.evaluateHandle` returns a [Promise], then

#### chromiumPlaywright.connect(options)
- `options` <[Object]>
- `browserWSEndpoint` <?[string]> a [browser websocket endpoint](#browserwsendpoint) to connect to.
- `browserWSEndpoint` <?[string]> a browser websocket endpoint to connect to.
- `browserURL` <?[string]> a browser url to connect to, in format `http://${host}:${port}`. Use interchangeably with `browserWSEndpoint` to let Playwright fetch it from [metadata endpoint](https://chromedevtools.github.io/devtools-protocol/#how-do-i-access-the-browser-target).
- `slowMo` <[number]> Slows down Playwright operations by the specified amount of milliseconds. Useful so that you can see what is going on.
- `transport` <[ConnectionTransport]> **Experimental** Specify a custom transport object for Playwright to use.
Expand Down Expand Up @@ -3327,7 +3325,7 @@ The default flags that Chromium will be launched with.
- `userDataDir` <[string]> Path to a [User Data Directory](https://chromium.googlesource.com/chromium/src/+/master/docs/user_data_dir.md).
- `env` <[Object]> Specify environment variables that will be visible to the browser. Defaults to `process.env`.
- `devtools` <[boolean]> Whether to auto-open a DevTools panel for each tab. If this option is `true`, the `headless` option will be set `false`.
- `pipe` <[boolean]> Connects to the browser over a pipe instead of a WebSocket. Defaults to `false`.
- `webSocket` <[boolean]> Connects to the browser over a WebSocket instead of a pipe. Defaults to `false`.
dgozman marked this conversation as resolved.
Show resolved Hide resolved
- returns: <[Promise]<[ChromiumBrowser]>> Promise which resolves to browser instance.


Expand Down Expand Up @@ -3361,7 +3359,7 @@ const browser = await playwright.launch({
- `userDataDir` <[string]> Path to a [User Data Directory](https://chromium.googlesource.com/chromium/src/+/master/docs/user_data_dir.md).
- `env` <[Object]> Specify environment variables that will be visible to the browser. Defaults to `process.env`.
- `devtools` <[boolean]> Whether to auto-open a DevTools panel for each tab. If this option is `true`, the `headless` option will be set `false`.
- `pipe` <[boolean]> Connects to the browser over a pipe instead of a WebSocket. Defaults to `false`.
- `webSocket` <[boolean]> Connects to the browser over a WebSocket instead of a pipe. Defaults to `false`.
- returns: <[Promise]<[BrowserApp]>> Promise which resolves to browser server instance.

### class: ChromiumBrowser
Expand Down Expand Up @@ -3554,7 +3552,7 @@ Identifies what kind of target this is. Can be `"page"`, [`"background_page"`](h

#### firefoxPlaywright.connect(options)
- `options` <[Object]>
- `browserWSEndpoint` <?[string]> a [browser websocket endpoint](#browserwsendpoint) to connect to.
- `browserWSEndpoint` <?[string]> a browser websocket endpoint to connect to.
- `slowMo` <[number]> Slows down Playwright operations by the specified amount of milliseconds. Useful so that you can see what is going on.
- `transport` <[ConnectionTransport]> **Experimental** Specify a custom transport object for Playwright to use.
- returns: <[Promise]<[FirefoxBrowser]>>
Expand Down Expand Up @@ -3584,6 +3582,7 @@ The default flags that Firefox will be launched with.
- `dumpio` <[boolean]> Whether to pipe the browser process stdout and stderr into `process.stdout` and `process.stderr`. Defaults to `false`.
- `userDataDir` <[string]> Path to a [User Data Directory](https://developer.mozilla.org/en-US/docs/Mozilla/Command_Line_Options#User_Profile).
- `env` <[Object]> Specify environment variables that will be visible to the browser. Defaults to `process.env`.
- `webSocket` <[boolean]> Connects to the browser over a WebSocket instead of a pipe. Defaults to `false`.
- returns: <[Promise]<[FirefoxBrowser]>> Promise which resolves to browser instance.


Expand All @@ -3608,6 +3607,7 @@ const browser = await playwright.launch({
- `dumpio` <[boolean]> Whether to pipe the browser process stdout and stderr into `process.stdout` and `process.stderr`. Defaults to `false`.
- `userDataDir` <[string]> Path to a [User Data Directory](https://developer.mozilla.org/en-US/docs/Mozilla/Command_Line_Options#User_Profile).
- `env` <[Object]> Specify environment variables that will be visible to the browser. Defaults to `process.env`.
- `webSocket` <[boolean]> Connects to the browser over a WebSocket instead of a pipe. Defaults to `false`.
- returns: <[Promise]<[BrowserApp]>> Promise which resolves to browser server instance.

### class: FirefoxBrowser
Expand All @@ -3630,7 +3630,7 @@ Firefox browser instance does not expose Firefox-specific features.

#### webkitPlaywright.connect(options)
- `options` <[Object]>
- `browserWSEndpoint` <?[string]> a [browser websocket endpoint](#browserwsendpoint) to connect to.
- `browserWSEndpoint` <?[string]> a browser websocket endpoint to connect to.
- `slowMo` <[number]> Slows down Playwright operations by the specified amount of milliseconds. Useful so that you can see what is going on.
- `transport` <[ConnectionTransport]> **Experimental** Specify a custom transport object for Playwright to use.
- returns: <[Promise]<[WebKitBrowser]>>
Expand Down Expand Up @@ -3660,7 +3660,7 @@ The default flags that WebKit will be launched with.
- `timeout` <[number]> Maximum time in milliseconds to wait for the browser instance to start. Defaults to `30000` (30 seconds). Pass `0` to disable timeout.
- `dumpio` <[boolean]> Whether to pipe the browser process stdout and stderr into `process.stdout` and `process.stderr`. Defaults to `false`.
- `env` <[Object]> Specify environment variables that will be visible to the browser. Defaults to `process.env`.
- `pipe` <[boolean]> Connects to the browser over a pipe instead of a WebSocket. Defaults to `false`.
- `webSocket` <[boolean]> Connects to the browser over a WebSocket instead of a pipe. Defaults to `false`.
- returns: <[Promise]<[WebKitBrowser]>> Promise which resolves to browser instance.


Expand All @@ -3685,7 +3685,7 @@ const browser = await playwright.launch({
- `timeout` <[number]> Maximum time in milliseconds to wait for the browser instance to start. Defaults to `30000` (30 seconds). Pass `0` to disable timeout.
- `dumpio` <[boolean]> Whether to pipe the browser process stdout and stderr into `process.stdout` and `process.stderr`. Defaults to `false`.
- `env` <[Object]> Specify environment variables that will be visible to the browser. Defaults to `process.env`.
- `pipe` <[boolean]> Connects to the browser over a pipe instead of a WebSocket. Defaults to `false`.
- `webSocket` <[boolean]> Connects to the browser over a WebSocket instead of a pipe. Defaults to `false`.
- returns: <[Promise]<[BrowserApp]>> Promise which resolves to browser server instance.

### class: WebKitBrowser
Expand Down
6 changes: 6 additions & 0 deletions src/chromium/crConnection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ export const ConnectionEvents = {
Disconnected: Symbol('ConnectionEvents.Disconnected')
};

// CRPlaywright uses this special id to issue Browser.close command which we
// should ignore.
export const kBrowserCloseMessageId = -9999;

export class CRConnection extends platform.EventEmitter {
private _lastId = 0;
private _transport: ConnectionTransport;
Expand Down Expand Up @@ -64,6 +68,8 @@ export class CRConnection extends platform.EventEmitter {
async _onMessage(message: string) {
debugProtocol('◀ RECV ' + message);
const object = JSON.parse(message);
if (object.id === kBrowserCloseMessageId)
return;
if (object.method === 'Target.attachedToTarget') {
const sessionId = object.params.sessionId;
const session = new CRSession(this, object.params.targetInfo.type, sessionId);
Expand Down
6 changes: 6 additions & 0 deletions src/firefox/ffConnection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ export const ConnectionEvents = {
Disconnected: Symbol('Disconnected'),
};

// FFPlaywright uses this special id to issue Browser.close command which we
// should ignore.
export const kBrowserCloseMessageId = -9999;

export class FFConnection extends platform.EventEmitter {
private _lastId: number;
private _callbacks: Map<number, {resolve: Function, reject: Function, error: Error, method: string}>;
Expand Down Expand Up @@ -89,6 +93,8 @@ export class FFConnection extends platform.EventEmitter {
async _onMessage(message: string) {
debugProtocol('◀ RECV ' + message);
const object = JSON.parse(message);
if (object.id === kBrowserCloseMessageId)
return;
if (object.method === 'Target.attachedToTarget') {
const sessionId = object.params.sessionId;
const session = new FFSession(this, object.params.targetInfo.type, sessionId, message => this._rawSend({...message, sessionId}));
Expand Down
18 changes: 11 additions & 7 deletions src/server/crPlaywright.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import { CRBrowser } from '../chromium/crBrowser';
import * as platform from '../platform';
import { TimeoutError } from '../errors';
import { launchProcess, waitForLine } from '../server/processLauncher';
import { CRConnection } from '../chromium/crConnection';
import { kBrowserCloseMessageId } from '../chromium/crConnection';
import { PipeTransport } from './pipeTransport';
import { Playwright } from './playwright';
import { createTransport, ConnectOptions } from '../browser';
Expand All @@ -53,7 +53,7 @@ export type LaunchOptions = ChromiumArgOptions & SlowMoOptions & {
timeout?: number,
dumpio?: boolean,
env?: {[key: string]: string} | undefined,
pipe?: boolean,
webSocket?: boolean,
};

export class CRPlaywright implements Playwright {
Expand All @@ -79,7 +79,7 @@ export class CRPlaywright implements Playwright {
args = [],
dumpio = false,
executablePath = null,
pipe = false,
webSocket = false,
env = process.env,
handleSIGINT = true,
handleSIGTERM = true,
Expand All @@ -99,7 +99,7 @@ export class CRPlaywright implements Playwright {
let temporaryUserDataDir: string | null = null;

if (!chromeArguments.some(argument => argument.startsWith('--remote-debugging-')))
chromeArguments.push(pipe ? '--remote-debugging-pipe' : '--remote-debugging-port=0');
chromeArguments.push(webSocket ? '--remote-debugging-port=0' : '--remote-debugging-pipe');
if (!chromeArguments.some(arg => arg.startsWith('--user-data-dir'))) {
temporaryUserDataDir = await mkdtempAsync(CHROMIUM_PROFILE_PATH);
chromeArguments.push(`--user-data-dir=${temporaryUserDataDir}`);
Expand All @@ -114,6 +114,8 @@ export class CRPlaywright implements Playwright {
}

const usePipe = chromeArguments.includes('--remote-debugging-pipe');
if (usePipe && webSocket)
throw new Error(`Argument "--remote-debugging-pipe" is not compatible with "webSocket" launch option.`);

const { launchedProcess, gracefullyClose } = await launchProcess({
executablePath: chromeExecutable!,
Expand All @@ -131,10 +133,10 @@ export class CRPlaywright implements Playwright {

// 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 is tolerant to unknown responses.
// our connection ignores kBrowserCloseMessageId.
const transport = await createTransport(connectOptions);
const connection = new CRConnection(transport);
connection.rootSession.send('Browser.close');
const message = { method: 'Browser.close', id: kBrowserCloseMessageId };
transport.send(JSON.stringify(message));
},
});

Expand All @@ -152,6 +154,8 @@ export class CRPlaywright implements Playwright {
}

async connect(options: ConnectOptions & { browserURL?: string }): Promise<CRBrowser> {
if (options.transport && options.transport.onmessage)
throw new Error('Transport is already in use');
if (options.browserURL) {
assert(!options.browserWSEndpoint && !options.transport, 'Exactly one of browserWSEndpoint, browserURL or transport must be passed to connect');
let connectionURL: string;
Expand Down
23 changes: 16 additions & 7 deletions src/server/ffPlaywright.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { DeviceDescriptors } from '../deviceDescriptors';
import { launchProcess, waitForLine } from './processLauncher';
import * as types from '../types';
import * as platform from '../platform';
import { FFConnection } from '../firefox/ffConnection';
import { kBrowserCloseMessageId } from '../firefox/ffConnection';
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
Expand Down Expand Up @@ -51,6 +51,7 @@ export type LaunchOptions = FirefoxArgOptions & SlowMoOptions & {
timeout?: number,
dumpio?: boolean,
env?: {[key: string]: string} | undefined,
webSocket?: boolean,
};

export class FFPlaywright implements Playwright {
Expand Down Expand Up @@ -82,6 +83,7 @@ export class FFPlaywright implements Playwright {
handleSIGTERM = true,
slowMo = 0,
timeout = 30000,
webSocket = false,
} = options;

const firefoxArguments = [];
Expand Down Expand Up @@ -129,22 +131,29 @@ export class FFPlaywright implements Playwright {
if (!connectOptions)
return Promise.reject();
// We try to gracefully close to prevent crash reporting and core dumps.
// Note that we don't support pipe yet, so there is no issue
// with reusing the same connection - we can always create a new one.
// Note that it's fine to reuse the pipe transport, since
// our connection ignores kBrowserCloseMessageId.
const transport = await createTransport(connectOptions);
const connection = new FFConnection(transport);
connection.send('Browser.close');
const message = { method: 'Browser.close', params: {}, id: kBrowserCloseMessageId };
transport.send(JSON.stringify(message));
},
});

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 url = match[1];
connectOptions = { browserWSEndpoint: url, slowMo };
const browserWSEndpoint = match[1];
if (webSocket) {
connectOptions = { browserWSEndpoint, slowMo };
} else {
const transport = await platform.createWebSocketTransport(browserWSEndpoint);
connectOptions = { transport, slowMo };
}
return new BrowserApp(launchedProcess, gracefullyClose, connectOptions);
}

async connect(options: ConnectOptions): Promise<FFBrowser> {
if (options.transport && options.transport.onmessage)
throw new Error('Transport is already in use');
return FFBrowser.connect(options);
}

Expand Down
10 changes: 7 additions & 3 deletions src/server/wkPlaywright.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export type LaunchOptions = WebKitArgOptions & SlowMoOptions & {
timeout?: number,
dumpio?: boolean,
env?: {[key: string]: string} | undefined,
pipe?: boolean,
webSocket?: boolean,
};

export class WKPlaywright implements Playwright {
Expand Down Expand Up @@ -87,7 +87,7 @@ export class WKPlaywright implements Playwright {
handleSIGTERM = true,
handleSIGHUP = true,
slowMo = 0,
pipe = false,
webSocket = false,
} = options;

const webkitArguments = [];
Expand Down Expand Up @@ -132,6 +132,8 @@ export class WKPlaywright implements Playwright {
if (!transport)
return Promise.reject();
// 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 message = JSON.stringify({method: 'Browser.close', params: {}, id: kBrowserCloseMessageId});
transport.send(message);
},
Expand All @@ -140,7 +142,7 @@ export class WKPlaywright implements Playwright {
transport = new PipeTransport(launchedProcess.stdio[3] as NodeJS.WritableStream, launchedProcess.stdio[4] as NodeJS.ReadableStream);

let connectOptions: ConnectOptions;
if (!pipe) {
if (webSocket) {
const browserWSEndpoint = wrapTransportWithWebSocket(transport);
connectOptions = { browserWSEndpoint, slowMo };
} else {
Expand All @@ -150,6 +152,8 @@ export class WKPlaywright implements Playwright {
}

async connect(options: ConnectOptions): Promise<WKBrowser> {
if (options.transport && options.transport.onmessage)
throw new Error('Transport is already in use');
return WKBrowser.connect(options);
}

Expand Down
1 change: 1 addition & 0 deletions test/chromium/browser.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
const options = Object.assign({}, defaultBrowserOptions, {
// Disable DUMPIO to cleanly read stdout.
dumpio: false,
webSocket: true,
});
const res = spawn('node', [path.join(__dirname, '..', 'fixtures', 'closeme.js'), playwrightPath, product, JSON.stringify(options)]);
let wsEndPointCallback;
Expand Down
14 changes: 0 additions & 14 deletions test/chromium/chromium.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,20 +21,6 @@ module.exports.describe = function({testRunner, expect, playwright, FFOX, CHROMI
const {it, fit, xit, dit} = testRunner;
const {beforeAll, beforeEach, afterAll, afterEach} = testRunner;

describe('Chromium', function() {
it('should work across sessions', async function({browserApp, server, browser, newContext}) {
expect(browser.browserContexts().length).toBe(2);
await newContext();
expect(browser.browserContexts().length).toBe(3);
const remoteBrowser = await playwright.connect({
browserWSEndpoint: browserApp.wsEndpoint()
});
const contexts = remoteBrowser.browserContexts();
expect(contexts.length).toBe(3);
remoteBrowser.disconnect();
});
});

describe('Target', function() {
it('Chromium.targets should return all of the targets', async({page, server, browser}) => {
// The pages will be the testing page and the original newtab page
Expand Down
Loading