From 331f0e603a3d93250c93624364d34d30b94c3269 Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Thu, 19 Dec 2019 14:51:49 -0800 Subject: [PATCH] feat: introduce BrowserServer (#308) --- src/browser.ts | 43 ++++++++++ src/chromium/Browser.ts | 106 +++++++++++++++++------- src/chromium/Launcher.ts | 68 ++------------- src/chromium/Playwright.ts | 67 +++++++++++++-- src/chromium/api.ts | 1 - src/chromium/events.ts | 5 +- src/chromium/features/chromium.ts | 106 ------------------------ src/firefox/Browser.ts | 21 ++--- src/firefox/Launcher.ts | 16 +--- src/firefox/Playwright.ts | 19 +++-- src/firefox/features/firefox.ts | 15 ---- src/processLauncher.ts | 1 + src/transport.ts | 5 +- src/webkit/Browser.ts | 22 ++--- src/webkit/Launcher.ts | 7 +- src/webkit/Playwright.ts | 11 ++- test/browser.spec.js | 4 +- test/chromium/browser.spec.js | 8 +- test/chromium/chromium.spec.js | 60 +++++++------- test/chromium/connect.spec.js | 86 ++++++++++--------- test/chromium/headful.spec.js | 6 +- test/chromium/launcher.spec.js | 44 +++++----- test/chromium/oopif.spec.js | 2 +- test/chromium/session.spec.js | 10 +-- test/chromium/tracing.spec.js | 30 +++---- test/firefox/browser.spec.js | 17 ---- test/firefox/launcher.spec.js | 6 +- test/fixtures/closeme.js | 7 +- test/playwright.spec.js | 11 ++- utils/browser/test.js | 11 +-- utils/protocol-types-generator/index.js | 8 +- 31 files changed, 378 insertions(+), 445 deletions(-) create mode 100644 src/browser.ts delete mode 100644 src/chromium/features/chromium.ts delete mode 100644 src/firefox/features/firefox.ts delete mode 100644 test/firefox/browser.spec.js diff --git a/src/browser.ts b/src/browser.ts new file mode 100644 index 0000000000..02eba0330a --- /dev/null +++ b/src/browser.ts @@ -0,0 +1,43 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +import { BrowserContext, BrowserContextOptions } from './browserContext'; +import { ChildProcess } from 'child_process'; + +export interface Browser { + newContext(options?: BrowserContextOptions): Promise; + disconnect(): void; + isConnected(): boolean; + + browserContexts(): BrowserContext[]; + defaultContext(): BrowserContext; + close(): Promise; +} + +export class BrowserServer { + private _browser: T; + private _process: ChildProcess; + private _wsEndpoint: string; + + constructor(browser: T, process: ChildProcess, wsEndpoint: string) { + this._browser = browser; + this._process = process; + this._wsEndpoint = wsEndpoint; + } + + async connect(): Promise { + return this._browser; + } + + process(): ChildProcess { + return this._process; + } + + wsEndpoint(): string { + return this._wsEndpoint; + } + + async close(): Promise { + await this._browser.close(); + } +} diff --git a/src/chromium/Browser.ts b/src/chromium/Browser.ts index b2febed4e7..f83e07e9ba 100644 --- a/src/chromium/Browser.ts +++ b/src/chromium/Browser.ts @@ -15,7 +15,6 @@ * limitations under the License. */ -import * as childProcess from 'child_process'; import { EventEmitter } from 'events'; import { Events } from './events'; import { assert, helper } from '../helper'; @@ -24,44 +23,41 @@ import { Connection, ConnectionEvents, CDPSession } from './Connection'; import { Page } from '../page'; import { Target } from './Target'; import { Protocol } from './protocol'; -import { Chromium } from './features/chromium'; import { FrameManager } from './FrameManager'; +import * as browser from '../browser'; import * as network from '../network'; import { Permissions } from './features/permissions'; import { Overrides } from './features/overrides'; +import { Worker } from './features/workers'; import { ConnectionTransport } from '../transport'; +import { readProtocolStream } from './protocolHelper'; -export class Browser extends EventEmitter { - private _process: childProcess.ChildProcess; +export class Browser extends EventEmitter implements browser.Browser { _connection: Connection; _client: CDPSession; private _defaultContext: BrowserContext; private _contexts = new Map(); _targets = new Map(); - readonly chromium: Chromium; + + private _tracingRecording = false; + private _tracingPath = ''; + private _tracingClient: CDPSession | undefined; static async create( - browserWSEndpoint: string, - transport: ConnectionTransport, - process: childProcess.ChildProcess | null) { + transport: ConnectionTransport) { const connection = new Connection(transport); const { browserContextIds } = await connection.rootSession.send('Target.getBrowserContexts'); - const browser = new Browser(browserWSEndpoint, connection, browserContextIds, process); + const browser = new Browser(connection, browserContextIds); await connection.rootSession.send('Target.setDiscoverTargets', { discover: true }); + await browser.waitForTarget(t => t.type() === 'page'); return browser; } - constructor( - browserWSEndpoint: string, - connection: Connection, - contextIds: string[], - process: childProcess.ChildProcess | null) { + constructor(connection: Connection, contextIds: string[]) { super(); this._connection = connection; this._client = connection.rootSession; - this._process = process; - this.chromium = new Chromium(this, browserWSEndpoint); this._defaultContext = this._createBrowserContext(null, {}); for (const contextId of contextIds) @@ -139,10 +135,6 @@ export class Browser extends EventEmitter { return context; } - process(): childProcess.ChildProcess | null { - return this._process; - } - async newContext(options: BrowserContextOptions = {}): Promise { const { browserContextId } = await this._client.send('Target.createBrowserContext'); const context = this._createBrowserContext(browserContextId, options); @@ -167,8 +159,8 @@ export class Browser extends EventEmitter { assert(!this._targets.has(event.targetInfo.targetId), 'Target should not exist before targetCreated'); this._targets.set(event.targetInfo.targetId, target); - if (await target._initializedPromise) - this.chromium.emit(Events.Chromium.TargetCreated, target); + if (target._isInitialized || await target._initializedPromise) + this.emit(Events.Browser.TargetCreated, target); } async _targetDestroyed(event: { targetId: string; }) { @@ -177,7 +169,7 @@ export class Browser extends EventEmitter { this._targets.delete(event.targetId); target._didClose(); if (await target._initializedPromise) - this.chromium.emit(Events.Chromium.TargetDestroyed, target); + this.emit(Events.Browser.TargetDestroyed, target); } _targetInfoChanged(event: Protocol.Target.targetInfoChangedPayload) { @@ -187,7 +179,7 @@ export class Browser extends EventEmitter { const wasInitialized = target._isInitialized; target._targetInfoChanged(event.targetInfo); if (wasInitialized && previousURL !== target.url()) - this.chromium.emit(Events.Chromium.TargetChanged, target); + this.emit(Events.Browser.TargetChanged, target); } async _closePage(page: Page) { @@ -202,7 +194,7 @@ export class Browser extends EventEmitter { await (page._delegate as FrameManager)._client.send('Target.activateTarget', {targetId: Target.fromPage(page)._targetId}); } - async _waitForTarget(predicate: (arg0: Target) => boolean, options: { timeout?: number; } | undefined = {}): Promise { + async waitForTarget(predicate: (arg0: Target) => boolean, options: { timeout?: number; } | undefined = {}): Promise { const { timeout = 30000 } = options; @@ -211,15 +203,15 @@ export class Browser extends EventEmitter { return existingTarget; let resolve: (target: Target) => void; const targetPromise = new Promise(x => resolve = x); - this.chromium.on(Events.Chromium.TargetCreated, check); - this.chromium.on(Events.Chromium.TargetChanged, check); + this.on(Events.Browser.TargetCreated, check); + this.on(Events.Browser.TargetChanged, check); try { if (!timeout) return await targetPromise; return await helper.waitWithTimeout(targetPromise, 'target', timeout); } finally { - this.chromium.removeListener(Events.Chromium.TargetCreated, check); - this.chromium.removeListener(Events.Chromium.TargetChanged, check); + this.removeListener(Events.Browser.TargetCreated, check); + this.removeListener(Events.Browser.TargetChanged, check); } function check(target: Target) { @@ -233,6 +225,62 @@ export class Browser extends EventEmitter { this.disconnect(); } + browserTarget(): Target { + return [...this._targets.values()].find(t => t.type() === 'browser'); + } + + serviceWorker(target: Target): Promise { + return target._worker(); + } + + async startTracing(page: Page | undefined, options: { path?: string; screenshots?: boolean; categories?: string[]; } = {}) { + assert(!this._tracingRecording, 'Cannot start recording trace while already recording trace.'); + this._tracingClient = page ? (page._delegate as FrameManager)._client : this._client; + + const defaultCategories = [ + '-*', 'devtools.timeline', 'v8.execute', 'disabled-by-default-devtools.timeline', + 'disabled-by-default-devtools.timeline.frame', 'toplevel', + 'blink.console', 'blink.user_timing', 'latencyInfo', 'disabled-by-default-devtools.timeline.stack', + 'disabled-by-default-v8.cpu_profiler', 'disabled-by-default-v8.cpu_profiler.hires' + ]; + const { + path = null, + screenshots = false, + categories = defaultCategories, + } = options; + + if (screenshots) + categories.push('disabled-by-default-devtools.screenshot'); + + this._tracingPath = path; + this._tracingRecording = true; + await this._tracingClient.send('Tracing.start', { + transferMode: 'ReturnAsStream', + categories: categories.join(',') + }); + } + + async stopTracing(): Promise { + assert(this._tracingClient, 'Tracing was not started.'); + let fulfill: (buffer: Buffer) => void; + const contentPromise = new Promise(x => fulfill = x); + this._tracingClient.once('Tracing.tracingComplete', event => { + readProtocolStream(this._tracingClient, event.stream, this._tracingPath).then(fulfill); + }); + await this._tracingClient.send('Tracing.end'); + this._tracingRecording = false; + return contentPromise; + } + + targets(context?: BrowserContext): Target[] { + const targets = this._allTargets(); + return context ? targets.filter(t => t.browserContext() === context) : targets; + } + + pageTarget(page: Page): Target { + return Target.fromPage(page); + } + disconnect() { this._connection.dispose(); } diff --git a/src/chromium/Launcher.ts b/src/chromium/Launcher.ts index e3b3a11d06..efd5238485 100644 --- a/src/chromium/Launcher.ts +++ b/src/chromium/Launcher.ts @@ -16,18 +16,16 @@ */ import * as fs from 'fs'; -import * as http from 'http'; -import * as https from 'https'; import * as os from 'os'; import * as path from 'path'; -import * as URL from 'url'; -import { Browser } from './Browser'; +import * as util from 'util'; import { BrowserFetcher, BrowserFetcherOptions } from '../browserFetcher'; import { TimeoutError } from '../errors'; import { assert, helper } from '../helper'; -import { ConnectionTransport, WebSocketTransport, PipeTransport, SlowMoTransport } from '../transport'; -import * as util from 'util'; import { launchProcess, waitForLine } from '../processLauncher'; +import { ConnectionTransport, PipeTransport, SlowMoTransport, WebSocketTransport } from '../transport'; +import { Browser } from './Browser'; +import { BrowserServer } from '../browser'; const mkdtempAsync = helper.promisify(fs.mkdtemp); @@ -69,7 +67,7 @@ export class Launcher { this._preferredRevision = preferredRevision; } - async launch(options: (LauncherLaunchOptions & LauncherChromeArgOptions & ConnectionOptions) = {}): Promise { + async launch(options: (LauncherLaunchOptions & LauncherChromeArgOptions & ConnectionOptions) = {}): Promise> { const { ignoreDefaultArgs = false, args = [], @@ -139,9 +137,9 @@ export class Launcher { } else { transport = new PipeTransport(launchedProcess.stdio[3] as NodeJS.WritableStream, launchedProcess.stdio[4] as NodeJS.ReadableStream); } - browser = await Browser.create(browserWSEndpoint, SlowMoTransport.wrap(transport, slowMo), launchedProcess); - await browser._waitForTarget(t => t.type() === 'page'); - return browser; + + browser = await Browser.create(SlowMoTransport.wrap(transport, slowMo)); + return new BrowserServer(browser, launchedProcess, browserWSEndpoint); } catch (e) { if (browser) await browser.close(); @@ -178,26 +176,6 @@ export class Launcher { return this._resolveExecutablePath().executablePath; } - async connect(options: (ConnectionOptions & { - browserWSEndpoint?: string; - browserURL?: string; - transport?: ConnectionTransport; })): Promise { - assert(Number(!!options.browserWSEndpoint) + Number(!!options.browserURL) + Number(!!options.transport) === 1, 'Exactly one of browserWSEndpoint, browserURL or transport must be passed to playwright.connect'); - - let transport: ConnectionTransport | undefined; - let connectionURL: string = ''; - if (options.transport) { - transport = options.transport; - } else if (options.browserWSEndpoint) { - connectionURL = options.browserWSEndpoint; - transport = await WebSocketTransport.create(options.browserWSEndpoint); - } else if (options.browserURL) { - connectionURL = await getWSEndpoint(options.browserURL); - transport = await WebSocketTransport.create(connectionURL); - } - return Browser.create(connectionURL, SlowMoTransport.wrap(transport, options.slowMo), null); - } - _resolveExecutablePath(): { executablePath: string; missingText: string | null; } { const browserFetcher = createBrowserFetcher(this._projectRoot); const revisionInfo = browserFetcher.revisionInfo(this._preferredRevision); @@ -207,36 +185,6 @@ export class Launcher { } -function getWSEndpoint(browserURL: string): Promise { - let resolve: (url: string) => void; - let reject: (e: Error) => void; - const promise = new Promise((res, rej) => { resolve = res; reject = rej; }); - - const endpointURL = URL.resolve(browserURL, '/json/version'); - const protocol = endpointURL.startsWith('https') ? https : http; - const requestOptions = Object.assign(URL.parse(endpointURL), { method: 'GET' }); - const request = protocol.request(requestOptions, res => { - let data = ''; - if (res.statusCode !== 200) { - // Consume response data to free up memory. - res.resume(); - reject(new Error('HTTP ' + res.statusCode)); - return; - } - res.setEncoding('utf8'); - res.on('data', chunk => data += chunk); - res.on('end', () => resolve(JSON.parse(data).webSocketDebuggerUrl)); - }); - - request.on('error', reject); - request.end(); - - return promise.catch(e => { - e.message = `Failed to fetch browser webSocket url from ${endpointURL}: ` + e.message; - throw e; - }); -} - export type LauncherChromeArgOptions = { headless?: boolean, args?: string[], diff --git a/src/chromium/Playwright.ts b/src/chromium/Playwright.ts index dc3cceb30a..ae1a2e3831 100644 --- a/src/chromium/Playwright.ts +++ b/src/chromium/Playwright.ts @@ -15,12 +15,17 @@ * limitations under the License. */ -import { Browser } from './Browser'; +import * as http from 'http'; +import * as https from 'https'; +import * as URL from 'url'; +import * as browsers from '../browser'; import { BrowserFetcher, BrowserFetcherOptions, BrowserFetcherRevisionInfo, OnProgressCallback } from '../browserFetcher'; -import { ConnectionTransport } from '../transport'; -import { DeviceDescriptors, DeviceDescriptor } from '../deviceDescriptors'; +import { DeviceDescriptor, DeviceDescriptors } from '../deviceDescriptors'; import * as Errors from '../errors'; -import { Launcher, ConnectionOptions, LauncherChromeArgOptions, LauncherLaunchOptions, createBrowserFetcher } from './Launcher'; +import { assert } from '../helper'; +import { ConnectionTransport, WebSocketTransport, SlowMoTransport } from '../transport'; +import { ConnectionOptions, createBrowserFetcher, Launcher, LauncherChromeArgOptions, LauncherLaunchOptions } from './Launcher'; +import { Browser } from './Browser'; type Devices = { [name: string]: DeviceDescriptor } & DeviceDescriptor[]; @@ -42,15 +47,33 @@ export class Playwright { return revisionInfo; } - launch(options?: (LauncherLaunchOptions & LauncherChromeArgOptions & ConnectionOptions) | undefined): Promise { + async launch(options?: (LauncherLaunchOptions & LauncherChromeArgOptions & ConnectionOptions) | undefined): Promise { + const server = await this._launcher.launch(options); + return server.connect(); + } + + async launchServer(options: (LauncherLaunchOptions & LauncherChromeArgOptions & ConnectionOptions) = {}): Promise> { return this._launcher.launch(options); } - connect(options: (ConnectionOptions & { + async connect(options: (ConnectionOptions & { browserWSEndpoint?: string; browserURL?: string; transport?: ConnectionTransport; })): Promise { - return this._launcher.connect(options); + assert(Number(!!options.browserWSEndpoint) + Number(!!options.browserURL) + Number(!!options.transport) === 1, 'Exactly one of browserWSEndpoint, browserURL or transport must be passed to playwright.connect'); + + let transport: ConnectionTransport | undefined; + let connectionURL: string = ''; + if (options.transport) { + transport = options.transport; + } else if (options.browserWSEndpoint) { + connectionURL = options.browserWSEndpoint; + transport = await WebSocketTransport.create(options.browserWSEndpoint); + } else if (options.browserURL) { + connectionURL = await getWSEndpoint(options.browserURL); + transport = await WebSocketTransport.create(connectionURL); + } + return Browser.create(SlowMoTransport.wrap(transport, options.slowMo)); } executablePath(): string { @@ -76,3 +99,33 @@ export class Playwright { return createBrowserFetcher(this._projectRoot, options); } } + +function getWSEndpoint(browserURL: string): Promise { + let resolve: (url: string) => void; + let reject: (e: Error) => void; + const promise = new Promise((res, rej) => { resolve = res; reject = rej; }); + + const endpointURL = URL.resolve(browserURL, '/json/version'); + const protocol = endpointURL.startsWith('https') ? https : http; + const requestOptions = Object.assign(URL.parse(endpointURL), { method: 'GET' }); + const request = protocol.request(requestOptions, res => { + let data = ''; + if (res.statusCode !== 200) { + // Consume response data to free up memory. + res.resume(); + reject(new Error('HTTP ' + res.statusCode)); + return; + } + res.setEncoding('utf8'); + res.on('data', chunk => data += chunk); + res.on('end', () => resolve(JSON.parse(data).webSocketDebuggerUrl)); + }); + + request.on('error', reject); + request.end(); + + return promise.catch(e => { + e.message = `Failed to fetch browser webSocket url from ${endpointURL}: ` + e.message; + throw e; + }); +} \ No newline at end of file diff --git a/src/chromium/api.ts b/src/chromium/api.ts index 7bbda2a8aa..f3aa2dfc1e 100644 --- a/src/chromium/api.ts +++ b/src/chromium/api.ts @@ -12,7 +12,6 @@ export { Request, Response } from '../network'; export { BrowserContext } from '../browserContext'; export { CDPSession } from './Connection'; export { Accessibility } from './features/accessibility'; -export { Chromium } from './features/chromium'; export { Coverage } from './features/coverage'; export { Interception } from './features/interception'; export { Overrides } from './features/overrides'; diff --git a/src/chromium/events.ts b/src/chromium/events.ts index e6680b26b2..bd402c6ac5 100644 --- a/src/chromium/events.ts +++ b/src/chromium/events.ts @@ -17,10 +17,7 @@ export const Events = { Browser: { - Disconnected: 'disconnected' - }, - - Chromium: { + Disconnected: 'disconnected', TargetCreated: 'targetcreated', TargetDestroyed: 'targetdestroyed', TargetChanged: 'targetchanged', diff --git a/src/chromium/features/chromium.ts b/src/chromium/features/chromium.ts deleted file mode 100644 index 02e92de442..0000000000 --- a/src/chromium/features/chromium.ts +++ /dev/null @@ -1,106 +0,0 @@ -/** - * Copyright 2017 Google Inc. All rights reserved. - * Modifications 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 { EventEmitter } from 'events'; -import { assert } from '../../helper'; -import { Browser } from '../Browser'; -import { BrowserContext } from '../../browserContext'; -import { CDPSession } from '../Connection'; -import { Page } from '../../page'; -import { readProtocolStream } from '../protocolHelper'; -import { Target } from '../Target'; -import { Worker } from './workers'; -import { FrameManager } from '../FrameManager'; - -export class Chromium extends EventEmitter { - private _client: CDPSession; - private _recording = false; - private _path = ''; - private _tracingClient: CDPSession | undefined; - private _browser: Browser; - private _browserWSEndpoint: string; - - constructor(browser: Browser, browserWSEndpoint: string) { - super(); - this._browserWSEndpoint = browserWSEndpoint; - this._client = browser._client; - this._browser = browser; - } - - browserTarget(): Target { - return [...this._browser._targets.values()].find(t => t.type() === 'browser'); - } - - serviceWorker(target: Target): Promise { - return target._worker(); - } - - async startTracing(page: Page | undefined, options: { path?: string; screenshots?: boolean; categories?: string[]; } = {}) { - assert(!this._recording, 'Cannot start recording trace while already recording trace.'); - this._tracingClient = page ? (page._delegate as FrameManager)._client : this._client; - - const defaultCategories = [ - '-*', 'devtools.timeline', 'v8.execute', 'disabled-by-default-devtools.timeline', - 'disabled-by-default-devtools.timeline.frame', 'toplevel', - 'blink.console', 'blink.user_timing', 'latencyInfo', 'disabled-by-default-devtools.timeline.stack', - 'disabled-by-default-v8.cpu_profiler', 'disabled-by-default-v8.cpu_profiler.hires' - ]; - const { - path = null, - screenshots = false, - categories = defaultCategories, - } = options; - - if (screenshots) - categories.push('disabled-by-default-devtools.screenshot'); - - this._path = path; - this._recording = true; - await this._tracingClient.send('Tracing.start', { - transferMode: 'ReturnAsStream', - categories: categories.join(',') - }); - } - - async stopTracing(): Promise { - assert(this._tracingClient, 'Tracing was not started.'); - let fulfill: (buffer: Buffer) => void; - const contentPromise = new Promise(x => fulfill = x); - this._tracingClient.once('Tracing.tracingComplete', event => { - readProtocolStream(this._tracingClient, event.stream, this._path).then(fulfill); - }); - await this._tracingClient.send('Tracing.end'); - this._recording = false; - return contentPromise; - } - - targets(context?: BrowserContext): Target[] { - const targets = this._browser._allTargets(); - return context ? targets.filter(t => t.browserContext() === context) : targets; - } - - pageTarget(page: Page): Target { - return Target.fromPage(page); - } - - waitForTarget(predicate: (arg0: Target) => boolean, options: { timeout?: number; } | undefined = {}): Promise { - return this._browser._waitForTarget(predicate, options); - } - - wsEndpoint(): string { - return this._browserWSEndpoint; - } -} diff --git a/src/firefox/Browser.ts b/src/firefox/Browser.ts index 4a4729aecd..4119985b14 100644 --- a/src/firefox/Browser.ts +++ b/src/firefox/Browser.ts @@ -16,7 +16,6 @@ */ import { EventEmitter } from 'events'; -import { ChildProcess } from 'child_process'; import { helper, RegisteredListener, assert } from '../helper'; import { Connection, ConnectionEvents, JugglerSessionEvents } from './Connection'; import { Events } from './events'; @@ -24,35 +23,29 @@ import { Events as CommonEvents } from '../events'; import { Permissions } from './features/permissions'; import { Page } from '../page'; import { FrameManager } from './FrameManager'; -import { Firefox } from './features/firefox'; +import * as browser from '../browser'; import * as network from '../network'; import { BrowserContext, BrowserContextOptions } from '../browserContext'; import { ConnectionTransport } from '../transport'; -export class Browser extends EventEmitter { +export class Browser extends EventEmitter implements browser.Browser { _connection: Connection; - private _process: ChildProcess; _targets: Map; private _defaultContext: BrowserContext; private _contexts: Map; private _eventListeners: RegisteredListener[]; - readonly firefox: Firefox; - readonly _browserWSEndpoint: string; - static async create(browserWSEndpoint: string, transport: ConnectionTransport, process: ChildProcess | null) { + static async create(transport: ConnectionTransport) { const connection = new Connection(transport); const {browserContextIds} = await connection.send('Target.getBrowserContexts'); - const browser = new Browser(browserWSEndpoint, connection, browserContextIds, process); + const browser = new Browser(connection, browserContextIds); await connection.send('Target.enable'); return browser; } - constructor(browserWSEndpoint: string, connection: Connection, browserContextIds: Array, process: ChildProcess | null) { + constructor(connection: Connection, browserContextIds: Array) { super(); this._connection = connection; - this._process = process; - this.firefox = new Firefox(browserWSEndpoint); - this._targets = new Map(); this._defaultContext = this._createBrowserContext(null, {}); @@ -95,10 +88,6 @@ export class Browser extends EventEmitter { return this._defaultContext; } - process(): ChildProcess | null { - return this._process; - } - async _waitForTarget(predicate: (target: Target) => boolean, options: { timeout?: number; } = {}): Promise { const { timeout = 30000 diff --git a/src/firefox/Launcher.ts b/src/firefox/Launcher.ts index 85640f9986..44b38d7330 100644 --- a/src/firefox/Launcher.ts +++ b/src/firefox/Launcher.ts @@ -25,6 +25,7 @@ import { assert } from '../helper'; import { TimeoutError } from '../errors'; import { WebSocketTransport, SlowMoTransport } from '../transport'; import { launchProcess, waitForLine } from '../processLauncher'; +import { BrowserServer } from '../browser'; const mkdtempAsync = util.promisify(fs.mkdtemp); const writeFileAsync = util.promisify(fs.writeFile); @@ -58,7 +59,7 @@ export class Launcher { return firefoxArguments; } - async launch(options: any = {}): Promise { + async launch(options: any = {}): Promise> { const { ignoreDefaultArgs = false, args = [], @@ -122,9 +123,9 @@ export class Launcher { const match = await waitForLine(launchedProcess, launchedProcess.stdout, /^Juggler listening on (ws:\/\/.*)$/, timeout, timeoutError); const url = match[1]; const transport = await WebSocketTransport.create(url); - browser = await Browser.create(url, SlowMoTransport.wrap(transport, slowMo), launchedProcess); + browser = await Browser.create(SlowMoTransport.wrap(transport, slowMo)); await browser._waitForTarget(t => t.type() === 'page'); - return browser; + return new BrowserServer(browser, launchedProcess, url); } catch (e) { if (browser) await browser.close(); @@ -132,15 +133,6 @@ export class Launcher { } } - async connect(options: any = {}): Promise { - const { - browserWSEndpoint, - slowMo = 0, - } = options; - const transport = await WebSocketTransport.create(browserWSEndpoint); - return await Browser.create(browserWSEndpoint, SlowMoTransport.wrap(transport, slowMo), null); - } - executablePath(): string { return this._resolveExecutablePath().executablePath; } diff --git a/src/firefox/Playwright.ts b/src/firefox/Playwright.ts index 2fa46e8e5e..d8f5dd58d5 100644 --- a/src/firefox/Playwright.ts +++ b/src/firefox/Playwright.ts @@ -14,9 +14,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + +import * as browsers from '../browser'; import { Browser } from './Browser'; import { BrowserFetcher, BrowserFetcherOptions, OnProgressCallback, BrowserFetcherRevisionInfo } from '../browserFetcher'; -import { ConnectionTransport } from '../transport'; +import { WebSocketTransport, SlowMoTransport } from '../transport'; import { DeviceDescriptors, DeviceDescriptor } from '../deviceDescriptors'; import * as Errors from '../errors'; import { Launcher, createBrowserFetcher } from './Launcher'; @@ -41,15 +43,18 @@ export class Playwright { return revisionInfo; } - launch(options: any): Promise { + async launch(options: any): Promise { + const server = await this._launcher.launch(options); + return server.connect(); + } + + async launchServer(options: any): Promise> { return this._launcher.launch(options); } - connect(options: (any & { - browserWSEndpoint?: string; - browserURL?: string; - transport?: ConnectionTransport; })): Promise { - return this._launcher.connect(options); + async connect(options: { slowMo?: number, browserWSEndpoint: string }): Promise { + const transport = await WebSocketTransport.create(options.browserWSEndpoint); + return Browser.create(SlowMoTransport.wrap(transport, options.slowMo || 0)); } executablePath(): string { diff --git a/src/firefox/features/firefox.ts b/src/firefox/features/firefox.ts deleted file mode 100644 index 684c78a641..0000000000 --- a/src/firefox/features/firefox.ts +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - - -export class Firefox { - private _browserWSEndpoint: string; - - constructor(browserWSEndpoint: string) { - this._browserWSEndpoint = browserWSEndpoint; - } - - wsEndpoint(): string { - return this._browserWSEndpoint; - } -} diff --git a/src/processLauncher.ts b/src/processLauncher.ts index 336402a504..8e18c06a19 100644 --- a/src/processLauncher.ts +++ b/src/processLauncher.ts @@ -76,6 +76,7 @@ export async function launchProcess(options: LaunchProcessOptions, attemptToGrac const waitForProcessToClose = new Promise((fulfill, reject) => { spawnedProcess.once('exit', () => { processClosed = true; + helper.removeEventListeners(listeners); // Cleanup as processes exit. if (options.tempDir) { removeFolderAsync(options.tempDir) diff --git a/src/transport.ts b/src/transport.ts index f7334b0d10..7ab2cb83e1 100644 --- a/src/transport.ts +++ b/src/transport.ts @@ -27,6 +27,7 @@ export interface ConnectionTransport { export class WebSocketTransport implements ConnectionTransport { private _ws: WebSocket; + onmessage?: (message: string) => void; onclose?: () => void; @@ -36,12 +37,12 @@ export class WebSocketTransport implements ConnectionTransport { perMessageDeflate: false, maxPayload: 256 * 1024 * 1024, // 256Mb }); - ws.addEventListener('open', () => resolve(new WebSocketTransport(ws))); + ws.addEventListener('open', () => resolve(new WebSocketTransport(ws, url))); ws.addEventListener('error', reject); }); } - constructor(ws: WebSocket) { + constructor(ws: WebSocket, url: string) { this._ws = ws; this._ws.addEventListener('message', event => { if (this.onmessage) diff --git a/src/webkit/Browser.ts b/src/webkit/Browser.ts index 1a080af977..29797f3a58 100644 --- a/src/webkit/Browser.ts +++ b/src/webkit/Browser.ts @@ -15,9 +15,9 @@ * limitations under the License. */ -import * as childProcess from 'child_process'; import { EventEmitter } from 'events'; import { helper, RegisteredListener, debugError, assert } from '../helper'; +import * as browser from '../browser'; import * as network from '../network'; import { Connection, ConnectionEvents, TargetSession } from './Connection'; import { Page } from '../page'; @@ -27,8 +27,7 @@ import { Events } from '../events'; import { BrowserContext, BrowserContextOptions } from '../browserContext'; import { ConnectionTransport } from '../transport'; -export class Browser extends EventEmitter { - private readonly _process: childProcess.ChildProcess; +export class Browser extends EventEmitter implements browser.Browser { readonly _connection: Connection; private _defaultContext: BrowserContext; private _contexts = new Map(); @@ -36,12 +35,9 @@ export class Browser extends EventEmitter { private _eventListeners: RegisteredListener[]; private _privateEvents = new EventEmitter(); - constructor( - transport: ConnectionTransport, - process: childProcess.ChildProcess | null) { + constructor(transport: ConnectionTransport) { super(); this._connection = new Connection(transport); - this._process = process; /** @type {!Map} */ this._targets = new Map(); @@ -63,10 +59,6 @@ export class Browser extends EventEmitter { }); } - process(): childProcess.ChildProcess | null { - return this._process; - } - async newContext(options: BrowserContextOptions = {}): Promise { const { browserContextId } = await this._connection.send('Browser.createContext'); const context = this._createBrowserContext(browserContextId, options); @@ -84,15 +76,11 @@ export class Browser extends EventEmitter { return this._defaultContext; } - targets(): Target[] { - return Array.from(this._targets.values()); - } - async _waitForTarget(predicate: (arg0: Target) => boolean, options: { timeout?: number; } | undefined = {}): Promise { const { timeout = 30000 } = options; - const existingTarget = this.targets().find(predicate); + const existingTarget = Array.from(this._targets.values()).find(predicate); if (existingTarget) return existingTarget; let resolve : (a: Target) => void; @@ -185,7 +173,7 @@ export class Browser extends EventEmitter { _createBrowserContext(browserContextId: string | undefined, options: BrowserContextOptions): BrowserContext { const context = new BrowserContext({ pages: async (): Promise => { - const targets = this.targets().filter(target => target._browserContext === context && target._type === 'page'); + const targets = Array.from(this._targets.values()).filter(target => target._browserContext === context && target._type === 'page'); const pages = await Promise.all(targets.map(target => target.page())); return pages.filter(page => !!page); }, diff --git a/src/webkit/Launcher.ts b/src/webkit/Launcher.ts index 7430d65ddd..cd6b14bc64 100644 --- a/src/webkit/Launcher.ts +++ b/src/webkit/Launcher.ts @@ -24,6 +24,7 @@ import * as path from 'path'; import * as util from 'util'; import * as os from 'os'; import { launchProcess } from '../processLauncher'; +import { BrowserServer } from '../browser'; const DEFAULT_ARGS = [ ]; @@ -46,7 +47,7 @@ export class Launcher { return webkitArguments; } - async launch(options: LauncherLaunchOptions = {}): Promise { + async launch(options: LauncherLaunchOptions = {}): Promise> { const { ignoreDefaultArgs = false, args = [], @@ -96,9 +97,9 @@ export class Launcher { let browser: Browser | undefined; try { const transport = new PipeTransport(launchedProcess.stdio[3] as NodeJS.WritableStream, launchedProcess.stdio[4] as NodeJS.ReadableStream); - browser = new Browser(SlowMoTransport.wrap(transport, slowMo), launchedProcess); + browser = new Browser(SlowMoTransport.wrap(transport, slowMo)); await browser._waitForTarget(t => t._type === 'page'); - return browser; + return new BrowserServer(browser, launchedProcess, ''); } catch (e) { if (browser) await browser.close(); diff --git a/src/webkit/Playwright.ts b/src/webkit/Playwright.ts index 5057635cba..c2a035e0cb 100644 --- a/src/webkit/Playwright.ts +++ b/src/webkit/Playwright.ts @@ -14,11 +14,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Browser } from './Browser'; + +import * as browsers from '../browser'; import { BrowserFetcher, BrowserFetcherOptions, OnProgressCallback, BrowserFetcherRevisionInfo } from '../browserFetcher'; import { DeviceDescriptors } from '../deviceDescriptors'; import * as Errors from '../errors'; import { Launcher, LauncherLaunchOptions, createBrowserFetcher } from './Launcher'; +import { Browser } from './Browser'; export class Playwright { private _projectRoot: string; @@ -38,7 +40,12 @@ export class Playwright { return revisionInfo; } - launch(options: (LauncherLaunchOptions) | undefined): Promise { + async launch(options: (LauncherLaunchOptions) | undefined): Promise { + const server = await this._launcher.launch(options); + return server.connect(); + } + + async launchServer(options: (LauncherLaunchOptions) | undefined): Promise> { return this._launcher.launch(options); } diff --git a/test/browser.spec.js b/test/browser.spec.js index be77d79474..9ba4999850 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({browser}) { - const process = await browser.process(); + it('should return child_process instance', async function({browserServer}) { + const process = await browserServer.process(); expect(process.pid).toBeGreaterThan(0); }); }); diff --git a/test/chromium/browser.spec.js b/test/chromium/browser.spec.js index 7e30b76689..9a11f9e4e3 100644 --- a/test/chromium/browser.spec.js +++ b/test/chromium/browser.spec.js @@ -7,14 +7,8 @@ module.exports.describe = function({testRunner, expect, headless, playwright, FF const {beforeAll, beforeEach, afterAll, afterEach} = testRunner; describe('CrBrowser', function() { - it('should not return child_process for remote browser', async function({browser}) { - const browserWSEndpoint = browser.chromium.wsEndpoint(); - const remoteBrowser = await playwright.connect({browserWSEndpoint}); - expect(remoteBrowser.process()).toBe(null); - remoteBrowser.disconnect(); - }); it('should close all belonging targets once closing context', async function({browser, newContext}) { - const targets = async () => (await browser.chromium.targets()).filter(t => t.type() === 'page'); + const targets = async () => (await browser.targets()).filter(t => t.type() === 'page'); expect((await targets()).length).toBe(1); const context = await newContext(); diff --git a/test/chromium/chromium.spec.js b/test/chromium/chromium.spec.js index c5a552b44e..d573e141d0 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, CHROME const {beforeAll, beforeEach, afterAll, afterEach} = testRunner; describe('Chromium', function() { - it('should work across sessions', async function({browser, newContext}) { + it('should work across sessions', async function({browserServer, server, browser, newContext}) { expect(browser.browserContexts().length).toBe(2); await newContext(); expect(browser.browserContexts().length).toBe(3); const remoteBrowser = await playwright.connect({ - browserWSEndpoint: browser.chromium.wsEndpoint() + browserWSEndpoint: browserServer.wsEndpoint() }); const contexts = remoteBrowser.browserContexts(); expect(contexts.length).toBe(3); @@ -38,7 +38,7 @@ module.exports.describe = function({testRunner, expect, playwright, FFOX, CHROME describe('Target', function() { it('Chromium.targets should return all of the targets', async({page, server, browser}) => { // The pages will be the testing page and the original newtab page - const targets = browser.chromium.targets(); + const targets = browser.targets(); expect(targets.some(target => target.type() === 'page' && target.url() === 'about:blank')).toBeTruthy('Missing blank page'); expect(targets.some(target => target.type() === 'browser')).toBeTruthy('Missing browser target'); @@ -51,7 +51,7 @@ module.exports.describe = function({testRunner, expect, playwright, FFOX, CHROME expect(allPages[0]).not.toBe(allPages[1]); }); it('should contain browser target', async({browser}) => { - const targets = browser.chromium.targets(); + const targets = browser.targets(); const browserTarget = targets.find(target => target.type() === 'browser'); expect(browserTarget).toBeTruthy(); }); @@ -63,7 +63,7 @@ module.exports.describe = function({testRunner, expect, playwright, FFOX, CHROME }); it('should report when a new page is created and closed', async({browser, page, server, context}) => { const [otherPage] = await Promise.all([ - browser.chromium.waitForTarget(target => target.url() === server.CROSS_PROCESS_PREFIX + '/empty.html').then(target => target.page()), + browser.waitForTarget(target => target.url() === server.CROSS_PROCESS_PREFIX + '/empty.html').then(target => target.page()), page.evaluate(url => window.open(url), server.CROSS_PROCESS_PREFIX + '/empty.html'), ]); expect(otherPage.url()).toContain(server.CROSS_PROCESS_PREFIX); @@ -74,32 +74,32 @@ module.exports.describe = function({testRunner, expect, playwright, FFOX, CHROME expect(allPages).toContain(page); expect(allPages).toContain(otherPage); - const closePagePromise = new Promise(fulfill => browser.chromium.once('targetdestroyed', target => fulfill(target.page()))); + const closePagePromise = new Promise(fulfill => browser.once('targetdestroyed', target => fulfill(target.page()))); await otherPage.close(); expect(await closePagePromise).toBe(otherPage); - allPages = await Promise.all(browser.chromium.targets().map(target => target.page())); + allPages = await Promise.all(browser.targets().map(target => target.page())); expect(allPages).toContain(page); expect(allPages).not.toContain(otherPage); }); it('should report when a service worker is created and destroyed', async({browser, page, server, context}) => { await page.goto(server.EMPTY_PAGE); - const createdTarget = new Promise(fulfill => browser.chromium.once('targetcreated', target => fulfill(target))); + const createdTarget = new Promise(fulfill => browser.once('targetcreated', target => fulfill(target))); await page.goto(server.PREFIX + '/serviceworkers/empty/sw.html'); expect((await createdTarget).type()).toBe('service_worker'); expect((await createdTarget).url()).toBe(server.PREFIX + '/serviceworkers/empty/sw.js'); - const destroyedTarget = new Promise(fulfill => browser.chromium.once('targetdestroyed', target => fulfill(target))); + const destroyedTarget = new Promise(fulfill => browser.once('targetdestroyed', target => fulfill(target))); await page.evaluate(() => window.registrationPromise.then(registration => registration.unregister())); expect(await destroyedTarget).toBe(await createdTarget); }); it('should create a worker from a service worker', async({browser, page, server, context}) => { await page.goto(server.PREFIX + '/serviceworkers/empty/sw.html'); - const target = await browser.chromium.waitForTarget(target => target.type() === 'service_worker'); - const worker = await browser.chromium.serviceWorker(target); + const target = await browser.waitForTarget(target => target.type() === 'service_worker'); + const worker = await browser.serviceWorker(target); expect(await worker.evaluate(() => self.toString())).toBe('[object ServiceWorkerGlobalScope]'); }); it('should create a worker from a shared worker', async({browser, page, server, context}) => { @@ -107,38 +107,38 @@ module.exports.describe = function({testRunner, expect, playwright, FFOX, CHROME await page.evaluate(() => { new SharedWorker('data:text/javascript,console.log("hi")'); }); - const target = await browser.chromium.waitForTarget(target => target.type() === 'shared_worker'); - const worker = await browser.chromium.serviceWorker(target); + const target = await browser.waitForTarget(target => target.type() === 'shared_worker'); + const worker = await browser.serviceWorker(target); expect(await worker.evaluate(() => self.toString())).toBe('[object SharedWorkerGlobalScope]'); }); it('should report when a target url changes', async({browser, page, server, context}) => { await page.goto(server.EMPTY_PAGE); - let changedTarget = new Promise(fulfill => browser.chromium.once('targetchanged', target => fulfill(target))); + let changedTarget = new Promise(fulfill => browser.once('targetchanged', target => fulfill(target))); await page.goto(server.CROSS_PROCESS_PREFIX + '/'); expect((await changedTarget).url()).toBe(server.CROSS_PROCESS_PREFIX + '/'); - changedTarget = new Promise(fulfill => browser.chromium.once('targetchanged', target => fulfill(target))); + changedTarget = new Promise(fulfill => browser.once('targetchanged', target => fulfill(target))); await page.goto(server.EMPTY_PAGE); expect((await changedTarget).url()).toBe(server.EMPTY_PAGE); }); it('should not report uninitialized pages', async({browser, page, server, context}) => { let targetChanged = false; const listener = () => targetChanged = true; - browser.chromium.on('targetchanged', listener); - const targetPromise = new Promise(fulfill => browser.chromium.once('targetcreated', target => fulfill(target))); + browser.on('targetchanged', listener); + const targetPromise = new Promise(fulfill => browser.once('targetcreated', target => fulfill(target))); const newPagePromise = context.newPage(); const target = await targetPromise; expect(target.url()).toBe('about:blank'); const newPage = await newPagePromise; - const targetPromise2 = new Promise(fulfill => browser.chromium.once('targetcreated', target => fulfill(target))); + const targetPromise2 = new Promise(fulfill => browser.once('targetcreated', target => fulfill(target))); const evaluatePromise = newPage.evaluate(() => window.open('about:blank')); const target2 = await targetPromise2; expect(target2.url()).toBe('about:blank'); await evaluatePromise; await newPage.close(); expect(targetChanged).toBe(false, 'target should not be reported as changed'); - browser.chromium.removeListener('targetchanged', listener); + browser.removeListener('targetchanged', listener); }); it('should not crash while redirecting if original request was missed', async({browser, page, server, context}) => { let serverResponse = null; @@ -149,7 +149,7 @@ module.exports.describe = function({testRunner, expect, playwright, FFOX, CHROME server.waitForRequest('/one-style.css') ]); // Connect to the opened page. - const target = await browser.chromium.waitForTarget(target => target.url().includes('one-style.html')); + const target = await browser.waitForTarget(target => target.url().includes('one-style.html')); const newPage = await target.page(); // Issue a redirect. serverResponse.writeHead(302, { location: '/injectedstyle.css' }); @@ -162,12 +162,12 @@ module.exports.describe = function({testRunner, expect, playwright, FFOX, CHROME it('should have an opener', async({browser, page, server, context}) => { await page.goto(server.EMPTY_PAGE); const [createdTarget] = await Promise.all([ - new Promise(fulfill => browser.chromium.once('targetcreated', target => fulfill(target))), + new Promise(fulfill => browser.once('targetcreated', target => fulfill(target))), page.goto(server.PREFIX + '/popup/window-open.html') ]); expect((await createdTarget.page()).url()).toBe(server.PREFIX + '/popup/popup.html'); - expect(createdTarget.opener()).toBe(browser.chromium.pageTarget(page)); - expect(browser.chromium.pageTarget(page).opener()).toBe(null); + expect(createdTarget.opener()).toBe(browser.pageTarget(page)); + expect(browser.pageTarget(page).opener()).toBe(null); }); }); @@ -175,7 +175,7 @@ module.exports.describe = function({testRunner, expect, playwright, FFOX, CHROME it('should wait for a target', async function({browser, server, newContext}) { const context = await newContext(); let resolved = false; - const targetPromise = browser.chromium.waitForTarget(target => target.browserContext() === context && target.url() === server.EMPTY_PAGE); + const targetPromise = browser.waitForTarget(target => target.browserContext() === context && target.url() === server.EMPTY_PAGE); targetPromise.then(() => resolved = true); const page = await context.newPage(); expect(resolved).toBe(false); @@ -185,13 +185,13 @@ module.exports.describe = function({testRunner, expect, playwright, FFOX, CHROME }); it('should timeout waiting for a non-existent target', async function({browser, server, newContext}) { const context = await newContext(); - const error = await browser.chromium.waitForTarget(target => target.browserContext() === context && target.url() === server.EMPTY_PAGE, {timeout: 1}).catch(e => e); + const error = await browser.waitForTarget(target => target.browserContext() === context && target.url() === server.EMPTY_PAGE, {timeout: 1}).catch(e => e); expect(error).toBeInstanceOf(playwright.errors.TimeoutError); await context.close(); }); it('should wait for a target', async function({browser, server}) { let resolved = false; - const targetPromise = browser.chromium.waitForTarget(target => target.url() === server.EMPTY_PAGE); + const targetPromise = browser.waitForTarget(target => target.url() === server.EMPTY_PAGE); targetPromise.then(() => resolved = true); const page = await browser.defaultContext().newPage(); expect(resolved).toBe(false); @@ -202,7 +202,7 @@ module.exports.describe = function({testRunner, expect, playwright, FFOX, CHROME }); it('should timeout waiting for a non-existent target', async function({browser, server}) { let error = null; - await browser.chromium.waitForTarget(target => target.url() === server.EMPTY_PAGE, { + await browser.waitForTarget(target => target.url() === server.EMPTY_PAGE, { timeout: 1 }).catch(e => error = e); expect(error).toBeInstanceOf(playwright.errors.TimeoutError); @@ -210,9 +210,9 @@ module.exports.describe = function({testRunner, expect, playwright, FFOX, CHROME it('should fire target events', async function({browser, newContext, server}) { const context = await newContext(); const events = []; - browser.chromium.on('targetcreated', target => events.push('CREATED: ' + target.url())); - browser.chromium.on('targetchanged', target => events.push('CHANGED: ' + target.url())); - browser.chromium.on('targetdestroyed', target => events.push('DESTROYED: ' + target.url())); + browser.on('targetcreated', target => events.push('CREATED: ' + target.url())); + browser.on('targetchanged', target => events.push('CHANGED: ' + target.url())); + browser.on('targetdestroyed', target => events.push('DESTROYED: ' + target.url())); const page = await context.newPage(); await page.goto(server.EMPTY_PAGE); await page.close(); diff --git a/test/chromium/connect.spec.js b/test/chromium/connect.spec.js index 2681d4b26c..c170cd325f 100644 --- a/test/chromium/connect.spec.js +++ b/test/chromium/connect.spec.js @@ -14,10 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -const fs = require('fs'); -const os = require('os'); -const path = require('path'); -const {helper} = require('../../lib/helper'); + const utils = require('../utils'); module.exports.describe = function({testRunner, expect, defaultBrowserOptions, playwright, FFOX, CHROME, WEBKIT}) { @@ -27,39 +24,41 @@ 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 originalBrowser = await playwright.launch(defaultBrowserOptions); - const browser = await playwright.connect({ + const browserServer = await playwright.launchServer(defaultBrowserOptions); + const local = await browserServer.connect(); + const remote = await playwright.connect({ ...defaultBrowserOptions, - browserWSEndpoint: originalBrowser.chromium.wsEndpoint() + browserWSEndpoint: browserServer.wsEndpoint() }); - const page = await browser.defaultContext().newPage(); + const page = await remote.defaultContext().newPage(); expect(await page.evaluate(() => 7 * 8)).toBe(56); - browser.disconnect(); + remote.disconnect(); - const secondPage = await originalBrowser.defaultContext().newPage(); + const secondPage = await local.defaultContext().newPage(); expect(await secondPage.evaluate(() => 7 * 6)).toBe(42, 'original browser should still work'); - await originalBrowser.close(); + await browserServer.close(); }); it('should be able to close remote browser', async({server}) => { - const originalBrowser = await playwright.launch(defaultBrowserOptions); - const remoteBrowser = await playwright.connect({ + const browserServer = await playwright.launchServer(defaultBrowserOptions); + const local = await browserServer.connect(); + const remote = await playwright.connect({ ...defaultBrowserOptions, - browserWSEndpoint: originalBrowser.chromium.wsEndpoint() + browserWSEndpoint: browserServer.wsEndpoint() }); await Promise.all([ - utils.waitEvent(originalBrowser, 'disconnected'), - remoteBrowser.close(), + utils.waitEvent(local, 'disconnected'), + remote.close(), ]); }); - it('should be able to reconnect to a disconnected browser', async({server}) => { - const originalBrowser = await playwright.launch(defaultBrowserOptions); - const browserWSEndpoint = originalBrowser.chromium.wsEndpoint(); - const page = await originalBrowser.defaultContext().newPage(); + 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'); - originalBrowser.disconnect(); - const browser = await playwright.connect({...defaultBrowserOptions, browserWSEndpoint}); - const pages = await browser.defaultContext().pages(); + 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', @@ -69,57 +68,61 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p ' http://localhost:/frames/frame.html (uno)', ]); expect(await restoredPage.evaluate(() => 7 * 8)).toBe(56); - await browser.close(); + 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 browserOne = await playwright.launch(defaultBrowserOptions); - const browserTwo = await playwright.connect({ ...defaultBrowserOptions, browserWSEndpoint: browserOne.chromium.wsEndpoint() }); + const browserServer = await playwright.launchServer(defaultBrowserOptions); + const local = await browserServer.connect(); + const remote = await playwright.connect({ ...defaultBrowserOptions, browserWSEndpoint: browserServer.wsEndpoint() }); const [page1, page2] = await Promise.all([ - new Promise(x => browserOne.chromium.once('targetcreated', target => x(target.page()))), - browserTwo.defaultContext().newPage(), + new Promise(x => local.once('targetcreated', target => x(target.page()))), + remote.defaultContext().newPage(), ]); expect(await page1.evaluate(() => 7 * 8)).toBe(56); expect(await page2.evaluate(() => 7 * 6)).toBe(42); - await browserOne.close(); + await local.close(); }); }); describe('Browser.disconnect', function() { it('should reject navigation when browser closes', async({server}) => { server.setRoute('/one-style.css', () => {}); - const browser = await playwright.launch(defaultBrowserOptions); - const remote = await playwright.connect({...defaultBrowserOptions, browserWSEndpoint: browser.chromium.wsEndpoint()}); + 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 browser.close(); + await local.close(); }); it('should reject waitForSelector when browser closes', async({server}) => { server.setRoute('/empty.html', () => {}); - const browser = await playwright.launch(defaultBrowserOptions); - const remote = await playwright.connect({...defaultBrowserOptions, browserWSEndpoint: browser.chromium.wsEndpoint()}); + 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 browser.close(); + await local.close(); }); }); describe('Browser.close', function() { it('should terminate network waiters', async({context, server}) => { - const browser = await playwright.launch(defaultBrowserOptions); - const remote = await playwright.connect({...defaultBrowserOptions, browserWSEndpoint: browser.chromium.wsEndpoint()}); + 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), - browser.close() + local.close() ]); for (let i = 0; i < 2; i++) { const message = results[i].message; @@ -131,13 +134,14 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p describe('Browser.isConnected', () => { it('should set the browser connected state', async () => { - const browser = await playwright.launch(defaultBrowserOptions); - const browserWSEndpoint = browser.chromium.wsEndpoint(); + 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 browser.close(); + await browserServer.close(); }); }); diff --git a/test/chromium/headful.spec.js b/test/chromium/headful.spec.js index 22a2fbdd5b..f666ead812 100644 --- a/test/chromium/headful.spec.js +++ b/test/chromium/headful.spec.js @@ -48,14 +48,14 @@ module.exports.describe = function({testRunner, expect, playwright, defaultBrows it('background_page target type should be available', async() => { const browserWithExtension = await playwright.launch(extensionOptions); const page = await browserWithExtension.defaultContext().newPage(); - const backgroundPageTarget = await browserWithExtension.chromium.waitForTarget(target => target.type() === 'background_page'); + const backgroundPageTarget = await browserWithExtension.waitForTarget(target => target.type() === 'background_page'); await page.close(); await browserWithExtension.close(); expect(backgroundPageTarget).toBeTruthy(); }); it('target.page() should return a background_page', async({}) => { const browserWithExtension = await playwright.launch(extensionOptions); - const backgroundPageTarget = await browserWithExtension.chromium.waitForTarget(target => target.type() === 'background_page'); + const backgroundPageTarget = await browserWithExtension.waitForTarget(target => target.type() === 'background_page'); const page = await backgroundPageTarget.page(); expect(await page.evaluate(() => 2 * 3)).toBe(6); expect(await page.evaluate(() => window.MAGIC)).toBe(42); @@ -121,7 +121,7 @@ module.exports.describe = function({testRunner, expect, playwright, defaultBrows const context = await browser.newContext(); await Promise.all([ context.newPage(), - browser.chromium.waitForTarget(target => target.browserContext() === context && target.url().includes('devtools://')), + browser.waitForTarget(target => target.browserContext() === context && target.url().includes('devtools://')), ]); await browser.close(); }); diff --git a/test/chromium/launcher.spec.js b/test/chromium/launcher.spec.js index c1a429085a..6cdce27b77 100644 --- a/test/chromium/launcher.spec.js +++ b/test/chromium/launcher.spec.js @@ -68,15 +68,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 browser = await playwright.launch(Object.assign({}, defaultBrowserOptions, { + const browserServer = await playwright.launchServer(Object.assign({}, defaultBrowserOptions, { // Ignore first and third default argument. ignoreDefaultArgs: [ defaultArgs[0], defaultArgs[2] ], })); - const spawnargs = browser.process().spawnargs; + const spawnargs = browserServer.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 browser.close(); + await browserServer.close(); }); }); describe('Playwright.launch |browserURL| option', function() { @@ -98,16 +98,16 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p originalBrowser.close(); }); it('should throw when using both browserWSEndpoint and browserURL', async({server}) => { - const originalBrowser = await playwright.launch(Object.assign({}, defaultBrowserOptions, { + const browserServer = await playwright.launchServer(Object.assign({}, defaultBrowserOptions, { args: ['--remote-debugging-port=21222'] })); const browserURL = 'http://127.0.0.1:21222'; let error = null; - await playwright.connect({browserURL, browserWSEndpoint: originalBrowser.chromium.wsEndpoint()}).catch(e => error = e); + await playwright.connect({browserURL, browserWSEndpoint: browserServer.wsEndpoint()}).catch(e => error = e); expect(error.message).toContain('Exactly one of browserWSEndpoint, browserURL or transport'); - originalBrowser.close(); + browserServer.close(); }); it('should throw when trying to connect to non-existing browser', async({server}) => { const originalBrowser = await playwright.launch(Object.assign({}, defaultBrowserOptions, { @@ -160,30 +160,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 browser = await playwright.launch(options); + const browserServer = await playwright.launchServer(options); + const browser = await browserServer.connect(); expect((await browser.defaultContext().pages()).length).toBe(1); - expect(browser.chromium.wsEndpoint()).toBe(''); + expect(browserServer.wsEndpoint()).toBe(''); const page = await browser.defaultContext().newPage(); expect(await page.evaluate('11 * 11')).toBe(121); await page.close(); - await browser.close(); + await browserServer.close(); }); it('should support the pipe argument', async() => { const options = Object.assign({}, defaultBrowserOptions); options.args = ['--remote-debugging-pipe'].concat(options.args || []); - const browser = await playwright.launch(options); - expect(browser.chromium.wsEndpoint()).toBe(''); + const browserServer = await playwright.launchServer(options); + const browser = await browserServer.connect(); + expect(browserServer.wsEndpoint()).toBe(''); const page = await browser.defaultContext().newPage(); expect(await page.evaluate('11 * 11')).toBe(121); await page.close(); - await browser.close(); + await browserServer.close(); }); it('should fire "disconnected" when closing with pipe', async() => { const options = Object.assign({pipe: true}, defaultBrowserOptions); - const browser = await playwright.launch(options); + const browserServer = await playwright.launchServer(options); + const browser = await browserServer.connect(); const disconnectedEventPromise = new Promise(resolve => browser.once('disconnected', resolve)); // Emulate user exiting browser. - browser.process().kill(); + browserServer.process().kill(); await disconnectedEventPromise; }); }); @@ -193,9 +196,9 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p it('should work', async({server}) => { const browser = await playwright.launch(defaultBrowserOptions); const events = []; - browser.chromium.on('targetcreated', () => events.push('CREATED')); - browser.chromium.on('targetchanged', () => events.push('CHANGED')); - browser.chromium.on('targetdestroyed', () => events.push('DESTROYED')); + browser.on('targetcreated', () => events.push('CREATED')); + browser.on('targetchanged', () => events.push('CHANGED')); + browser.on('targetdestroyed', () => events.push('DESTROYED')); const page = await browser.defaultContext().newPage(); await page.goto(server.EMPTY_PAGE); await page.close(); @@ -206,8 +209,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 originalBrowser = await playwright.launch(defaultBrowserOptions); - const browserWSEndpoint = originalBrowser.chromium.wsEndpoint(); + const browserServer = await playwright.launchServer(defaultBrowserOptions); + const originalBrowser = await browserServer.connect(); + const browserWSEndpoint = browserServer.wsEndpoint(); const remoteBrowser1 = await playwright.connect({browserWSEndpoint}); const remoteBrowser2 = await playwright.connect({browserWSEndpoint}); @@ -230,7 +234,7 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p await Promise.all([ waitEvent(remoteBrowser1, 'disconnected'), waitEvent(originalBrowser, 'disconnected'), - originalBrowser.close(), + browserServer.close(), ]); expect(disconnectedOriginal).toBe(1); diff --git a/test/chromium/oopif.spec.js b/test/chromium/oopif.spec.js index 3412695300..ef39ff84c2 100644 --- a/test/chromium/oopif.spec.js +++ b/test/chromium/oopif.spec.js @@ -54,5 +54,5 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p function oopifs(browser) { - return browser.chromium.targets().filter(target => target._targetInfo.type === 'iframe'); + return browser.targets().filter(target => target._targetInfo.type === 'iframe'); } diff --git a/test/chromium/session.spec.js b/test/chromium/session.spec.js index 5451a27b84..6946504c6d 100644 --- a/test/chromium/session.spec.js +++ b/test/chromium/session.spec.js @@ -23,7 +23,7 @@ module.exports.describe = function({testRunner, expect, FFOX, CHROME, WEBKIT}) { describe('Chromium.createCDPSession', function() { it('should work', async function({page, browser, server}) { - const client = await browser.chromium.pageTarget(page).createCDPSession(); + const client = await browser.pageTarget(page).createCDPSession(); await Promise.all([ client.send('Runtime.enable'), @@ -33,7 +33,7 @@ module.exports.describe = function({testRunner, expect, FFOX, CHROME, WEBKIT}) { expect(foo).toBe('bar'); }); it('should send events', async function({page, browser, server}) { - const client = await browser.chromium.pageTarget(page).createCDPSession(); + const client = await browser.pageTarget(page).createCDPSession(); await client.send('Network.enable'); const events = []; client.on('Network.requestWillBeSent', event => events.push(event)); @@ -41,7 +41,7 @@ module.exports.describe = function({testRunner, expect, FFOX, CHROME, WEBKIT}) { expect(events.length).toBe(1); }); it('should enable and disable domains independently', async function({page, browser, server}) { - const client = await browser.chromium.pageTarget(page).createCDPSession(); + const client = await browser.pageTarget(page).createCDPSession(); await client.send('Runtime.enable'); await client.send('Debugger.enable'); // JS coverage enables and then disables Debugger domain. @@ -56,7 +56,7 @@ module.exports.describe = function({testRunner, expect, FFOX, CHROME, WEBKIT}) { expect(event.url).toBe('foo.js'); }); it('should be able to detach session', async function({page, browser, server}) { - const client = await browser.chromium.pageTarget(page).createCDPSession(); + const client = await browser.pageTarget(page).createCDPSession(); await client.send('Runtime.enable'); const evalResponse = await client.send('Runtime.evaluate', {expression: '1 + 2', returnByValue: true}); expect(evalResponse.result.value).toBe(3); @@ -70,7 +70,7 @@ module.exports.describe = function({testRunner, expect, FFOX, CHROME, WEBKIT}) { expect(error.message).toContain('Session closed.'); }); it('should throw nice errors', async function({page, browser}) { - const client = await browser.chromium.pageTarget(page).createCDPSession(); + const client = await browser.pageTarget(page).createCDPSession(); const error = await theSourceOfTheProblems().catch(error => error); expect(error.stack).toContain('theSourceOfTheProblems'); expect(error.message).toContain('ThisCommand.DoesNotExist'); diff --git a/test/chromium/tracing.spec.js b/test/chromium/tracing.spec.js index 3af62de4aa..3071593722 100644 --- a/test/chromium/tracing.spec.js +++ b/test/chromium/tracing.spec.js @@ -38,55 +38,55 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p } }); it('should output a trace', async({browser, page, server, outputFile}) => { - await browser.chromium.startTracing(page, {screenshots: true, path: outputFile}); + await browser.startTracing(page, {screenshots: true, path: outputFile}); await page.goto(server.PREFIX + '/grid.html'); - await browser.chromium.stopTracing(); + await browser.stopTracing(); expect(fs.existsSync(outputFile)).toBe(true); }); it('should run with custom categories if provided', async({browser, page, outputFile}) => { - await browser.chromium.startTracing(page, {path: outputFile, categories: ['disabled-by-default-v8.cpu_profiler.hires']}); - await browser.chromium.stopTracing(); + await browser.startTracing(page, {path: outputFile, categories: ['disabled-by-default-v8.cpu_profiler.hires']}); + await browser.stopTracing(); const traceJson = JSON.parse(fs.readFileSync(outputFile).toString()); expect(traceJson.metadata['trace-config']).toContain('disabled-by-default-v8.cpu_profiler.hires'); }); it('should throw if tracing on two pages', async({browser, page, server, outputFile}) => { - await browser.chromium.startTracing(page, {path: outputFile}); + await browser.startTracing(page, {path: outputFile}); const newPage = await browser.defaultContext().newPage(); let error = null; - await browser.chromium.startTracing(newPage, {path: outputFile}).catch(e => error = e); + await browser.startTracing(newPage, {path: outputFile}).catch(e => error = e); await newPage.close(); expect(error).toBeTruthy(); - await browser.chromium.stopTracing(); + await browser.stopTracing(); }); it('should return a buffer', async({browser, page, server, outputFile}) => { - await browser.chromium.startTracing(page, {screenshots: true, path: outputFile}); + await browser.startTracing(page, {screenshots: true, path: outputFile}); await page.goto(server.PREFIX + '/grid.html'); - const trace = await browser.chromium.stopTracing(); + const trace = await browser.stopTracing(); const buf = fs.readFileSync(outputFile); expect(trace.toString()).toEqual(buf.toString()); }); it('should work without options', async({browser, page, server, outputFile}) => { - await browser.chromium.startTracing(page); + await browser.startTracing(page); await page.goto(server.PREFIX + '/grid.html'); - const trace = await browser.chromium.stopTracing(); + const trace = await browser.stopTracing(); expect(trace).toBeTruthy(); }); it('should return null in case of Buffer error', async({browser, page, server}) => { - await browser.chromium.startTracing(page, {screenshots: true}); + await browser.startTracing(page, {screenshots: true}); await page.goto(server.PREFIX + '/grid.html'); const oldBufferConcat = Buffer.concat; Buffer.concat = bufs => { throw 'error'; }; - const trace = await browser.chromium.stopTracing(); + const trace = await browser.stopTracing(); expect(trace).toEqual(null); Buffer.concat = oldBufferConcat; }); it('should support a buffer without a path', async({browser, page, server}) => { - await browser.chromium.startTracing(page, {screenshots: true}); + await browser.startTracing(page, {screenshots: true}); await page.goto(server.PREFIX + '/grid.html'); - const trace = await browser.chromium.stopTracing(); + const trace = await browser.stopTracing(); expect(trace.toString()).toContain('screenshot'); }); }); diff --git a/test/firefox/browser.spec.js b/test/firefox/browser.spec.js deleted file mode 100644 index 5a77febb76..0000000000 --- a/test/firefox/browser.spec.js +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -module.exports.describe = function({testRunner, expect, headless, playwright, FFOX, CHROME, WEBKIT}) { - const {describe, xdescribe, fdescribe} = testRunner; - const {it, fit, xit} = testRunner; - const {beforeAll, beforeEach, afterAll, afterEach} = testRunner; - - describe('Browser.process', function() { - it('should not return child_process for remote browser', async function({browser}) { - const browserWSEndpoint = browser.firefox.wsEndpoint(); - const remoteBrowser = await playwright.connect({browserWSEndpoint}); - expect(remoteBrowser.process()).toBe(null); - remoteBrowser.disconnect(); - }); - }); -}; diff --git a/test/firefox/launcher.spec.js b/test/firefox/launcher.spec.js index 5e5b3e8ea2..be3358332e 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 browser = await playwright.launch(Object.assign({}, defaultBrowserOptions, { + const browserServer = await playwright.launchServer(Object.assign({}, defaultBrowserOptions, { // Ignore first and third default argument. ignoreDefaultArgs: [ defaultArgs[0], defaultArgs[2] ], })); - const spawnargs = browser.process().spawnargs; + const spawnargs = browserServer.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 browser.close(); + await browserServer.close(); }); }); }); diff --git a/test/fixtures/closeme.js b/test/fixtures/closeme.js index 3e398ef388..90da469a80 100644 --- a/test/fixtures/closeme.js +++ b/test/fixtures/closeme.js @@ -1,8 +1,5 @@ (async() => { const [, , playwrightRoot, options] = process.argv; - const browser = await require(playwrightRoot).launch(JSON.parse(options)); - if (browser.chromium) - console.log(browser.chromium.wsEndpoint()); - else if (browser.firefox) - console.log(browser.firefox.wsEndpoint()); + const browserServer = await require(playwrightRoot).launchServer(JSON.parse(options)); + console.log(browserServer.wsEndpoint()); })(); diff --git a/test/playwright.spec.js b/test/playwright.spec.js index 1ae7e56229..6471a739ec 100644 --- a/test/playwright.spec.js +++ b/test/playwright.spec.js @@ -90,12 +90,14 @@ module.exports.describe = ({testRunner, product, playwrightPath}) => { describe('Browser', function() { beforeAll(async state => { - state.browser = await playwright.launch(defaultBrowserOptions); + state.browserServer = await playwright.launchServer(defaultBrowserOptions); + state.browser = await state.browserServer.connect(); }); afterAll(async state => { - await state.browser.close(); + await state.browserServer.close(); state.browser = null; + state.browserServer = null; }); beforeEach(async(state, test) => { @@ -104,7 +106,7 @@ module.exports.describe = ({testRunner, product, playwrightPath}) => { let rl; if (!WEBKIT) { - rl = require('readline').createInterface({ input: state.browser.process().stderr }); + rl = require('readline').createInterface({ input: state.browserServer.process().stderr }); test.output = ''; rl.on('line', onLine); } @@ -188,9 +190,6 @@ module.exports.describe = ({testRunner, product, playwrightPath}) => { if (CHROME) { testRunner.loadTests(require('./chromium/browser.spec.js'), testOptions); } - if (FFOX) { - testRunner.loadTests(require('./firefox/browser.spec.js'), testOptions); - } }); // Top-level tests that launch Browser themselves. diff --git a/utils/browser/test.js b/utils/browser/test.js index 5b2363b82b..7bea4f116d 100644 --- a/utils/browser/test.js +++ b/utils/browser/test.js @@ -50,24 +50,25 @@ afterEach(async state => { describe('Playwright-Web', () => { it('should work over web socket', async({page, serverConfig}) => { - const browser2 = await playwright.launch(); + const browserServer = await playwright.launchServer(); // Use in-page playwright to create a new page and navigate it to the EMPTY_PAGE await page.evaluate(async(browserWSEndpoint, serverConfig) => { const playwright = require('playwright'); const browser = await playwright.connect({browserWSEndpoint}); const page = await browser.defaultContext().newPage(); await page.goto(serverConfig.EMPTY_PAGE); - }, browser2.wsEndpoint(), serverConfig); - const pageURLs = (await browser2.pages()).map(page => page.url()).sort(); + }, browserServer.wsEndpoint(), serverConfig); + const browser = await browserServer.connect(); + const pageURLs = (await browser.defaultContext().pages()).map(page => page.url()).sort(); expect(pageURLs).toEqual([ 'about:blank', serverConfig.EMPTY_PAGE ]); - await browser2.close(); + await browserServer.close(); }); it('should work over exposed DevTools protocol', async({browser, page, serverConfig}) => { // Expose devtools protocol binding into page. - const session = await browser.chromium.browserTarget().createCDPSession(); + const session = await browser.browserTarget().createCDPSession(); const pageInfo = (await session.send('Target.getTargets')).targetInfos.find(info => info.attached); await session.send('Target.exposeDevToolsProtocol', {targetId: pageInfo.targetId}); await session.detach(); diff --git a/utils/protocol-types-generator/index.js b/utils/protocol-types-generator/index.js index b0ff592db9..b7d69e7542 100644 --- a/utils/protocol-types-generator/index.js +++ b/utils/protocol-types-generator/index.js @@ -10,12 +10,12 @@ async function generateChromeProtocol(revision) { if (revision.local && fs.existsSync(outputPath)) return; const playwright = await require('../../chromium'); - const browser = await playwright.launch({executablePath: revision.executablePath}); - const origin = browser.chromium.wsEndpoint().match(/ws:\/\/([0-9A-Za-z:\.]*)\//)[1]; - const page = await browser.defaultContext().newPage(); + const browserServer = await playwright.launchServer({executablePath: revision.executablePath}); + const origin = browserServer.wsEndpoint().match(/ws:\/\/([0-9A-Za-z:\.]*)\//)[1]; + const page = (await browserServer.connect()).defaultContext().newPage(); await page.goto(`http://${origin}/json/protocol`); const json = JSON.parse(await page.evaluate(() => document.documentElement.innerText)); - await browser.close(); + await browserServer.close(); fs.writeFileSync(outputPath, jsonToTS(json)); console.log(`Wrote protocol.ts to ${path.relative(process.cwd(), outputPath)}`); }