diff --git a/docs/api.md b/docs/api.md index da9a650db7..89f2144c0c 100644 --- a/docs/api.md +++ b/docs/api.md @@ -173,6 +173,7 @@ Closes browser and all of its pages (if any were opened). The [Browser] object i Returns the default browser context. The default browser context can not be closed. #### browser.disconnect() +- returns: <[Promise]> Disconnects Playwright from the browser, but leaves the browser process running. After calling `disconnect`, the [Browser] object is considered disposed and cannot be used anymore. @@ -3603,11 +3604,21 @@ Browser websocket endpoint which can be used as an argument to [firefoxPlaywrigh * extends: [Playwright] +- [webkitPlaywright.connect(options)](#webkitplaywrightconnectoptions) - [webkitPlaywright.defaultArgs([options])](#webkitplaywrightdefaultargsoptions) - [webkitPlaywright.launch([options])](#webkitplaywrightlaunchoptions) - [webkitPlaywright.launchServer([options])](#webkitplaywrightlaunchserveroptions) +#### webkitPlaywright.connect(options) +- `options` <[Object]> + - `browserWSEndpoint` a [browser websocket endpoint](#browserwsendpoint) to connect to. + - `slowMo` <[number]> Slows down Playwright operations by the specified amount of milliseconds. Useful so that you can see what is going on. + - `transport` <[ConnectionTransport]> **Experimental** Specify a custom transport object for Playwright to use. +- returns: <[Promise]<[WebKitBrowser]>> + +This methods attaches Playwright to an existing WebKit instance. + #### webkitPlaywright.defaultArgs([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`. @@ -3631,6 +3642,7 @@ The default flags that WebKit will be launched with. - `timeout` <[number]> Maximum time in milliseconds to wait for the browser instance to start. Defaults to `30000` (30 seconds). Pass `0` to disable timeout. - `dumpio` <[boolean]> Whether to pipe the browser process stdout and stderr into `process.stdout` and `process.stderr`. Defaults to `false`. - `env` <[Object]> Specify environment variables that will be visible to the browser. Defaults to `process.env`. + - `pipe` <[boolean]> Connects to the browser over a pipe instead of a WebSocket. Defaults to `false`. - returns: <[Promise]<[WebKitBrowser]>> Promise which resolves to browser instance. @@ -3655,6 +3667,7 @@ const browser = await playwright.launch({ - `timeout` <[number]> Maximum time in milliseconds to wait for the browser instance to start. Defaults to `30000` (30 seconds). Pass `0` to disable timeout. - `dumpio` <[boolean]> Whether to pipe the browser process stdout and stderr into `process.stdout` and `process.stderr`. Defaults to `false`. - `env` <[Object]> Specify environment variables that will be visible to the browser. Defaults to `process.env`. + - `pipe` <[boolean]> Connects to the browser over a pipe instead of a WebSocket. Defaults to `false`. - returns: <[Promise]<[WebKitBrowserServer]>> Promise which resolves to browser server instance. ### class: WebKitBrowser @@ -3670,6 +3683,7 @@ WebKit browser instance does not expose WebKit-specific features. - [webKitBrowserServer.connect()](#webkitbrowserserverconnect) - [webKitBrowserServer.connectOptions()](#webkitbrowserserverconnectoptions) - [webKitBrowserServer.process()](#webkitbrowserserverprocess) +- [webKitBrowserServer.wsEndpoint()](#webkitbrowserserverwsendpoint) #### webKitBrowserServer.close() @@ -3692,6 +3706,11 @@ This options object can be passed to [webKitPlaywright.connect(options)](#webkit #### 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). diff --git a/package.json b/package.json index ed636e9014..89208677ba 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "progress": "^2.0.3", "proxy-from-env": "^1.0.0", "rimraf": "^2.6.1", + "uuid": "^3.4.0", "ws": "^6.1.0" }, "devDependencies": { @@ -56,6 +57,7 @@ "@types/pngjs": "^3.4.0", "@types/proxy-from-env": "^1.0.0", "@types/rimraf": "^2.0.2", + "@types/uuid": "^3.4.6", "@types/ws": "^6.0.1", "@typescript-eslint/eslint-plugin": "^2.6.1", "@typescript-eslint/parser": "^2.6.1", diff --git a/src/browser.ts b/src/browser.ts index 137f7a40a5..e118bf2564 100644 --- a/src/browser.ts +++ b/src/browser.ts @@ -22,7 +22,7 @@ export interface Browser extends platform.EventEmitterType { browserContexts(): BrowserContext[]; defaultContext(): BrowserContext; - disconnect(): void; + disconnect(): Promise; isConnected(): boolean; close(): Promise; } diff --git a/src/chromium/crBrowser.ts b/src/chromium/crBrowser.ts index 3740ae0ac3..9a5708ac91 100644 --- a/src/chromium/crBrowser.ts +++ b/src/chromium/crBrowser.ts @@ -299,8 +299,10 @@ export class CRBrowser extends platform.EventEmitter implements Browser { return CRTarget.fromPage(page); } - disconnect() { - this._connection.dispose(); + async disconnect() { + const disconnected = new Promise(f => this.once(CommonEvents.Browser.Disconnected, f)); + this._connection.close(); + await disconnected; } isConnected(): boolean { diff --git a/src/chromium/crConnection.ts b/src/chromium/crConnection.ts index ad45fae53f..a23c67e2e0 100644 --- a/src/chromium/crConnection.ts +++ b/src/chromium/crConnection.ts @@ -81,8 +81,6 @@ export class CRConnection extends platform.EventEmitter { } _onClose() { - if (this._closed) - return; this._closed = true; this._transport.onmessage = undefined; this._transport.onclose = undefined; @@ -92,9 +90,9 @@ export class CRConnection extends platform.EventEmitter { Promise.resolve().then(() => this.emit(ConnectionEvents.Disconnected)); } - dispose() { - this._onClose(); - this._transport.close(); + close() { + if (!this._closed) + this._transport.close(); } async createSession(targetInfo: Protocol.Target.TargetInfo): Promise { diff --git a/src/firefox/ffBrowser.ts b/src/firefox/ffBrowser.ts index 1d2292cfe1..7207bf8e49 100644 --- a/src/firefox/ffBrowser.ts +++ b/src/firefox/ffBrowser.ts @@ -70,8 +70,10 @@ export class FFBrowser extends platform.EventEmitter implements Browser { ]; } - disconnect() { - this._connection.dispose(); + async disconnect() { + const disconnected = new Promise(f => this.once(Events.Browser.Disconnected, f)); + this._connection.close(); + await disconnected; } isConnected(): boolean { diff --git a/src/firefox/ffConnection.ts b/src/firefox/ffConnection.ts index 6cb4c9fac4..57b7cc408d 100644 --- a/src/firefox/ffConnection.ts +++ b/src/firefox/ffConnection.ts @@ -120,8 +120,6 @@ export class FFConnection extends platform.EventEmitter { } _onClose() { - if (this._closed) - return; this._closed = true; this._transport.onmessage = undefined; this._transport.onclose = undefined; @@ -134,9 +132,9 @@ export class FFConnection extends platform.EventEmitter { Promise.resolve().then(() => this.emit(ConnectionEvents.Disconnected)); } - dispose() { - this._onClose(); - this._transport.close(); + close() { + if (!this._closed) + this._transport.close(); } async createSession(targetId: string): Promise { diff --git a/src/server/pipeTransport.ts b/src/server/pipeTransport.ts index 8f7bb61243..cb1a858e87 100644 --- a/src/server/pipeTransport.ts +++ b/src/server/pipeTransport.ts @@ -69,5 +69,7 @@ export class PipeTransport implements ConnectionTransport { close() { this._pipeWrite = null; helper.removeEventListeners(this._eventListeners); + if (this.onclose) + this.onclose.call(null); } } diff --git a/src/server/processLauncher.ts b/src/server/processLauncher.ts index 0d814ec810..db5e4bad2c 100644 --- a/src/server/processLauncher.ts +++ b/src/server/processLauncher.ts @@ -130,7 +130,7 @@ export async function launchProcess(options: LaunchProcessOptions): Promise`); helper.removeEventListeners(listeners); if (spawnedProcess.pid && !spawnedProcess.killed && !processClosed) { - // Force kill chrome. + // Force kill the browser. try { if (process.platform === 'win32') childProcess.execSync(`taskkill /pid ${spawnedProcess.pid} /T /F`); diff --git a/src/server/wkPlaywright.ts b/src/server/wkPlaywright.ts index e4448b34db..46574a9e54 100644 --- a/src/server/wkPlaywright.ts +++ b/src/server/wkPlaywright.ts @@ -19,7 +19,7 @@ import { BrowserFetcher, BrowserFetcherOptions } from './browserFetcher'; import { DeviceDescriptors } from '../deviceDescriptors'; import { TimeoutError } from '../errors'; import * as types from '../types'; -import { WKBrowser, createTransport } from '../webkit/wkBrowser'; +import { WKBrowser } from '../webkit/wkBrowser'; import { WKConnectOptions } from '../webkit/wkBrowser'; import { execSync, ChildProcess } from 'child_process'; import { PipeTransport } from './pipeTransport'; @@ -32,6 +32,9 @@ import * as os from 'os'; import { assert } from '../helper'; import { kBrowserCloseMessageId } from '../webkit/wkConnection'; import { Playwright } from './playwright'; +import { ConnectionTransport } from '../transport'; +import * as ws from 'ws'; +import * as uuidv4 from 'uuid/v4'; export type SlowMoOptions = { slowMo?: number, @@ -52,6 +55,7 @@ export type LaunchOptions = WebKitArgOptions & SlowMoOptions & { timeout?: number, dumpio?: boolean, env?: {[key: string]: string} | undefined, + pipe?: boolean, }; export class WKBrowserServer { @@ -76,6 +80,10 @@ export class WKBrowserServer { return this._process; } + wsEndpoint(): string | null { + return this._connectOptions.browserWSEndpoint || null; + } + connectOptions(): WKConnectOptions { return this._connectOptions; } @@ -110,6 +118,7 @@ export class WKPlaywright implements Playwright { handleSIGTERM = true, handleSIGHUP = true, slowMo = 0, + pipe = false, } = options; const webkitArguments = []; @@ -142,7 +151,7 @@ export class WKPlaywright implements Playwright { if (options.headless !== false) webkitArguments.push('--headless'); - let connectOptions: WKConnectOptions | undefined = undefined; + let transport: PipeTransport | undefined = undefined; const { launchedProcess, gracefullyClose } = await launchProcess({ executablePath: webkitExecutable!, @@ -155,20 +164,30 @@ export class WKPlaywright implements Playwright { pipe: true, tempDir: temporaryUserDataDir || undefined, attemptToGracefullyClose: async () => { - if (!connectOptions) + if (!transport) return Promise.reject(); // We try to gracefully close to prevent crash reporting and core dumps. - const transport = await createTransport(connectOptions); const message = JSON.stringify({method: 'Browser.close', params: {}, id: kBrowserCloseMessageId}); transport.send(message); }, }); - const transport = new PipeTransport(launchedProcess.stdio[3] as NodeJS.WritableStream, launchedProcess.stdio[4] as NodeJS.ReadableStream); - connectOptions = { transport, slowMo }; + transport = new PipeTransport(launchedProcess.stdio[3] as NodeJS.WritableStream, launchedProcess.stdio[4] as NodeJS.ReadableStream); + + let connectOptions: WKConnectOptions; + if (!pipe) { + const browserWSEndpoint = wrapTransportWithWebSocket(transport); + connectOptions = { browserWSEndpoint, slowMo }; + } else { + connectOptions = { transport, slowMo }; + } return new WKBrowserServer(launchedProcess, gracefullyClose, connectOptions); } + async connect(options: WKConnectOptions): Promise { + return WKBrowser.connect(options); + } + executablePath(): string { return this._resolveExecutablePath().executablePath; } @@ -252,3 +271,39 @@ function getMacVersion() { return cachedMacVersion; } +function wrapTransportWithWebSocket(transport: ConnectionTransport) { + const server = new ws.Server({ port: 0 }); + let socket: ws | undefined; + const guid = uuidv4(); + + server.on('connection', (s, req) => { + if (req.url !== '/' + guid) { + s.close(); + return; + } + if (socket) { + s.close(undefined, 'Multiple connections are not supported'); + return; + } + socket = s; + s.on('message', message => transport.send(Buffer.from(message).toString())); + transport.onmessage = message => s.send(message); + s.on('close', () => { + socket = undefined; + transport.onmessage = undefined; + }); + }); + + transport.onclose = () => { + if (socket) + socket.close(undefined, 'Browser disconnected'); + server.close(); + transport.onmessage = undefined; + transport.onclose = undefined; + }; + + const address = server.address(); + if (typeof address === 'string') + return address + '/' + guid; + return 'ws://127.0.0.1:' + address.port + '/' + guid; +} diff --git a/src/transport.ts b/src/transport.ts index 0659429164..f6d4feb7ba 100644 --- a/src/transport.ts +++ b/src/transport.ts @@ -17,7 +17,7 @@ export interface ConnectionTransport { send(s: string): void; - close(): void; + close(): void; // Note: calling close is expected to issue onclose at some point. onmessage?: (message: string) => void, onclose?: () => void, } @@ -27,7 +27,6 @@ export class SlowMoTransport { private readonly _delegate: ConnectionTransport; private _incomingMessageQueue: string[] = []; private _dispatchTimerId?: NodeJS.Timer; - private _closed = false; onmessage?: (message: string) => void; onclose?: () => void; @@ -60,8 +59,6 @@ export class SlowMoTransport { } private _dispatchOneMessageFromQueue() { - if (this._closed) - return; const message = this._incomingMessageQueue.shift(); try { if (this.onmessage) @@ -72,11 +69,8 @@ export class SlowMoTransport { } private _onClose() { - if (this._closed) - return; if (this.onclose) this.onclose(); - this._closed = true; this._delegate.onmessage = undefined; this._delegate.onclose = undefined; } @@ -86,7 +80,6 @@ export class SlowMoTransport { } close() { - this._closed = true; this._delegate.close(); } } diff --git a/src/webkit/wkBrowser.ts b/src/webkit/wkBrowser.ts index 97d3c5bc5e..8abfd1ad56 100644 --- a/src/webkit/wkBrowser.ts +++ b/src/webkit/wkBrowser.ts @@ -30,7 +30,8 @@ import * as platform from '../platform'; export type WKConnectOptions = { slowMo?: number, - transport: ConnectionTransport; + browserWSEndpoint?: string, + transport?: ConnectionTransport, }; export class WKBrowser extends platform.EventEmitter implements Browser { @@ -148,12 +149,14 @@ export class WKBrowser extends platform.EventEmitter implements Browser { pageProxy.handleProvisionalLoadFailed(event); } - disconnect() { - throw new Error('Unsupported operation'); + async disconnect() { + const disconnected = new Promise(f => this.once(Events.Browser.Disconnected, f)); + this._connection.close(); + await disconnected; } isConnected(): boolean { - return true; + return !this._connection.isClosed(); } async close() { @@ -227,6 +230,11 @@ export class WKBrowser extends platform.EventEmitter implements Browser { } export async function createTransport(options: WKConnectOptions): Promise { - assert(!!options.transport, 'Transport must be passed to connect'); - return SlowMoTransport.wrap(options.transport, options.slowMo); + 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 329c2a5b05..4a787694ed 100644 --- a/src/webkit/wkConnection.ts +++ b/src/webkit/wkConnection.ts @@ -73,8 +73,6 @@ export class WKConnection { } _onClose() { - if (this._closed) - return; this._closed = true; this._transport.onmessage = undefined; this._transport.onclose = undefined; @@ -82,9 +80,13 @@ export class WKConnection { this._onDisconnect(); } - dispose() { - this._onClose(); - this._transport.close(); + isClosed() { + return this._closed; + } + + close() { + if (!this._closed) + this._transport.close(); } } diff --git a/test/chromium/connect.spec.js b/test/chromium/connect.spec.js index 4ca873bc18..3e1544e849 100644 --- a/test/chromium/connect.spec.js +++ b/test/chromium/connect.spec.js @@ -50,26 +50,6 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p remote.close(), ]); }); - it('should be able to reconnect to a browser', async({server}) => { - const browserServer = await playwright.launchServer(defaultBrowserOptions); - const browser = await browserServer.connect(); - const browserWSEndpoint = browserServer.wsEndpoint(); - const page = await browser.defaultContext().newPage(); - await page.goto(server.PREFIX + '/frames/nested-frames.html'); - - const remote = await playwright.connect({...defaultBrowserOptions, browserWSEndpoint}); - const pages = await remote.defaultContext().pages(); - const restoredPage = pages.find(page => page.url() === server.PREFIX + '/frames/nested-frames.html'); - expect(utils.dumpFrames(restoredPage.mainFrame())).toEqual([ - 'http://localhost:/frames/nested-frames.html', - ' http://localhost:/frames/frame.html (aframe)', - ' http://localhost:/frames/two-frames.html (2frames)', - ' http://localhost:/frames/frame.html (dos)', - ' http://localhost:/frames/frame.html (uno)', - ]); - expect(await restoredPage.evaluate(() => 7 * 8)).toBe(56); - await remote.close(); - }); // @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); @@ -84,65 +64,4 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p await local.close(); }); }); - - 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 local = await browserServer.connect(); - const remote = await playwright.connect({...defaultBrowserOptions, browserWSEndpoint: browserServer.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'); - remote.disconnect(); - const error = await navigationPromise; - expect(error.message).toBe('Navigation failed because browser has disconnected!'); - await local.close(); - }); - it('should reject waitForSelector when browser closes', async({server}) => { - server.setRoute('/empty.html', () => {}); - const browserServer = await playwright.launchServer(defaultBrowserOptions); - const local = await browserServer.connect(); - const remote = await playwright.connect({...defaultBrowserOptions, browserWSEndpoint: browserServer.wsEndpoint()}); - const page = await remote.defaultContext().newPage(); - const watchdog = page.waitForSelector('div', { timeout: 60000 }).catch(e => e); - remote.disconnect(); - const error = await watchdog; - expect(error.message).toContain('Protocol error'); - await local.close(); - }); - }); - - describe('Browser.close', function() { - it('should terminate network waiters', async({context, server}) => { - const browserServer = await playwright.launchServer(defaultBrowserOptions); - const local = await browserServer.connect(); - const remote = await playwright.connect({...defaultBrowserOptions, browserWSEndpoint: browserServer.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), - local.close() - ]); - for (let i = 0; i < 2; i++) { - const message = results[i].message; - expect(message).toContain('Target closed'); - expect(message).not.toContain('Timeout'); - } - }); - }); - - describe('Browser.isConnected', () => { - it('should set the browser connected state', async () => { - const browserServer = await playwright.launchServer(defaultBrowserOptions); - const local = await browserServer.connect(); - const browserWSEndpoint = browserServer.wsEndpoint(); - const newBrowser = await playwright.connect({browserWSEndpoint}); - expect(newBrowser.isConnected()).toBe(true); - newBrowser.disconnect(); - expect(newBrowser.isConnected()).toBe(false); - await browserServer.close(); - }); - }); - }; diff --git a/test/launcher.spec.js b/test/launcher.spec.js index 01af97bb9b..7122821796 100644 --- a/test/launcher.spec.js +++ b/test/launcher.spec.js @@ -80,4 +80,84 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p expect(Devices['iPhone 6']).toBe(playwright.devices['iPhone 6']); }); }); + + describe('Browser.isConnected', () => { + it('should set the browser connected state', async () => { + const browserServer = await playwright.launchServer(defaultBrowserOptions); + const browserWSEndpoint = browserServer.wsEndpoint(); + const remote = await playwright.connect({browserWSEndpoint}); + expect(remote.isConnected()).toBe(true); + await remote.disconnect(); + expect(remote.isConnected()).toBe(false); + await browserServer.close(); + }); + }); + + 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 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(); + }); + 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 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(); + }); + }); + + 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 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() + ]); + for (let i = 0; i < 2; i++) { + const message = results[i].message; + expect(message).toContain('Target closed'); + expect(message).not.toContain('Timeout'); + } + }); + }); + + 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 browserServer.connect(); + const browserWSEndpoint = browserServer.wsEndpoint(); + const page = await browser.defaultContext().newPage(); + await page.goto(server.PREFIX + '/frames/nested-frames.html'); + await browser.disconnect(); + + const remote = await playwright.connect({...defaultBrowserOptions, browserWSEndpoint}); + const pages = await remote.defaultContext().pages(); + const restoredPage = pages.find(page => page.url() === server.PREFIX + '/frames/nested-frames.html'); + expect(utils.dumpFrames(restoredPage.mainFrame())).toEqual([ + 'http://localhost:/frames/nested-frames.html', + ' http://localhost:/frames/frame.html (aframe)', + ' http://localhost:/frames/two-frames.html (2frames)', + ' http://localhost:/frames/frame.html (dos)', + ' http://localhost:/frames/frame.html (uno)', + ]); + expect(await restoredPage.evaluate(() => 7 * 8)).toBe(56); + await browserServer.close(); + }); + }); }; diff --git a/test/webkit/launcher.spec.js b/test/webkit/launcher.spec.js index 6e5dc0be51..f46965f47b 100644 --- a/test/webkit/launcher.spec.js +++ b/test/webkit/launcher.spec.js @@ -15,7 +15,7 @@ * limitations under the License. */ -module.exports.describe = function ({ testRunner, expect, playwright }) { +module.exports.describe = function ({ testRunner, expect, playwright, defaultBrowserOptions }) { const {describe, xdescribe, fdescribe} = testRunner; const {it, fit, xit, dit} = testRunner; const {beforeAll, beforeEach, afterAll, afterEach} = testRunner; @@ -26,5 +26,48 @@ module.exports.describe = function ({ testRunner, expect, playwright }) { expect(playwright.defaultArgs().length).toBe(0); }); }); + + 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 browserServer.connect(); + expect((await browser.defaultContext().pages()).length).toBe(1); + expect(browserServer.wsEndpoint()).not.toBe(null); + const page = await browser.defaultContext().newPage(); + expect(await page.evaluate('11 * 11')).toBe(121); + await page.close(); + await browserServer.close(); + }); + it('should support the pipe option', async() => { + const options = Object.assign({pipe: true}, defaultBrowserOptions); + const browserServer = await playwright.launchServer(options); + const browser = await browserServer.connect(); + expect((await browser.defaultContext().pages()).length).toBe(1); + expect(browserServer.wsEndpoint()).toBe(null); + const page = await browser.defaultContext().newPage(); + expect(await page.evaluate('11 * 11')).toBe(121); + await page.close(); + await browserServer.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 browserServer.connect(); + const disconnectedEventPromise = new Promise(resolve => browser.once('disconnected', resolve)); + // Emulate user exiting browser. + process.kill(-browserServer.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 browserServer.connect(); + const disconnectedEventPromise = new Promise(resolve => browser.once('disconnected', resolve)); + // Emulate user exiting browser. + process.kill(-browserServer.process().pid, 'SIGKILL'); + await disconnectedEventPromise; + }); + }); }); };