fix(api): BrowserServer -> BrowserApp, resuse it between browsers (#599)

This commit is contained in:
Dmitry Gozman 2020-01-23 14:40:37 -08:00 committed by GitHub
parent b4209e9dc8
commit ac2ba3cbd9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 263 additions and 391 deletions

View file

@ -7,6 +7,7 @@
<!-- GEN:toc-top-level -->
- [class: Playwright](#class-playwright)
- [class: Browser](#class-browser)
- [class: BrowserApp](#class-browserapp)
- [class: BrowserContext](#class-browsercontext)
- [class: ConsoleMessage](#class-consolemessage)
- [class: Dialog](#class-dialog)
@ -24,15 +25,12 @@
- [class: Worker](#class-worker)
- [class: ChromiumPlaywright](#class-chromiumplaywright)
- [class: ChromiumBrowser](#class-chromiumbrowser)
- [class: ChromiumBrowserServer](#class-chromiumbrowserserver)
- [class: ChromiumSession](#class-chromiumsession)
- [class: ChromiumTarget](#class-chromiumtarget)
- [class: FirefoxPlaywright](#class-firefoxplaywright)
- [class: FirefoxBrowser](#class-firefoxbrowser)
- [class: FirefoxBrowserServer](#class-firefoxbrowserserver)
- [class: WebKitPlaywright](#class-webkitplaywright)
- [class: WebKitBrowser](#class-webkitbrowser)
- [class: WebKitBrowserServer](#class-webkitbrowserserver)
- [Working with selectors](#working-with-selectors)
- [Working with Chrome Extensions](#working-with-chrome-extensions)
- [Downloaded browsers](#downloaded-browsers)
@ -128,17 +126,17 @@ const playwright = require('playwright').firefox; // Or 'chromium' or 'webkit'.
})();
```
An example of disconnecting from and reconnecting to a [Browser]:
An example of launching a browser executable and connecting to a [Browser] later:
```js
const playwright = require('playwright').webkit; // Or 'chromium' or 'firefox'.
(async () => {
const browserServer = await playwright.launchServer();
const browserWSEndpoint = browserServer.wsEndpoint();
// Use the endpoint to establish a connection
const browser = await playwright.connect({browserWSEndpoint});
// Close Chromium
await browser.close();
const browserApp = await playwright.launchBrowserApp();
const connectOptions = browserApp.connectOptions();
// Use connect options later to establish a connection.
const browser = await playwright.connect(connectOptions);
// Close browser instance.
await browserApp.close();
})();
```
<!-- GEN:toc -->
@ -214,6 +212,38 @@ Creates a new browser context. It won't share cookies/cache with other browser c
})();
```
### class: BrowserApp
<!-- GEN:toc -->
- [browserApp.close()](#browserappclose)
- [browserApp.connectOptions()](#browserappconnectoptions)
- [browserApp.process()](#browserappprocess)
- [browserApp.wsEndpoint()](#browserappwsendpoint)
<!-- GEN:stop -->
#### browserApp.close()
- returns: <[Promise]>
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.
- `slowMo` <[number]>
- `transport` <[ConnectionTransport]> **Experimental** A custom transport object which should be used to connect.
This options object can be passed to [chromiumPlaywright.connect(options)](#chromiumplaywrightconnectoptions), [firefoxPlaywright.connect(options)](#firefoxplaywrightconnectoptions) or [webkitPlaywright.connect(options)](#webkitplaywrightconnectoptions) to establish connection to the browser.
#### browserApp.process()
- returns: <?[ChildProcess]> Spawned browser server process.
#### browserApp.wsEndpoint()
- returns: <?[string]> Browser websocket url.
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)
@ -3191,7 +3221,7 @@ If the function passed to the `worker.evaluateHandle` returns a [Promise], then
- [chromiumPlaywright.connect(options)](#chromiumplaywrightconnectoptions)
- [chromiumPlaywright.defaultArgs([options])](#chromiumplaywrightdefaultargsoptions)
- [chromiumPlaywright.launch([options])](#chromiumplaywrightlaunchoptions)
- [chromiumPlaywright.launchServer([options])](#chromiumplaywrightlaunchserveroptions)
- [chromiumPlaywright.launchBrowserApp([options])](#chromiumplaywrightlaunchbrowserappoptions)
<!-- GEN:stop -->
#### chromiumPlaywright.connect(options)
@ -3248,7 +3278,7 @@ const browser = await playwright.launch({
>
> See [`this article`](https://www.howtogeek.com/202825/what%E2%80%99s-the-difference-between-chromium-and-chrome/) for a description of the differences between Chromium and Chrome. [`This article`](https://chromium.googlesource.com/chromium/src/+/lkgr/docs/chromium_browser_vs_google_chrome.md) describes some differences for Linux users.
#### chromiumPlaywright.launchServer([options])
#### chromiumPlaywright.launchBrowserApp([options])
- `options` <[Object]> Set of configurable options to set on the browser. Can have the following fields:
- `headless` <[boolean]> Whether to run Chromium in [headless mode](https://developers.google.com/web/updates/2017/04/headless-chrome). Defaults to `true` unless the `devtools` option is `true`.
- `executablePath` <[string]> Path to a Chromium or Chrome executable to run instead of the bundled Chromium. If `executablePath` is a relative path, then it is resolved relative to [current working directory](https://nodejs.org/api/process.html#process_process_cwd). **BEWARE**: Playwright is only [guaranteed to work](https://github.com/Microsoft/playwright/#q-why-doesnt-playwright-vxxx-work-with-chromium-vyyy) with the bundled Chromium, use at your own risk.
@ -3264,7 +3294,7 @@ const browser = await playwright.launch({
- `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`.
- returns: <[Promise]<[ChromiumBrowserServer]>> Promise which resolves to browser server instance.
- returns: <[Promise]<[BrowserApp]>> Promise which resolves to browser server instance.
### class: ChromiumBrowser
@ -3363,39 +3393,6 @@ await page.evaluate(() => window.open('https://www.example.com/'));
const newWindowTarget = await browser.chromium.waitForTarget(target => target.url() === 'https://www.example.com/');
```
### class: ChromiumBrowserServer
<!-- GEN:toc -->
- [chromiumBrowserServer.close()](#chromiumbrowserserverclose)
- [chromiumBrowserServer.connectOptions()](#chromiumbrowserserverconnectoptions)
- [chromiumBrowserServer.process()](#chromiumbrowserserverprocess)
- [chromiumBrowserServer.wsEndpoint()](#chromiumbrowserserverwsendpoint)
<!-- GEN:stop -->
#### chromiumBrowserServer.close()
- returns: <[Promise]>
Closes the browser gracefully and makes sure the process is terminated.
#### chromiumBrowserServer.connectOptions()
- returns: <[Object]>
- `browserWSEndpoint` <?[string]> a [browser websocket endpoint](#browserwsendpoint) 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]>
- `transport` <[ConnectionTransport]> **Experimental** A custom transport object which should be used to connect.
This options object can be passed to [chromiumPlaywright.connect(options)](#chromiumplaywrightconnectoptions) to establish connection to the browser.
#### chromiumBrowserServer.process()
- returns: <?[ChildProcess]> Spawned browser server process.
#### chromiumBrowserServer.wsEndpoint()
- returns: <?[string]> Browser websocket url.
Browser websocket endpoint which can be used as an argument to [chromiumPlaywright.connect(options)](#chromiumplaywrightconnectoptions).
Learn more about the [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: ChromiumSession
* extends: [EventEmitter](https://nodejs.org/api/events.html#events_class_eventemitter)
@ -3484,7 +3481,7 @@ Identifies what kind of target this is. Can be `"page"`, [`"background_page"`](h
- [firefoxPlaywright.connect(options)](#firefoxplaywrightconnectoptions)
- [firefoxPlaywright.defaultArgs([options])](#firefoxplaywrightdefaultargsoptions)
- [firefoxPlaywright.launch([options])](#firefoxplaywrightlaunchoptions)
- [firefoxPlaywright.launchServer([options])](#firefoxplaywrightlaunchserveroptions)
- [firefoxPlaywright.launchBrowserApp([options])](#firefoxplaywrightlaunchbrowserappoptions)
<!-- GEN:stop -->
#### firefoxPlaywright.connect(options)
@ -3529,7 +3526,7 @@ const browser = await playwright.launch({
});
```
#### firefoxPlaywright.launchServer([options])
#### firefoxPlaywright.launchBrowserApp([options])
- `options` <[Object]> Set of configurable options to set on the browser. Can have the following fields:
- `headless` <[boolean]> Whether to run Firefox in [headless mode](https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/Headless_mode). Defaults to `true`.
- `executablePath` <[string]> Path to a Firefox executable to run instead of the bundled Firefox. If `executablePath` is a relative path, then it is resolved relative to [current working directory](https://nodejs.org/api/process.html#process_process_cwd). **BEWARE**: Playwright is only guaranteed to work with the bundled Firefox, use at your own risk.
@ -3543,7 +3540,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`.
- returns: <[Promise]<[FirefoxBrowserServer]>> Promise which resolves to browser server instance.
- returns: <[Promise]<[BrowserApp]>> Promise which resolves to browser server instance.
### class: FirefoxBrowser
@ -3551,35 +3548,6 @@ const browser = await playwright.launch({
Firefox browser instance does not expose Firefox-specific features.
### class: FirefoxBrowserServer
<!-- GEN:toc -->
- [firefoxBrowserServer.close()](#firefoxbrowserserverclose)
- [firefoxBrowserServer.connectOptions()](#firefoxbrowserserverconnectoptions)
- [firefoxBrowserServer.process()](#firefoxbrowserserverprocess)
- [firefoxBrowserServer.wsEndpoint()](#firefoxbrowserserverwsendpoint)
<!-- GEN:stop -->
#### firefoxBrowserServer.close()
- returns: <[Promise]>
Closes the browser gracefully and makes sure the process is terminated.
#### firefoxBrowserServer.connectOptions()
- returns: <[Object]>
- `browserWSEndpoint` <?[string]> a [browser websocket endpoint](#browserwsendpoint) to connect to.
- `slowMo` <[number]>
- `transport` <[ConnectionTransport]> **Experimental** A custom transport object which should be used to connect.
This options object can be passed to [firefoxPlaywright.connect(options)](#firefoxplaywrightconnectoptions) to establish connection to the browser.
#### firefoxBrowserServer.process()
- returns: <?[ChildProcess]> Spawned browser server process.
#### firefoxBrowserServer.wsEndpoint()
- returns: <?[string]> Browser websocket url.
Browser websocket endpoint which can be used as an argument to [firefoxPlaywright.connect(options)](#firefoxplaywrightconnectoptions).
### class: WebKitPlaywright
@ -3589,7 +3557,7 @@ Browser websocket endpoint which can be used as an argument to [firefoxPlaywrigh
- [webkitPlaywright.connect(options)](#webkitplaywrightconnectoptions)
- [webkitPlaywright.defaultArgs([options])](#webkitplaywrightdefaultargsoptions)
- [webkitPlaywright.launch([options])](#webkitplaywrightlaunchoptions)
- [webkitPlaywright.launchServer([options])](#webkitplaywrightlaunchserveroptions)
- [webkitPlaywright.launchBrowserApp([options])](#webkitplaywrightlaunchbrowserappoptions)
<!-- GEN:stop -->
#### webkitPlaywright.connect(options)
@ -3635,7 +3603,7 @@ const browser = await playwright.launch({
});
```
#### webkitPlaywright.launchServer([options])
#### webkitPlaywright.launchBrowserApp([options])
- `options` <[Object]> Set of configurable options to set on the browser. Can have the following fields:
- `headless` <[boolean]> Whether to run WebKit in headless mode. Defaults to `true`.
- `executablePath` <[string]> Path to a WebKit executable to run instead of the bundled WebKit. If `executablePath` is a relative path, then it is resolved relative to [current working directory](https://nodejs.org/api/process.html#process_process_cwd). **BEWARE**: Playwright is only guaranteed to work with the bundled WebKit, use at your own risk.
@ -3650,7 +3618,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`.
- `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`.
- returns: <[Promise]<[WebKitBrowserServer]>> Promise which resolves to browser server instance.
- returns: <[Promise]<[BrowserApp]>> Promise which resolves to browser server instance.
### class: WebKitBrowser
@ -3658,35 +3626,6 @@ const browser = await playwright.launch({
WebKit browser instance does not expose WebKit-specific features.
### class: WebKitBrowserServer
<!-- GEN:toc -->
- [webKitBrowserServer.close()](#webkitbrowserserverclose)
- [webKitBrowserServer.connectOptions()](#webkitbrowserserverconnectoptions)
- [webKitBrowserServer.process()](#webkitbrowserserverprocess)
- [webKitBrowserServer.wsEndpoint()](#webkitbrowserserverwsendpoint)
<!-- GEN:stop -->
#### webKitBrowserServer.close()
- returns: <[Promise]>
Closes the browser gracefully and makes sure the process is terminated.
#### webKitBrowserServer.connectOptions()
- returns: <[Object]>
- `slowMo` <[number]>
- `transport` <[ConnectionTransport]> **Experimental** A custom transport object which should be used to connect.
This options object can be passed to [webKitPlaywright.connect(options)](#webkitplaywrightconnectoptions) to establish connection to the browser.
#### webKitBrowserServer.process()
- returns: <?[ChildProcess]> Spawned browser server process.
#### webKitBrowserServer.wsEndpoint()
- returns: <?[string]> Browser websocket url.
Browser websocket endpoint which can be used as an argument to [webkitPlaywright.connect(options)](#webkitplaywrightconnectoptions).
### Working with selectors
Selector describes an element in the page. It can be used to obtain `ElementHandle` (see [page.$()](#pageselector) for example) or shortcut element operations to avoid intermediate handle (see [page.click()](#pageclickselector-options) for example).
@ -3768,12 +3707,12 @@ During installation Playwright downloads browser executables, according to revis
[Accessibility]: #class-accessibility "Accessibility"
[Array]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array "Array"
[Body]: #class-body "Body"
[BrowserApp]: #class-browserapp "BrowserApp"
[BrowserContext]: #class-browsercontext "BrowserContext"
[Browser]: #class-browser "Browser"
[Buffer]: https://nodejs.org/api/buffer.html#buffer_class_buffer "Buffer"
[ChildProcess]: https://nodejs.org/api/child_process.html "ChildProcess"
[ChromiumBrowser]: #class-chromiumbrowser "ChromiumBrowser"
[ChromiumBrowserServer]: #class-chromiumbrowserserver "ChromiumBrowserServer"
[ChromiumPlaywright]: #class-chromiumplaywright "ChromiumPlaywright"
[ChromiumSession]: #class-chromiumsession "ChromiumSession"
[ChromiumTarget]: #class-chromiumtarget "ChromiumTarget"
@ -3787,7 +3726,6 @@ During installation Playwright downloads browser executables, according to revis
[File]: #class-file "https://developer.mozilla.org/en-US/docs/Web/API/File"
[FileChooser]: #class-filechooser "FileChooser"
[FirefoxBrowser]: #class-firefoxbrowser "FirefoxBrowser"
[FirefoxBrowserServer]: #class-firefoxbrowserserver "FirefoxBrowserServer"
[FirefoxPlaywright]: #class-firefoxplaywright "FirefoxPlaywright"
[Frame]: #class-frame "Frame"
[JSHandle]: #class-jshandle "JSHandle"
@ -3809,7 +3747,6 @@ During installation Playwright downloads browser executables, according to revis
[USKeyboardLayout]: ../lib/USKeyboardLayout.js "USKeyboardLayout"
[UnixTime]: https://en.wikipedia.org/wiki/Unix_time "Unix Time"
[WebKitBrowser]: #class-webkitbrowser "WebKitBrowser"
[WebKitBrowserServer]: #class-webkitbrowserserver "WebKitBrowserServer"
[WebKitPlaywright]: #class-webkitplaywright "WebKitPlaywright"
[Worker]: #class-worker "Worker"
[boolean]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Boolean_type "Boolean"

View file

@ -36,6 +36,7 @@ export { FFBrowser as FirefoxBrowser } from './firefox/ffBrowser';
export { WKBrowser as WebKitBrowser } from './webkit/wkBrowser';
export { Playwright } from './server/playwright';
export { CRPlaywright as ChromiumPlaywright, CRBrowserServer as ChromiumBrowserServer } from './server/crPlaywright';
export { FFPlaywright as FirefoxPlaywright, FFBrowserServer as FirefoxBrowserServer } from './server/ffPlaywright';
export { WKPlaywright as WebKitPlaywright, WKBrowserServer as WebKitBrowserServer } from './server/wkPlaywright';
export { BrowserApp } from './server/browserApp';
export { CRPlaywright as ChromiumPlaywright } from './server/crPlaywright';
export { FFPlaywright as FirefoxPlaywright } from './server/ffPlaywright';
export { WKPlaywright as WebKitPlaywright } from './server/wkPlaywright';

View file

@ -15,7 +15,9 @@
*/
import { BrowserContext, BrowserContextOptions } from './browserContext';
import { ConnectionTransport, SlowMoTransport } from './transport';
import * as platform from './platform';
import { assert } from './helper';
export interface Browser extends platform.EventEmitterType {
newContext(options?: BrowserContextOptions): Promise<BrowserContext>;
@ -26,3 +28,19 @@ export interface Browser extends platform.EventEmitterType {
isConnected(): boolean;
close(): Promise<void>;
}
export type ConnectOptions = {
slowMo?: number,
browserWSEndpoint?: string;
transport?: ConnectionTransport;
};
export async function createTransport(options: ConnectOptions): Promise<ConnectionTransport> {
assert(Number(!!options.browserWSEndpoint) + Number(!!options.transport) === 1, 'Exactly one of browserWSEndpoint or transport must be passed to connect');
let transport: ConnectionTransport | undefined;
if (options.transport)
transport = options.transport;
else if (options.browserWSEndpoint)
transport = await platform.createWebSocketTransport(options.browserWSEndpoint);
return SlowMoTransport.wrap(transport!, options.slowMo);
}

View file

@ -24,20 +24,12 @@ import { Page, Worker } from '../page';
import { CRTarget } from './crTarget';
import { Protocol } from './protocol';
import { CRPage } from './crPage';
import { Browser } from '../browser';
import { Browser, createTransport, ConnectOptions } from '../browser';
import * as network from '../network';
import * as types from '../types';
import * as platform from '../platform';
import { ConnectionTransport, SlowMoTransport } from '../transport';
import { readProtocolStream } from './crProtocolHelper';
export type CRConnectOptions = {
slowMo?: number,
browserWSEndpoint?: string;
browserURL?: string;
transport?: ConnectionTransport;
};
export class CRBrowser extends platform.EventEmitter implements Browser {
_connection: CRConnection;
_client: CRSession;
@ -49,7 +41,7 @@ export class CRBrowser extends platform.EventEmitter implements Browser {
private _tracingPath: string | null = '';
private _tracingClient: CRSession | undefined;
static async connect(options: CRConnectOptions): Promise<CRBrowser> {
static async connect(options: ConnectOptions): Promise<CRBrowser> {
const transport = await createTransport(options);
const connection = new CRConnection(transport);
const { browserContextIds } = await connection.rootSession.send('Target.getBrowserContexts');
@ -309,24 +301,3 @@ export class CRBrowser extends platform.EventEmitter implements Browser {
return !this._connection._closed;
}
}
export async function createTransport(options: CRConnectOptions): Promise<ConnectionTransport> {
assert(Number(!!options.browserWSEndpoint) + Number(!!options.browserURL) + Number(!!options.transport) === 1, 'Exactly one of browserWSEndpoint, browserURL or transport must be passed to connect');
let transport: ConnectionTransport | undefined;
if (options.transport) {
transport = options.transport;
} else if (options.browserWSEndpoint) {
transport = await platform.createWebSocketTransport(options.browserWSEndpoint);
} else if (options.browserURL) {
let connectionURL: string;
try {
const data = await platform.fetchUrl(new URL('/json/version', options.browserURL).href);
connectionURL = JSON.parse(data).webSocketDebuggerUrl;
} catch (e) {
e.message = `Failed to fetch browser webSocket url from ${options.browserURL}: ` + e.message;
throw e;
}
transport = await platform.createWebSocketTransport(connectionURL);
}
return SlowMoTransport.wrap(transport!, options.slowMo);
}

View file

@ -15,25 +15,18 @@
* limitations under the License.
*/
import { Browser } from '../browser';
import { Browser, createTransport, ConnectOptions } from '../browser';
import { BrowserContext, BrowserContextOptions } from '../browserContext';
import { Events } from '../events';
import { assert, helper, RegisteredListener } from '../helper';
import * as network from '../network';
import * as types from '../types';
import { Page } from '../page';
import { ConnectionTransport, SlowMoTransport } from '../transport';
import { ConnectionEvents, FFConnection, FFSessionEvents } from './ffConnection';
import { FFPage } from './ffPage';
import * as platform from '../platform';
import { Protocol } from './protocol';
export type FFConnectOptions = {
slowMo?: number,
browserWSEndpoint?: string;
transport?: ConnectionTransport;
};
export class FFBrowser extends platform.EventEmitter implements Browser {
_connection: FFConnection;
_targets: Map<string, Target>;
@ -41,7 +34,7 @@ export class FFBrowser extends platform.EventEmitter implements Browser {
private _contexts: Map<string, BrowserContext>;
private _eventListeners: RegisteredListener[];
static async connect(options: FFConnectOptions): Promise<FFBrowser> {
static async connect(options: ConnectOptions): Promise<FFBrowser> {
const transport = await createTransport(options);
const connection = new FFConnection(transport);
const {browserContextIds} = await connection.send('Target.getBrowserContexts');
@ -292,13 +285,3 @@ class Target {
return this._browser;
}
}
export async function createTransport(options: FFConnectOptions): Promise<ConnectionTransport> {
assert(Number(!!options.browserWSEndpoint) + Number(!!options.transport) === 1, 'Exactly one of browserWSEndpoint or transport must be passed to connect');
let transport: ConnectionTransport | undefined;
if (options.transport)
transport = options.transport;
else if (options.browserWSEndpoint)
transport = await platform.createWebSocketTransport(options.browserWSEndpoint);
return SlowMoTransport.wrap(transport!, options.slowMo);
}

46
src/server/browserApp.ts Normal file
View file

@ -0,0 +1,46 @@
/**
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { ChildProcess } from 'child_process';
import { ConnectOptions } from '../browser';
export class BrowserApp {
private _process: ChildProcess;
private _gracefullyClose: () => Promise<void>;
private _connectOptions: ConnectOptions;
constructor(process: ChildProcess, gracefullyClose: () => Promise<void>, connectOptions: ConnectOptions) {
this._process = process;
this._gracefullyClose = gracefullyClose;
this._connectOptions = connectOptions;
}
process(): ChildProcess {
return this._process;
}
wsEndpoint(): string | null {
return this._connectOptions.browserWSEndpoint || null;
}
connectOptions(): ConnectOptions {
return this._connectOptions;
}
async close(): Promise<void> {
await this._gracefullyClose();
}
}

View file

@ -23,14 +23,15 @@ import { BrowserFetcher, BrowserFetcherOptions } from '../server/browserFetcher'
import { DeviceDescriptors } from '../deviceDescriptors';
import * as types from '../types';
import { assert } from '../helper';
import { CRBrowser, CRConnectOptions, createTransport } from '../chromium/crBrowser';
import { CRBrowser } from '../chromium/crBrowser';
import * as platform from '../platform';
import { TimeoutError } from '../errors';
import { launchProcess, waitForLine } from '../server/processLauncher';
import { ChildProcess } from 'child_process';
import { CRConnection } from '../chromium/crConnection';
import { PipeTransport } from './pipeTransport';
import { Playwright } from './playwright';
import { createTransport, ConnectOptions } from '../browser';
import { BrowserApp } from './browserApp';
export type SlowMoOptions = {
slowMo?: number,
@ -55,34 +56,6 @@ export type LaunchOptions = ChromiumArgOptions & SlowMoOptions & {
pipe?: boolean,
};
export class CRBrowserServer {
private _process: ChildProcess;
private _gracefullyClose: () => Promise<void>;
private _connectOptions: CRConnectOptions;
constructor(process: ChildProcess, gracefullyClose: () => Promise<void>, connectOptions: CRConnectOptions) {
this._process = process;
this._gracefullyClose = gracefullyClose;
this._connectOptions = connectOptions;
}
process(): ChildProcess {
return this._process;
}
wsEndpoint(): string | null {
return this._connectOptions.browserWSEndpoint || null;
}
connectOptions(): CRConnectOptions {
return this._connectOptions;
}
async close(): Promise<void> {
await this._gracefullyClose();
}
}
export class CRPlaywright implements Playwright {
private _projectRoot: string;
readonly _revision: string;
@ -93,14 +66,14 @@ export class CRPlaywright implements Playwright {
}
async launch(options?: LaunchOptions): Promise<CRBrowser> {
const server = await this.launchServer(options);
const browser = await CRBrowser.connect(server.connectOptions());
const app = await this.launchBrowserApp(options);
const browser = await CRBrowser.connect(app.connectOptions());
// Hack: for typical launch scenario, ensure that close waits for actual process termination.
browser.close = () => server.close();
browser.close = () => app.close();
return browser;
}
async launchServer(options: LaunchOptions = {}): Promise<CRBrowserServer> {
async launchBrowserApp(options: LaunchOptions = {}): Promise<BrowserApp> {
const {
ignoreDefaultArgs = false,
args = [],
@ -165,7 +138,7 @@ export class CRPlaywright implements Playwright {
},
});
let connectOptions: CRConnectOptions | undefined;
let connectOptions: ConnectOptions | undefined;
if (!usePipe) {
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}`);
const match = await waitForLine(launchedProcess, launchedProcess.stderr, /^DevTools listening on (ws:\/\/.*)$/, timeout, timeoutError);
@ -175,10 +148,23 @@ export class CRPlaywright implements Playwright {
const transport = new PipeTransport(launchedProcess.stdio[3] as NodeJS.WritableStream, launchedProcess.stdio[4] as NodeJS.ReadableStream);
connectOptions = { slowMo, transport };
}
return new CRBrowserServer(launchedProcess, gracefullyClose, connectOptions);
return new BrowserApp(launchedProcess, gracefullyClose, connectOptions);
}
async connect(options: CRConnectOptions): Promise<CRBrowser> {
async connect(options: ConnectOptions & { browserURL?: string }): Promise<CRBrowser> {
if (options.browserURL) {
assert(!options.browserWSEndpoint && !options.transport, 'Exactly one of browserWSEndpoint, browserURL or transport must be passed to connect');
let connectionURL: string;
try {
const data = await platform.fetchUrl(new URL('/json/version', options.browserURL).href);
connectionURL = JSON.parse(data).webSocketDebuggerUrl;
} catch (e) {
e.message = `Failed to fetch browser webSocket url from ${options.browserURL}: ` + e.message;
throw e;
}
const transport = await platform.createWebSocketTransport(connectionURL);
options = { ...options, transport };
}
return CRBrowser.connect(options);
}

View file

@ -15,14 +15,13 @@
* limitations under the License.
*/
import { FFBrowser, FFConnectOptions, createTransport } from '../firefox/ffBrowser';
import { FFBrowser } from '../firefox/ffBrowser';
import { BrowserFetcher, BrowserFetcherOptions } from './browserFetcher';
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 { ChildProcess } from 'child_process';
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
@ -30,6 +29,8 @@ import * as util from 'util';
import { TimeoutError } from '../errors';
import { assert } from '../helper';
import { Playwright } from './playwright';
import { createTransport, ConnectOptions } from '../browser';
import { BrowserApp } from './browserApp';
export type SlowMoOptions = {
slowMo?: number,
@ -52,34 +53,6 @@ export type LaunchOptions = FirefoxArgOptions & SlowMoOptions & {
env?: {[key: string]: string} | undefined,
};
export class FFBrowserServer {
private _process: ChildProcess;
private _gracefullyClose: () => Promise<void>;
private _connectOptions: FFConnectOptions;
constructor(process: ChildProcess, gracefullyClose: () => Promise<void>, connectOptions: FFConnectOptions) {
this._process = process;
this._gracefullyClose = gracefullyClose;
this._connectOptions = connectOptions;
}
process(): ChildProcess {
return this._process;
}
wsEndpoint(): string | null {
return this._connectOptions.browserWSEndpoint || null;
}
connectOptions(): FFConnectOptions {
return this._connectOptions;
}
async close(): Promise<void> {
await this._gracefullyClose();
}
}
export class FFPlaywright implements Playwright {
private _projectRoot: string;
readonly _revision: string;
@ -90,14 +63,14 @@ export class FFPlaywright implements Playwright {
}
async launch(options: LaunchOptions): Promise<FFBrowser> {
const server = await this.launchServer(options);
const browser = await FFBrowser.connect(server.connectOptions());
const app = await this.launchBrowserApp(options);
const browser = await FFBrowser.connect(app.connectOptions());
// Hack: for typical launch scenario, ensure that close waits for actual process termination.
browser.close = () => server.close();
browser.close = () => app.close();
return browser;
}
async launchServer(options: LaunchOptions = {}): Promise<FFBrowserServer> {
async launchBrowserApp(options: LaunchOptions = {}): Promise<BrowserApp> {
const {
ignoreDefaultArgs = false,
args = [],
@ -136,7 +109,7 @@ export class FFPlaywright implements Playwright {
firefoxExecutable = executablePath;
}
let connectOptions: FFConnectOptions | undefined = undefined;
let connectOptions: ConnectOptions | undefined = undefined;
const { launchedProcess, gracefullyClose } = await launchProcess({
executablePath: firefoxExecutable,
@ -168,10 +141,10 @@ export class FFPlaywright implements Playwright {
const match = await waitForLine(launchedProcess, launchedProcess.stdout, /^Juggler listening on (ws:\/\/.*)$/, timeout, timeoutError);
const url = match[1];
connectOptions = { browserWSEndpoint: url, slowMo };
return new FFBrowserServer(launchedProcess, gracefullyClose, connectOptions);
return new BrowserApp(launchedProcess, gracefullyClose, connectOptions);
}
async connect(options: FFConnectOptions): Promise<FFBrowser> {
async connect(options: ConnectOptions): Promise<FFBrowser> {
return FFBrowser.connect(options);
}

View file

@ -20,8 +20,7 @@ import { DeviceDescriptors } from '../deviceDescriptors';
import { TimeoutError } from '../errors';
import * as types from '../types';
import { WKBrowser } from '../webkit/wkBrowser';
import { WKConnectOptions } from '../webkit/wkBrowser';
import { execSync, ChildProcess } from 'child_process';
import { execSync } from 'child_process';
import { PipeTransport } from './pipeTransport';
import { launchProcess } from './processLauncher';
import * as fs from 'fs';
@ -35,6 +34,8 @@ import { Playwright } from './playwright';
import { ConnectionTransport } from '../transport';
import * as ws from 'ws';
import * as uuidv4 from 'uuid/v4';
import { ConnectOptions } from '../browser';
import { BrowserApp } from './browserApp';
export type SlowMoOptions = {
slowMo?: number,
@ -58,34 +59,6 @@ export type LaunchOptions = WebKitArgOptions & SlowMoOptions & {
pipe?: boolean,
};
export class WKBrowserServer {
private _process: ChildProcess;
private _gracefullyClose: () => Promise<void>;
private _connectOptions: WKConnectOptions;
constructor(process: ChildProcess, gracefullyClose: () => Promise<void>, connectOptions: WKConnectOptions) {
this._process = process;
this._gracefullyClose = gracefullyClose;
this._connectOptions = connectOptions;
}
process(): ChildProcess {
return this._process;
}
wsEndpoint(): string | null {
return this._connectOptions.browserWSEndpoint || null;
}
connectOptions(): WKConnectOptions {
return this._connectOptions;
}
async close(): Promise<void> {
await this._gracefullyClose();
}
}
export class WKPlaywright implements Playwright {
private _projectRoot: string;
readonly _revision: string;
@ -96,14 +69,14 @@ export class WKPlaywright implements Playwright {
}
async launch(options?: LaunchOptions): Promise<WKBrowser> {
const server = await this.launchServer(options);
const browser = await WKBrowser.connect(server.connectOptions());
const app = await this.launchBrowserApp(options);
const browser = await WKBrowser.connect(app.connectOptions());
// Hack: for typical launch scenario, ensure that close waits for actual process termination.
browser.close = () => server.close();
browser.close = () => app.close();
return browser;
}
async launchServer(options: LaunchOptions = {}): Promise<WKBrowserServer> {
async launchBrowserApp(options: LaunchOptions = {}): Promise<BrowserApp> {
const {
ignoreDefaultArgs = false,
args = [],
@ -166,17 +139,17 @@ export class WKPlaywright implements Playwright {
transport = new PipeTransport(launchedProcess.stdio[3] as NodeJS.WritableStream, launchedProcess.stdio[4] as NodeJS.ReadableStream);
let connectOptions: WKConnectOptions;
let connectOptions: ConnectOptions;
if (!pipe) {
const browserWSEndpoint = wrapTransportWithWebSocket(transport);
connectOptions = { browserWSEndpoint, slowMo };
} else {
connectOptions = { transport, slowMo };
}
return new WKBrowserServer(launchedProcess, gracefullyClose, connectOptions);
return new BrowserApp(launchedProcess, gracefullyClose, connectOptions);
}
async connect(options: WKConnectOptions): Promise<WKBrowser> {
async connect(options: ConnectOptions): Promise<WKBrowser> {
return WKBrowser.connect(options);
}

View file

@ -15,12 +15,12 @@
* limitations under the License.
*/
import { Browser } from '../browser';
import { Browser, createTransport, ConnectOptions } from '../browser';
import { BrowserContext, BrowserContextOptions } from '../browserContext';
import { assert, helper, RegisteredListener } from '../helper';
import * as network from '../network';
import { Page } from '../page';
import { ConnectionTransport, SlowMoTransport } from '../transport';
import { ConnectionTransport } from '../transport';
import * as types from '../types';
import { Events } from '../events';
import { Protocol } from './protocol';
@ -28,12 +28,6 @@ import { WKConnection, WKSession, kPageProxyMessageReceived, PageProxyMessageRec
import { WKPageProxy } from './wkPageProxy';
import * as platform from '../platform';
export type WKConnectOptions = {
slowMo?: number,
browserWSEndpoint?: string,
transport?: ConnectionTransport,
};
export class WKBrowser extends platform.EventEmitter implements Browser {
private readonly _connection: WKConnection;
private readonly _browserSession: WKSession;
@ -45,7 +39,7 @@ export class WKBrowser extends platform.EventEmitter implements Browser {
private _firstPageProxyCallback?: () => void;
private readonly _firstPageProxyPromise: Promise<void>;
static async connect(options: WKConnectOptions): Promise<WKBrowser> {
static async connect(options: ConnectOptions): Promise<WKBrowser> {
const transport = await createTransport(options);
const browser = new WKBrowser(transport);
// TODO: figure out the timeout.
@ -228,13 +222,3 @@ export class WKBrowser extends platform.EventEmitter implements Browser {
return context;
}
}
export async function createTransport(options: WKConnectOptions): Promise<ConnectionTransport> {
assert(Number(!!options.browserWSEndpoint) + Number(!!options.transport) === 1, 'Exactly one of browserWSEndpoint or transport must be passed to connect');
let transport: ConnectionTransport | undefined;
if (options.transport)
transport = options.transport;
else if (options.browserWSEndpoint)
transport = await platform.createWebSocketTransport(options.browserWSEndpoint);
return SlowMoTransport.wrap(transport!, options.slowMo);
}

View file

@ -22,7 +22,7 @@ import { Protocol } from './protocol';
const debugProtocol = platform.debug('pw:protocol');
// WKBrowserServer uses this special id to issue Browser.close command which we
// WKPlaywright uses this special id to issue Browser.close command which we
// should ignore.
export const kBrowserCloseMessageId = -9999;

View file

@ -21,8 +21,8 @@ module.exports.describe = function({testRunner, expect, headless, playwright, FF
const {beforeAll, beforeEach, afterAll, afterEach} = testRunner;
describe('Browser.process', function() {
it('should return child_process instance', async function({browserServer}) {
const process = await browserServer.process();
it('should return child_process instance', async function({browserApp}) {
const process = await browserApp.process();
expect(process.pid).toBeGreaterThan(0);
});
});

View file

@ -22,12 +22,12 @@ module.exports.describe = function({testRunner, expect, playwright, FFOX, CHROMI
const {beforeAll, beforeEach, afterAll, afterEach} = testRunner;
describe('Chromium', function() {
it('should work across sessions', async function({browserServer, server, browser, newContext}) {
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: browserServer.wsEndpoint()
browserWSEndpoint: browserApp.wsEndpoint()
});
const contexts = remoteBrowser.browserContexts();
expect(contexts.length).toBe(3);

View file

@ -24,11 +24,11 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
describe('Playwright.connect', function() {
it('should be able to connect multiple times to the same browser', async({server}) => {
const browserServer = await playwright.launchServer(defaultBrowserOptions);
const local = await playwright.connect(browserServer.connectOptions());
const browserApp = await playwright.launchBrowserApp(defaultBrowserOptions);
const local = await playwright.connect(browserApp.connectOptions());
const remote = await playwright.connect({
...defaultBrowserOptions,
browserWSEndpoint: browserServer.wsEndpoint()
browserWSEndpoint: browserApp.wsEndpoint()
});
const page = await remote.defaultContext().newPage();
expect(await page.evaluate(() => 7 * 8)).toBe(56);
@ -36,14 +36,14 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
const secondPage = await local.defaultContext().newPage();
expect(await secondPage.evaluate(() => 7 * 6)).toBe(42, 'original browser should still work');
await browserServer.close();
await browserApp.close();
});
it('should be able to close remote browser', async({server}) => {
const browserServer = await playwright.launchServer(defaultBrowserOptions);
const local = await playwright.connect(browserServer.connectOptions());
const browserApp = await playwright.launchBrowserApp(defaultBrowserOptions);
const local = await playwright.connect(browserApp.connectOptions());
const remote = await playwright.connect({
...defaultBrowserOptions,
browserWSEndpoint: browserServer.wsEndpoint()
browserWSEndpoint: browserApp.wsEndpoint()
});
await Promise.all([
utils.waitEvent(local, 'disconnected'),
@ -52,9 +52,9 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
});
// @see https://github.com/GoogleChrome/puppeteer/issues/4197#issuecomment-481793410
it('should be able to connect to the same page simultaneously', async({server}) => {
const browserServer = await playwright.launchServer(defaultBrowserOptions);
const local = await playwright.connect(browserServer.connectOptions());
const remote = await playwright.connect({ ...defaultBrowserOptions, browserWSEndpoint: browserServer.wsEndpoint() });
const browserApp = await playwright.launchBrowserApp(defaultBrowserOptions);
const local = await playwright.connect(browserApp.connectOptions());
const remote = await playwright.connect({ ...defaultBrowserOptions, browserWSEndpoint: browserApp.wsEndpoint() });
const [page1, page2] = await Promise.all([
new Promise(x => local.once('targetcreated', target => x(target.page()))),
remote.defaultContext().newPage(),

View file

@ -51,16 +51,16 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
originalBrowser.close();
});
it('should throw when using both browserWSEndpoint and browserURL', async({server}) => {
const browserServer = await playwright.launchServer(Object.assign({}, defaultBrowserOptions, {
const browserApp = await playwright.launchBrowserApp(Object.assign({}, defaultBrowserOptions, {
args: ['--remote-debugging-port=21222']
}));
const browserURL = 'http://127.0.0.1:21222';
let error = null;
await playwright.connect({browserURL, browserWSEndpoint: browserServer.wsEndpoint()}).catch(e => error = e);
await playwright.connect({browserURL, browserWSEndpoint: browserApp.wsEndpoint()}).catch(e => error = e);
expect(error.message).toContain('Exactly one of browserWSEndpoint, browserURL or transport');
browserServer.close();
browserApp.close();
});
it('should throw when trying to connect to non-existing browser', async({server}) => {
const originalBrowser = await playwright.launch(Object.assign({}, defaultBrowserOptions, {
@ -78,33 +78,33 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
describe('Playwright.launch |pipe| option', function() {
it('should support the pipe option', async() => {
const options = Object.assign({pipe: true}, defaultBrowserOptions);
const browserServer = await playwright.launchServer(options);
const browser = await playwright.connect(browserServer.connectOptions());
const browserApp = await playwright.launchBrowserApp(options);
const browser = await playwright.connect(browserApp.connectOptions());
expect((await browser.defaultContext().pages()).length).toBe(1);
expect(browserServer.wsEndpoint()).toBe(null);
expect(browserApp.wsEndpoint()).toBe(null);
const page = await browser.defaultContext().newPage();
expect(await page.evaluate('11 * 11')).toBe(121);
await page.close();
await browserServer.close();
await browserApp.close();
});
it('should support the pipe argument', async() => {
const options = Object.assign({}, defaultBrowserOptions);
options.args = ['--remote-debugging-pipe'].concat(options.args || []);
const browserServer = await playwright.launchServer(options);
const browser = await playwright.connect(browserServer.connectOptions());
expect(browserServer.wsEndpoint()).toBe(null);
const browserApp = await playwright.launchBrowserApp(options);
const browser = await playwright.connect(browserApp.connectOptions());
expect(browserApp.wsEndpoint()).toBe(null);
const page = await browser.defaultContext().newPage();
expect(await page.evaluate('11 * 11')).toBe(121);
await page.close();
await browserServer.close();
await browserApp.close();
});
it('should fire "disconnected" when closing with pipe', async() => {
const options = Object.assign({pipe: true}, defaultBrowserOptions);
const browserServer = await playwright.launchServer(options);
const browser = await playwright.connect(browserServer.connectOptions());
const browserApp = await playwright.launchBrowserApp(options);
const browser = await playwright.connect(browserApp.connectOptions());
const disconnectedEventPromise = new Promise(resolve => browser.once('disconnected', resolve));
// Emulate user exiting browser.
browserServer.process().kill();
browserApp.process().kill();
await disconnectedEventPromise;
});
});
@ -127,9 +127,9 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
describe('Browser.Events.disconnected', function() {
it('should be emitted when: browser gets closed, disconnected or underlying websocket gets closed', async() => {
const browserServer = await playwright.launchServer(defaultBrowserOptions);
const originalBrowser = await playwright.connect(browserServer.connectOptions());
const browserWSEndpoint = browserServer.wsEndpoint();
const browserApp = await playwright.launchBrowserApp(defaultBrowserOptions);
const originalBrowser = await playwright.connect(browserApp.connectOptions());
const browserWSEndpoint = browserApp.wsEndpoint();
const remoteBrowser1 = await playwright.connect({browserWSEndpoint});
const remoteBrowser2 = await playwright.connect({browserWSEndpoint});
@ -152,7 +152,7 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
await Promise.all([
waitEvent(remoteBrowser1, 'disconnected'),
waitEvent(originalBrowser, 'disconnected'),
browserServer.close(),
browserApp.close(),
]);
expect(disconnectedOriginal).toBe(1);

View file

@ -65,15 +65,15 @@ module.exports.describe = function ({ testRunner, expect, defaultBrowserOptions,
it('should filter out ignored default arguments', async() => {
// Make sure we launch with `--enable-automation` by default.
const defaultArgs = playwright.defaultArgs(defaultBrowserOptions);
const browserServer = await playwright.launchServer(Object.assign({}, defaultBrowserOptions, {
const browserApp = await playwright.launchBrowserApp(Object.assign({}, defaultBrowserOptions, {
// Ignore first and third default argument.
ignoreDefaultArgs: [ defaultArgs[0], defaultArgs[2] ],
}));
const spawnargs = browserServer.process().spawnargs;
const spawnargs = browserApp.process().spawnargs;
expect(spawnargs.indexOf(defaultArgs[0])).toBe(-1);
expect(spawnargs.indexOf(defaultArgs[1])).not.toBe(-1);
expect(spawnargs.indexOf(defaultArgs[2])).toBe(-1);
await browserServer.close();
await browserApp.close();
});
});
});

View file

@ -1,5 +1,5 @@
(async() => {
const [, , playwrightRoot, product, options] = process.argv;
const browserServer = await require(playwrightRoot)[product.toLowerCase()].launchServer(JSON.parse(options));
console.log(browserServer.wsEndpoint());
const browserApp = await require(playwrightRoot)[product.toLowerCase()].launchBrowserApp(JSON.parse(options));
console.log(browserApp.wsEndpoint());
})();

View file

@ -17,7 +17,7 @@
if (product.toLowerCase() === 'firefox')
options.args.push('-juggler', '-profile');
try {
await require(playwrightRoot)[product.toLowerCase()].launchServer(options);
await require(playwrightRoot)[product.toLowerCase()].launchBrowserApp(options);
console.error('Browser launch unexpectedly succeeded.');
} catch (e) {
}

View file

@ -90,20 +90,20 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
describe('Browser.isConnected', () => {
it('should set the browser connected state', async () => {
const browserServer = await playwright.launchServer(defaultBrowserOptions);
const browserWSEndpoint = browserServer.wsEndpoint();
const browserApp = await playwright.launchBrowserApp(defaultBrowserOptions);
const browserWSEndpoint = browserApp.wsEndpoint();
const remote = await playwright.connect({browserWSEndpoint});
expect(remote.isConnected()).toBe(true);
await remote.disconnect();
expect(remote.isConnected()).toBe(false);
await browserServer.close();
await browserApp.close();
});
it('should throw when used after isConnected returns false', async({server}) => {
const browserServer = await playwright.launchServer(defaultBrowserOptions);
const remote = await playwright.connect({...defaultBrowserOptions, browserWSEndpoint: browserServer.wsEndpoint()});
const browserApp = await playwright.launchBrowserApp(defaultBrowserOptions);
const remote = await playwright.connect({...defaultBrowserOptions, browserWSEndpoint: browserApp.wsEndpoint()});
const page = await remote.defaultContext().newPage();
await Promise.all([
browserServer.close(),
browserApp.close(),
new Promise(f => remote.once('disconnected', f)),
]);
expect(remote.isConnected()).toBe(false);
@ -115,47 +115,47 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
describe('Browser.disconnect', function() {
it('should reject navigation when browser closes', async({server}) => {
server.setRoute('/one-style.css', () => {});
const browserServer = await playwright.launchServer(defaultBrowserOptions);
const remote = await playwright.connect({...defaultBrowserOptions, browserWSEndpoint: browserServer.wsEndpoint()});
const browserApp = await playwright.launchBrowserApp(defaultBrowserOptions);
const remote = await playwright.connect({...defaultBrowserOptions, browserWSEndpoint: browserApp.wsEndpoint()});
const page = await remote.defaultContext().newPage();
const navigationPromise = page.goto(server.PREFIX + '/one-style.html', {timeout: 60000}).catch(e => e);
await server.waitForRequest('/one-style.css');
await remote.disconnect();
const error = await navigationPromise;
expect(error.message).toBe('Navigation failed because browser has disconnected!');
await browserServer.close();
await browserApp.close();
});
it('should reject waitForSelector when browser closes', async({server}) => {
server.setRoute('/empty.html', () => {});
const browserServer = await playwright.launchServer(defaultBrowserOptions);
const remote = await playwright.connect({...defaultBrowserOptions, browserWSEndpoint: browserServer.wsEndpoint()});
const browserApp = await playwright.launchBrowserApp(defaultBrowserOptions);
const remote = await playwright.connect({...defaultBrowserOptions, browserWSEndpoint: browserApp.wsEndpoint()});
const page = await remote.defaultContext().newPage();
const watchdog = page.waitForSelector('div', { timeout: 60000 }).catch(e => e);
await remote.disconnect();
const error = await watchdog;
expect(error.message).toContain('Protocol error');
await browserServer.close();
await browserApp.close();
});
it('should throw if used after disconnect', async({server}) => {
const browserServer = await playwright.launchServer(defaultBrowserOptions);
const remote = await playwright.connect({...defaultBrowserOptions, browserWSEndpoint: browserServer.wsEndpoint()});
const browserApp = await playwright.launchBrowserApp(defaultBrowserOptions);
const remote = await playwright.connect({...defaultBrowserOptions, browserWSEndpoint: browserApp.wsEndpoint()});
const page = await remote.defaultContext().newPage();
await remote.disconnect();
const error = await page.evaluate('1 + 1').catch(e => e);
expect(error.message).toContain('has been closed');
await browserServer.close();
await browserApp.close();
});
});
describe('Browser.close', function() {
it('should terminate network waiters', async({context, server}) => {
const browserServer = await playwright.launchServer(defaultBrowserOptions);
const remote = await playwright.connect({...defaultBrowserOptions, browserWSEndpoint: browserServer.wsEndpoint()});
const browserApp = await playwright.launchBrowserApp(defaultBrowserOptions);
const remote = await playwright.connect({...defaultBrowserOptions, browserWSEndpoint: browserApp.wsEndpoint()});
const newPage = await remote.defaultContext().newPage();
const results = await Promise.all([
newPage.waitForRequest(server.EMPTY_PAGE).catch(e => e),
newPage.waitForResponse(server.EMPTY_PAGE).catch(e => e),
browserServer.close()
browserApp.close()
]);
for (let i = 0; i < 2; i++) {
const message = results[i].message;
@ -167,9 +167,9 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
describe('Playwright.connect', function() {
it.skip(WEBKIT)('should be able to reconnect to a browser', async({server}) => {
const browserServer = await playwright.launchServer(defaultBrowserOptions);
const browser = await playwright.connect(browserServer.connectOptions());
const browserWSEndpoint = browserServer.wsEndpoint();
const browserApp = await playwright.launchBrowserApp(defaultBrowserOptions);
const browser = await playwright.connect(browserApp.connectOptions());
const browserWSEndpoint = browserApp.wsEndpoint();
const page = await browser.defaultContext().newPage();
await page.goto(server.PREFIX + '/frames/nested-frames.html');
await browser.disconnect();
@ -185,7 +185,7 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
' http://localhost:<PORT>/frames/frame.html (uno)',
]);
expect(await restoredPage.evaluate(() => 7 * 8)).toBe(56);
await browserServer.close();
await browserApp.close();
});
});
@ -228,15 +228,15 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
it('should filter out ignored default arguments', async() => {
// Make sure we launch with `--enable-automation` by default.
const defaultArgs = playwright.defaultArgs(defaultBrowserOptions);
const browserServer = await playwright.launchServer(Object.assign({}, defaultBrowserOptions, {
const browserApp = await playwright.launchBrowserApp(Object.assign({}, defaultBrowserOptions, {
// Ignore first and third default argument.
ignoreDefaultArgs: [ defaultArgs[0], defaultArgs[2] ],
}));
const spawnargs = browserServer.process().spawnargs;
const spawnargs = browserApp.process().spawnargs;
expect(spawnargs.indexOf(defaultArgs[0])).toBe(-1);
expect(spawnargs.indexOf(defaultArgs[1])).not.toBe(-1);
expect(spawnargs.indexOf(defaultArgs[2])).toBe(-1);
await browserServer.close();
await browserApp.close();
});
it('userDataDir option should restore state', async({server}) => {
const userDataDir = await mkdtempAsync(TMP_FOLDER);

View file

@ -90,14 +90,14 @@ module.exports.describe = ({testRunner, product, playwrightPath}) => {
describe('Browser', function() {
beforeAll(async state => {
state.browserServer = await playwright.launchServer(defaultBrowserOptions);
state.browser = await playwright.connect(state.browserServer.connectOptions());
state.browserApp = await playwright.launchBrowserApp(defaultBrowserOptions);
state.browser = await playwright.connect(state.browserApp.connectOptions());
});
afterAll(async state => {
await state.browserServer.close();
await state.browserApp.close();
state.browser = null;
state.browserServer = null;
state.browserApp = null;
});
beforeEach(async(state, test) => {
@ -106,7 +106,7 @@ module.exports.describe = ({testRunner, product, playwrightPath}) => {
let rl;
if (!WEBKIT) {
rl = require('readline').createInterface({ input: state.browserServer.process().stderr });
rl = require('readline').createInterface({ input: state.browserApp.process().stderr });
test.output = '';
rl.on('line', onLine);
}

View file

@ -21,18 +21,18 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
(CHROMIUM || FFOX) && describe('Web SDK', function() {
beforeAll(async state => {
state.controlledBrowserServer = await playwright.launchServer({ ...defaultBrowserOptions, pipe: false });
state.hostBrowserServer = await playwright.launchServer(defaultBrowserOptions);
state.hostBrowser = await playwright.connect(state.hostBrowserServer.connectOptions());
state.controlledBrowserApp = await playwright.launchBrowserApp({ ...defaultBrowserOptions, pipe: false });
state.hostBrowserApp = await playwright.launchBrowserApp(defaultBrowserOptions);
state.hostBrowser = await playwright.connect(state.hostBrowserApp.connectOptions());
});
afterAll(async state => {
await state.hostBrowserServer.close();
await state.hostBrowserApp.close();
state.hostBrowser = null;
state.hostBrowserServer = null;
state.hostBrowserApp = null;
await state.controlledBrowserServer.close();
state.controlledBrowserServer = null;
await state.controlledBrowserApp.close();
state.controlledBrowserApp = null;
state.webUrl = null;
});
@ -40,7 +40,7 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
state.page = await state.hostBrowser.defaultContext().newPage();
state.page.on('console', message => console.log('TEST: ' + message.text()));
await state.page.goto(state.sourceServer.PREFIX + '/test/assets/playwrightweb.html');
await state.page.evaluate((product, connectOptions) => setup(product, connectOptions), product.toLowerCase(), state.controlledBrowserServer.connectOptions());
await state.page.evaluate((product, connectOptions) => setup(product, connectOptions), product.toLowerCase(), state.controlledBrowserApp.connectOptions());
});
afterEach(async state => {

View file

@ -24,42 +24,42 @@ module.exports.describe = function ({ testRunner, expect, playwright, defaultBro
describe('Playwright.launch |pipe| option', function() {
it('should have websocket by default', async() => {
const options = Object.assign({pipe: false}, defaultBrowserOptions);
const browserServer = await playwright.launchServer(options);
const browser = await playwright.connect(browserServer.connectOptions());
const browserApp = await playwright.launchBrowserApp(options);
const browser = await playwright.connect(browserApp.connectOptions());
expect((await browser.defaultContext().pages()).length).toBe(1);
expect(browserServer.wsEndpoint()).not.toBe(null);
expect(browserApp.wsEndpoint()).not.toBe(null);
const page = await browser.defaultContext().newPage();
expect(await page.evaluate('11 * 11')).toBe(121);
await page.close();
await browserServer.close();
await browserApp.close();
});
it('should support the pipe option', async() => {
const options = Object.assign({pipe: true}, defaultBrowserOptions);
const browserServer = await playwright.launchServer(options);
const browser = await playwright.connect(browserServer.connectOptions());
const browserApp = await playwright.launchBrowserApp(options);
const browser = await playwright.connect(browserApp.connectOptions());
expect((await browser.defaultContext().pages()).length).toBe(1);
expect(browserServer.wsEndpoint()).toBe(null);
expect(browserApp.wsEndpoint()).toBe(null);
const page = await browser.defaultContext().newPage();
expect(await page.evaluate('11 * 11')).toBe(121);
await page.close();
await browserServer.close();
await browserApp.close();
});
it('should fire "disconnected" when closing with pipe', async() => {
const options = Object.assign({pipe: true}, defaultBrowserOptions);
const browserServer = await playwright.launchServer(options);
const browser = await playwright.connect(browserServer.connectOptions());
const browserApp = await playwright.launchBrowserApp(options);
const browser = await playwright.connect(browserApp.connectOptions());
const disconnectedEventPromise = new Promise(resolve => browser.once('disconnected', resolve));
// Emulate user exiting browser.
process.kill(-browserServer.process().pid, 'SIGKILL');
process.kill(-browserApp.process().pid, 'SIGKILL');
await disconnectedEventPromise;
});
it('should fire "disconnected" when closing with websocket', async() => {
const options = Object.assign({pipe: false}, defaultBrowserOptions);
const browserServer = await playwright.launchServer(options);
const browser = await playwright.connect(browserServer.connectOptions());
const browserApp = await playwright.launchBrowserApp(options);
const browser = await playwright.connect(browserApp.connectOptions());
const disconnectedEventPromise = new Promise(resolve => browser.once('disconnected', resolve));
// Emulate user exiting browser.
process.kill(-browserServer.process().pid, 'SIGKILL');
process.kill(-browserApp.process().pid, 'SIGKILL');
await disconnectedEventPromise;
});
});

View file

@ -10,13 +10,13 @@ async function generateChromiunProtocol(revision) {
if (revision.local && fs.existsSync(outputPath))
return;
const playwright = await require('../../index').chromium;
const browserServer = await playwright.launchServer({executablePath: revision.executablePath});
const origin = browserServer.wsEndpoint().match(/ws:\/\/([0-9A-Za-z:\.]*)\//)[1];
const browser = await playwright.connect(browserServer.connectOptions());
const browserApp = await playwright.launchBrowserApp({executablePath: revision.executablePath});
const origin = browserApp.wsEndpoint().match(/ws:\/\/([0-9A-Za-z:\.]*)\//)[1];
const browser = await playwright.connect(browserApp.connectOptions());
const page = await browser.defaultContext().newPage();
await page.goto(`http://${origin}/json/protocol`);
const json = JSON.parse(await page.evaluate(() => document.documentElement.innerText));
await browserServer.close();
await browserApp.close();
fs.writeFileSync(outputPath, jsonToTS(json));
console.log(`Wrote protocol.ts to ${path.relative(process.cwd(), outputPath)}`);
}