From ac2ba3cbd940b6a3cd55f1196abc6a303117a709 Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Thu, 23 Jan 2020 14:40:37 -0800 Subject: [PATCH] fix(api): BrowserServer -> BrowserApp, resuse it between browsers (#599) --- docs/api.md | 163 ++++++++---------------- src/api.ts | 7 +- src/browser.ts | 18 +++ src/chromium/crBrowser.ts | 33 +---- src/firefox/ffBrowser.ts | 21 +-- src/server/browserApp.ts | 46 +++++++ src/server/crPlaywright.ts | 60 ++++----- src/server/ffPlaywright.ts | 47 ++----- src/server/wkPlaywright.ts | 47 ++----- src/webkit/wkBrowser.ts | 22 +--- src/webkit/wkConnection.ts | 2 +- test/browser.spec.js | 4 +- test/chromium/chromium.spec.js | 4 +- test/chromium/connect.spec.js | 20 +-- test/chromium/launcher.spec.js | 36 +++--- test/firefox/launcher.spec.js | 6 +- test/fixtures/closeme.js | 4 +- test/fixtures/dumpio.js | 2 +- test/launcher.spec.js | 50 ++++---- test/playwright.spec.js | 10 +- test/web.spec.js | 16 +-- test/webkit/launcher.spec.js | 28 ++-- utils/protocol-types-generator/index.js | 8 +- 23 files changed, 263 insertions(+), 391 deletions(-) create mode 100644 src/server/browserApp.ts diff --git a/docs/api.md b/docs/api.md index b1f4d8df5f..bc8e0ae476 100644 --- a/docs/api.md +++ b/docs/api.md @@ -7,6 +7,7 @@ - [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(); })(); ``` @@ -214,6 +212,38 @@ Creates a new browser context. It won't share cookies/cache with other browser c })(); ``` +### class: BrowserApp + + +- [browserApp.close()](#browserappclose) +- [browserApp.connectOptions()](#browserappconnectoptions) +- [browserApp.process()](#browserappprocess) +- [browserApp.wsEndpoint()](#browserappwsendpoint) + + +#### browserApp.close() +- returns: <[Promise]> + +Closes the browser gracefully and makes sure the process is terminated. + +#### browserApp.connectOptions() +- returns: <[Object]> + - `browserWSEndpoint` 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: Spawned browser server process. + +#### browserApp.wsEndpoint() +- returns: 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) #### 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 - - -- [chromiumBrowserServer.close()](#chromiumbrowserserverclose) -- [chromiumBrowserServer.connectOptions()](#chromiumbrowserserverconnectoptions) -- [chromiumBrowserServer.process()](#chromiumbrowserserverprocess) -- [chromiumBrowserServer.wsEndpoint()](#chromiumbrowserserverwsendpoint) - - -#### chromiumBrowserServer.close() -- returns: <[Promise]> - -Closes the browser gracefully and makes sure the process is terminated. - -#### chromiumBrowserServer.connectOptions() -- returns: <[Object]> - - `browserWSEndpoint` a [browser websocket endpoint](#browserwsendpoint) to connect to. - - `browserURL` 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: Spawned browser server process. - -#### chromiumBrowserServer.wsEndpoint() -- returns: 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) #### 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 - - -- [firefoxBrowserServer.close()](#firefoxbrowserserverclose) -- [firefoxBrowserServer.connectOptions()](#firefoxbrowserserverconnectoptions) -- [firefoxBrowserServer.process()](#firefoxbrowserserverprocess) -- [firefoxBrowserServer.wsEndpoint()](#firefoxbrowserserverwsendpoint) - - -#### firefoxBrowserServer.close() -- returns: <[Promise]> - -Closes the browser gracefully and makes sure the process is terminated. - -#### firefoxBrowserServer.connectOptions() -- returns: <[Object]> - - `browserWSEndpoint` 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: Spawned browser server process. - -#### firefoxBrowserServer.wsEndpoint() -- returns: 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) #### 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 - - -- [webKitBrowserServer.close()](#webkitbrowserserverclose) -- [webKitBrowserServer.connectOptions()](#webkitbrowserserverconnectoptions) -- [webKitBrowserServer.process()](#webkitbrowserserverprocess) -- [webKitBrowserServer.wsEndpoint()](#webkitbrowserserverwsendpoint) - - -#### 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: Spawned browser server process. - -#### webKitBrowserServer.wsEndpoint() -- returns: 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" diff --git a/src/api.ts b/src/api.ts index 85ef672edd..30b3b9fbc2 100644 --- a/src/api.ts +++ b/src/api.ts @@ -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'; diff --git a/src/browser.ts b/src/browser.ts index e118bf2564..f7d32ed1b2 100644 --- a/src/browser.ts +++ b/src/browser.ts @@ -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; @@ -26,3 +28,19 @@ export interface Browser extends platform.EventEmitterType { isConnected(): boolean; close(): Promise; } + +export type ConnectOptions = { + slowMo?: number, + browserWSEndpoint?: string; + transport?: ConnectionTransport; +}; + +export async function createTransport(options: ConnectOptions): Promise { + 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); +} diff --git a/src/chromium/crBrowser.ts b/src/chromium/crBrowser.ts index 9a5708ac91..ed8b0ae180 100644 --- a/src/chromium/crBrowser.ts +++ b/src/chromium/crBrowser.ts @@ -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 { + static async connect(options: ConnectOptions): Promise { 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 { - 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); -} diff --git a/src/firefox/ffBrowser.ts b/src/firefox/ffBrowser.ts index 7207bf8e49..79cba88427 100644 --- a/src/firefox/ffBrowser.ts +++ b/src/firefox/ffBrowser.ts @@ -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; @@ -41,7 +34,7 @@ export class FFBrowser extends platform.EventEmitter implements Browser { private _contexts: Map; private _eventListeners: RegisteredListener[]; - static async connect(options: FFConnectOptions): Promise { + static async connect(options: ConnectOptions): Promise { 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 { - 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); -} diff --git a/src/server/browserApp.ts b/src/server/browserApp.ts new file mode 100644 index 0000000000..60a3c6cfb4 --- /dev/null +++ b/src/server/browserApp.ts @@ -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; + private _connectOptions: ConnectOptions; + + constructor(process: ChildProcess, gracefullyClose: () => Promise, 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 { + await this._gracefullyClose(); + } +} diff --git a/src/server/crPlaywright.ts b/src/server/crPlaywright.ts index 9f816a6d01..5de39114ae 100644 --- a/src/server/crPlaywright.ts +++ b/src/server/crPlaywright.ts @@ -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; - private _connectOptions: CRConnectOptions; - - constructor(process: ChildProcess, gracefullyClose: () => Promise, 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 { - 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 { - 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 { + async launchBrowserApp(options: LaunchOptions = {}): Promise { 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 { + async connect(options: ConnectOptions & { browserURL?: string }): Promise { + 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); } diff --git a/src/server/ffPlaywright.ts b/src/server/ffPlaywright.ts index 0ad55ed84e..6dba2b2e78 100644 --- a/src/server/ffPlaywright.ts +++ b/src/server/ffPlaywright.ts @@ -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; - private _connectOptions: FFConnectOptions; - - constructor(process: ChildProcess, gracefullyClose: () => Promise, 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 { - 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 { - 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 { + async launchBrowserApp(options: LaunchOptions = {}): Promise { 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 { + async connect(options: ConnectOptions): Promise { return FFBrowser.connect(options); } diff --git a/src/server/wkPlaywright.ts b/src/server/wkPlaywright.ts index d90004445a..df6c0847cf 100644 --- a/src/server/wkPlaywright.ts +++ b/src/server/wkPlaywright.ts @@ -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; - private _connectOptions: WKConnectOptions; - - constructor(process: ChildProcess, gracefullyClose: () => Promise, 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 { - 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 { - 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 { + async launchBrowserApp(options: LaunchOptions = {}): Promise { 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 { + async connect(options: ConnectOptions): Promise { return WKBrowser.connect(options); } diff --git a/src/webkit/wkBrowser.ts b/src/webkit/wkBrowser.ts index 8abfd1ad56..deded6e18b 100644 --- a/src/webkit/wkBrowser.ts +++ b/src/webkit/wkBrowser.ts @@ -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; - static async connect(options: WKConnectOptions): Promise { + static async connect(options: ConnectOptions): Promise { 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 { - 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); -} diff --git a/src/webkit/wkConnection.ts b/src/webkit/wkConnection.ts index 4a787694ed..2f167d7526 100644 --- a/src/webkit/wkConnection.ts +++ b/src/webkit/wkConnection.ts @@ -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; diff --git a/test/browser.spec.js b/test/browser.spec.js index b598f59ba0..4fd02de855 100644 --- a/test/browser.spec.js +++ b/test/browser.spec.js @@ -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); }); }); diff --git a/test/chromium/chromium.spec.js b/test/chromium/chromium.spec.js index 47408a6607..6b44399797 100644 --- a/test/chromium/chromium.spec.js +++ b/test/chromium/chromium.spec.js @@ -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); diff --git a/test/chromium/connect.spec.js b/test/chromium/connect.spec.js index 6d5859de79..a0a52684a8 100644 --- a/test/chromium/connect.spec.js +++ b/test/chromium/connect.spec.js @@ -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(), diff --git a/test/chromium/launcher.spec.js b/test/chromium/launcher.spec.js index 9e0b8016c5..25a079a7c1 100644 --- a/test/chromium/launcher.spec.js +++ b/test/chromium/launcher.spec.js @@ -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); diff --git a/test/firefox/launcher.spec.js b/test/firefox/launcher.spec.js index 599eebac34..2f38df5459 100644 --- a/test/firefox/launcher.spec.js +++ b/test/firefox/launcher.spec.js @@ -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(); }); }); }); diff --git a/test/fixtures/closeme.js b/test/fixtures/closeme.js index 85543fa917..7b527d09d9 100644 --- a/test/fixtures/closeme.js +++ b/test/fixtures/closeme.js @@ -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()); })(); diff --git a/test/fixtures/dumpio.js b/test/fixtures/dumpio.js index 0f090aa92d..abb8407ced 100644 --- a/test/fixtures/dumpio.js +++ b/test/fixtures/dumpio.js @@ -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) { } diff --git a/test/launcher.spec.js b/test/launcher.spec.js index d5883717b1..d2a85e5b52 100644 --- a/test/launcher.spec.js +++ b/test/launcher.spec.js @@ -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:/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); diff --git a/test/playwright.spec.js b/test/playwright.spec.js index c8e7045fab..2e8257a681 100644 --- a/test/playwright.spec.js +++ b/test/playwright.spec.js @@ -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); } diff --git a/test/web.spec.js b/test/web.spec.js index e8df7cd367..d0a30c0562 100644 --- a/test/web.spec.js +++ b/test/web.spec.js @@ -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 => { diff --git a/test/webkit/launcher.spec.js b/test/webkit/launcher.spec.js index 3074da3d7d..d1a302a896 100644 --- a/test/webkit/launcher.spec.js +++ b/test/webkit/launcher.spec.js @@ -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; }); }); diff --git a/utils/protocol-types-generator/index.js b/utils/protocol-types-generator/index.js index 1e65c120a8..7562362155 100644 --- a/utils/protocol-types-generator/index.js +++ b/utils/protocol-types-generator/index.js @@ -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)}`); }