From 975519150e79caad193bef076b336d6b4fde870a Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Fri, 29 Jan 2021 16:00:56 -0800 Subject: [PATCH] chore: centralize playwright creation, bind context listeners to instance (#5217) --- src/browserServerImpl.ts | 10 ++++---- src/cli/driver.ts | 11 ++------ src/dispatchers/browserContextDispatcher.ts | 8 +++--- src/dispatchers/browserDispatcher.ts | 8 +++--- src/inprocess.ts | 12 ++------- src/remote/playwrightServer.ts | 12 ++------- src/server/android/android.ts | 7 ++++-- src/server/browser.ts | 17 ++++++++----- src/server/browserContext.ts | 12 ++++----- src/server/browserType.ts | 7 ++++-- src/server/chromium/chromium.ts | 6 ++--- src/server/chromium/crBrowser.ts | 8 +++--- src/server/chromium/crPage.ts | 2 +- src/server/electron/electron.ts | 9 ++++++- src/server/firefox/ffBrowser.ts | 6 ++--- src/server/page.ts | 2 +- src/server/playwright.ts | 25 +++++++++++++++---- src/{trace => server/supplements/har}/har.ts | 0 .../supplements/har}/harTracer.ts | 18 ++++++------- src/server/supplements/inspectorController.ts | 8 ++---- src/server/supplements/recorderSupplement.ts | 2 +- src/server/types.ts | 6 +---- src/server/webkit/wkBrowser.ts | 6 ++--- src/trace/tracer.ts | 10 +++----- test/har.spec.ts | 2 +- utils/check_deps.js | 2 +- 26 files changed, 104 insertions(+), 112 deletions(-) rename src/{trace => server/supplements/har}/har.ts (100%) rename src/{trace => server/supplements/har}/harTracer.ts (95%) diff --git a/src/browserServerImpl.ts b/src/browserServerImpl.ts index e690072257aee..2eb0f8f3d7115 100644 --- a/src/browserServerImpl.ts +++ b/src/browserServerImpl.ts @@ -70,7 +70,7 @@ export class BrowserServerImpl extends EventEmitter implements BrowserServer { this._browser = browser; this._wsEndpoint = ''; - this._process = browser._options.browserProcess.process!; + this._process = browser.options.browserProcess.process!; let readyCallback = () => {}; this._ready = new Promise(f => readyCallback = f); @@ -86,7 +86,7 @@ export class BrowserServerImpl extends EventEmitter implements BrowserServer { this._clientAttached(socket); }); - browser._options.browserProcess.onclose = (exitCode, signal) => { + browser.options.browserProcess.onclose = (exitCode, signal) => { this._server.close(); this.emit('close', exitCode, signal); }; @@ -101,11 +101,11 @@ export class BrowserServerImpl extends EventEmitter implements BrowserServer { } async close(): Promise { - await this._browser._options.browserProcess.close(); + await this._browser.options.browserProcess.close(); } async kill(): Promise { - await this._browser._options.browserProcess.kill(); + await this._browser.options.browserProcess.kill(); } private _clientAttached(socket: ws) { @@ -158,7 +158,7 @@ class ConnectedBrowser extends BrowserDispatcher { async newContext(params: channels.BrowserNewContextParams): Promise<{ context: channels.BrowserContextChannel }> { if (params.recordVideo) { // TODO: we should create a separate temp directory or accept a launchServer parameter. - params.recordVideo.dir = this._object._options.downloadsPath!; + params.recordVideo.dir = this._object.options.downloadsPath!; } const result = await super.newContext(params); const dispatcher = result.context as BrowserContextDispatcher; diff --git a/src/cli/driver.ts b/src/cli/driver.ts index 9d393506732d0..7209898541bda 100644 --- a/src/cli/driver.ts +++ b/src/cli/driver.ts @@ -18,15 +18,12 @@ import * as fs from 'fs'; import * as path from 'path'; -import { installInspectorController } from '../server/supplements/inspectorController'; import { DispatcherConnection } from '../dispatchers/dispatcher'; import { PlaywrightDispatcher } from '../dispatchers/playwrightDispatcher'; import { installBrowsersWithProgressBar } from '../install/installer'; import { Transport } from '../protocol/transport'; -import { Playwright } from '../server/playwright'; +import { createPlaywright } from '../server/playwright'; import { gracefullyCloseAll } from '../server/processLauncher'; -import { installHarTracer } from '../trace/harTracer'; -import { installTracer } from '../trace/tracer'; import { BrowserName } from '../utils/browserPaths'; export function printApiJson() { @@ -38,10 +35,6 @@ export function printProtocol() { } export function runServer() { - installInspectorController(); - installTracer(); - installHarTracer(); - const dispatcherConnection = new DispatcherConnection(); const transport = new Transport(process.stdout, process.stdin); transport.onmessage = message => dispatcherConnection.dispatch(JSON.parse(message)); @@ -56,7 +49,7 @@ export function runServer() { process.exit(0); }; - const playwright = new Playwright(__dirname, require('../../browsers.json')['browsers']); + const playwright = createPlaywright(); new PlaywrightDispatcher(dispatcherConnection.rootDispatcher(), playwright); } diff --git a/src/dispatchers/browserContextDispatcher.ts b/src/dispatchers/browserContextDispatcher.ts index 1c00c0b8c7a13..15c2ddd8754f9 100644 --- a/src/dispatchers/browserContextDispatcher.ts +++ b/src/dispatchers/browserContextDispatcher.ts @@ -28,7 +28,7 @@ export class BrowserContextDispatcher extends Dispatcher this._dispatchEvent('stdout', { data: Buffer.from(data, 'utf8').toString('base64') })); context.on(BrowserContext.Events.StdErr, data => this._dispatchEvent('stderr', { data: Buffer.from(data, 'utf8').toString('base64') })); - if (context._browser._options.name === 'chromium') { + if (context._browser.options.name === 'chromium') { for (const page of (context as CRBrowserContext).backgroundPages()) this._dispatchEvent('crBackgroundPage', { page: new PageDispatcher(this._scope, page) }); context.on(CRBrowserContext.CREvents.BackgroundPage, page => this._dispatchEvent('crBackgroundPage', { page: new PageDispatcher(this._scope, page) })); @@ -139,7 +139,7 @@ export class BrowserContextDispatcher extends Dispatcher { - if (!this._object._browser._options.isChromium) + if (!this._object._browser.options.isChromium) throw new Error(`CDP session is only available in Chromium`); const crBrowserContext = this._object as CRBrowserContext; return { session: new CDPSessionDispatcher(this._scope, await crBrowserContext.newCDPSession((params.page as PageDispatcher)._object)) }; diff --git a/src/dispatchers/browserDispatcher.ts b/src/dispatchers/browserDispatcher.ts index 8fca3f155701c..66dd42735241e 100644 --- a/src/dispatchers/browserDispatcher.ts +++ b/src/dispatchers/browserDispatcher.ts @@ -24,7 +24,7 @@ import { PageDispatcher } from './pageDispatcher'; export class BrowserDispatcher extends Dispatcher implements channels.BrowserChannel { constructor(scope: DispatcherScope, browser: Browser) { - super(scope, browser, 'Browser', { version: browser.version(), name: browser._options.name }, true); + super(scope, browser, 'Browser', { version: browser.version(), name: browser.options.name }, true); browser.on(Browser.Events.Disconnected, () => this._didClose()); } @@ -45,21 +45,21 @@ export class BrowserDispatcher extends Dispatcher { - if (!this._object._options.isChromium) + if (!this._object.options.isChromium) throw new Error(`CDP session is only available in Chromium`); const crBrowser = this._object as CRBrowser; return { session: new CDPSessionDispatcher(this._scope, await crBrowser.newBrowserCDPSession()) }; } async crStartTracing(params: channels.BrowserCrStartTracingParams): Promise { - if (!this._object._options.isChromium) + if (!this._object.options.isChromium) throw new Error(`Tracing is only available in Chromium`); const crBrowser = this._object as CRBrowser; await crBrowser.startTracing(params.page ? (params.page as PageDispatcher)._object : undefined, params); } async crStopTracing(): Promise { - if (!this._object._options.isChromium) + if (!this._object.options.isChromium) throw new Error(`Tracing is only available in Chromium`); const crBrowser = this._object as CRBrowser; const buffer = await crBrowser.stopTracing(); diff --git a/src/inprocess.ts b/src/inprocess.ts index 9f3d26b1481c2..df7926ddf44c9 100644 --- a/src/inprocess.ts +++ b/src/inprocess.ts @@ -15,22 +15,14 @@ */ import { DispatcherConnection } from './dispatchers/dispatcher'; -import { Playwright as PlaywrightImpl } from './server/playwright'; +import { createPlaywright } from './server/playwright'; import type { Playwright as PlaywrightAPI } from './client/playwright'; import { PlaywrightDispatcher } from './dispatchers/playwrightDispatcher'; import { Connection } from './client/connection'; import { BrowserServerLauncherImpl } from './browserServerImpl'; -import { installInspectorController } from './server/supplements/inspectorController'; -import { installTracer } from './trace/tracer'; -import { installHarTracer } from './trace/harTracer'; -import * as path from 'path'; function setupInProcess(): PlaywrightAPI { - const playwright = new PlaywrightImpl(path.join(__dirname, '..'), require('../browsers.json')['browsers']); - - installInspectorController(); - installTracer(); - installHarTracer(); + const playwright = createPlaywright(); const clientConnection = new Connection(); const dispatcherConnection = new DispatcherConnection(); diff --git a/src/remote/playwrightServer.ts b/src/remote/playwrightServer.ts index c1e7ef6b2db21..fc0a3d93b61e0 100644 --- a/src/remote/playwrightServer.ts +++ b/src/remote/playwrightServer.ts @@ -17,20 +17,13 @@ import * as debug from 'debug'; import * as http from 'http'; import * as WebSocket from 'ws'; -import { installInspectorController } from '../server/supplements/inspectorController'; import { DispatcherConnection } from '../dispatchers/dispatcher'; import { PlaywrightDispatcher } from '../dispatchers/playwrightDispatcher'; -import { Playwright } from '../server/playwright'; +import { createPlaywright } from '../server/playwright'; import { gracefullyCloseAll } from '../server/processLauncher'; -import { installTracer } from '../trace/tracer'; -import { installHarTracer } from '../trace/harTracer'; const debugLog = debug('pw:server'); -installInspectorController(); -installTracer(); -installHarTracer(); - export class PlaywrightServer { private _server: http.Server | undefined; private _client: WebSocket | undefined; @@ -62,8 +55,7 @@ export class PlaywrightServer { this._onDisconnect(); }); dispatcherConnection.onmessage = message => ws.send(JSON.stringify(message)); - const playwright = new Playwright(__dirname, require('../../browsers.json')['browsers']); - new PlaywrightDispatcher(dispatcherConnection.rootDispatcher(), playwright); + new PlaywrightDispatcher(dispatcherConnection.rootDispatcher(), createPlaywright()); }); } diff --git a/src/server/android/android.ts b/src/server/android/android.ts index b3eae364a12e9..cdc8754321ad3 100644 --- a/src/server/android/android.ts +++ b/src/server/android/android.ts @@ -22,7 +22,7 @@ import * as stream from 'stream'; import * as util from 'util'; import * as ws from 'ws'; import { createGuid, makeWaitForNextTask } from '../../utils/utils'; -import { BrowserOptions, BrowserProcess } from '../browser'; +import { BrowserOptions, BrowserProcess, PlaywrightOptions } from '../browser'; import { BrowserContext, validateBrowserContextOptions } from '../browserContext'; import { ProgressController } from '../progress'; import { CRBrowser } from '../chromium/crBrowser'; @@ -57,9 +57,11 @@ export class Android { private _backend: Backend; private _devices = new Map(); readonly _timeoutSettings: TimeoutSettings; + readonly _playwrightOptions: PlaywrightOptions; - constructor(backend: Backend) { + constructor(backend: Backend, playwrightOptions: PlaywrightOptions) { this._backend = backend; + this._playwrightOptions = playwrightOptions; this._timeoutSettings = new TimeoutSettings(); } @@ -255,6 +257,7 @@ export class AndroidDevice extends EventEmitter { this._browserConnections.add(androidBrowser); const browserOptions: BrowserOptions = { + ...this._android._playwrightOptions, name: 'clank', isChromium: true, slowMo: 0, diff --git a/src/server/browser.ts b/src/server/browser.ts index c37501dca7c44..285634fd320ac 100644 --- a/src/server/browser.ts +++ b/src/server/browser.ts @@ -15,7 +15,7 @@ */ import * as types from './types'; -import { BrowserContext, Video } from './browserContext'; +import { BrowserContext, ContextListener, Video } from './browserContext'; import { Page } from './page'; import { EventEmitter } from 'events'; import { Download } from './download'; @@ -30,7 +30,11 @@ export interface BrowserProcess { close(): Promise; } -export type BrowserOptions = types.UIOptions & { +export type PlaywrightOptions = { + contextListeners: ContextListener[] +}; + +export type BrowserOptions = PlaywrightOptions & { name: string, isChromium: boolean, downloadsPath?: string, @@ -40,6 +44,7 @@ export type BrowserOptions = types.UIOptions & { proxy?: ProxySettings, protocolLogger: types.ProtocolLogger, browserLogsCollector: RecentLogsCollector, + slowMo?: number, }; export abstract class Browser extends EventEmitter { @@ -47,7 +52,7 @@ export abstract class Browser extends EventEmitter { Disconnected: 'disconnected', }; - readonly _options: BrowserOptions; + readonly options: BrowserOptions; private _downloads = new Map(); _defaultContext: BrowserContext | null = null; private _startedClosing = false; @@ -55,7 +60,7 @@ export abstract class Browser extends EventEmitter { constructor(options: BrowserOptions) { super(); - this._options = options; + this.options = options; } abstract newContext(options?: types.BrowserContextOptions): Promise; @@ -71,7 +76,7 @@ export abstract class Browser extends EventEmitter { } _downloadCreated(page: Page, uuid: string, url: string, suggestedFilename?: string) { - const download = new Download(page, this._options.downloadsPath || '', uuid, url, suggestedFilename); + const download = new Download(page, this.options.downloadsPath || '', uuid, url, suggestedFilename); this._downloads.set(uuid, download); } @@ -117,7 +122,7 @@ export abstract class Browser extends EventEmitter { async close() { if (!this._startedClosing) { this._startedClosing = true; - await this._options.browserProcess.close(); + await this.options.browserProcess.close(); } if (this.isConnected()) await new Promise(x => this.once(Browser.Events.Disconnected, x)); diff --git a/src/server/browserContext.ts b/src/server/browserContext.ts index 3d410eebc73f8..179d0580ab7c8 100644 --- a/src/server/browserContext.ts +++ b/src/server/browserContext.ts @@ -94,8 +94,6 @@ export interface ContextListener { onContextDidDestroy(context: BrowserContext): Promise; } -export const contextListeners = new Set(); - export abstract class BrowserContext extends EventEmitter { static Events = { Close: 'close', @@ -140,7 +138,7 @@ export abstract class BrowserContext extends EventEmitter { } async _initialize() { - for (const listener of contextListeners) + for (const listener of this._browser.options.contextListeners) await listener.onContextCreated(this); } @@ -259,7 +257,7 @@ export abstract class BrowserContext extends EventEmitter { } protected _authenticateProxyViaHeader() { - const proxy = this._options.proxy || this._browser._options.proxy || { username: undefined, password: undefined }; + const proxy = this._options.proxy || this._browser.options.proxy || { username: undefined, password: undefined }; const { username, password } = proxy; if (username) { this._options.httpCredentials = { username, password: password! }; @@ -272,7 +270,7 @@ export abstract class BrowserContext extends EventEmitter { } protected _authenticateProxyViaCredentials() { - const proxy = this._options.proxy || this._browser._options.proxy; + const proxy = this._options.proxy || this._browser.options.proxy; if (!proxy) return; const { username, password } = proxy; @@ -294,7 +292,7 @@ export abstract class BrowserContext extends EventEmitter { this.emit(BrowserContext.Events.BeforeClose); this._closedStatus = 'closing'; - for (const listener of contextListeners) + for (const listener of this._browser.options.contextListeners) await listener.onContextWillDestroy(this); // Collect videos/downloads that we will await. @@ -323,7 +321,7 @@ export abstract class BrowserContext extends EventEmitter { await this._browser.close(); // Bookkeeping. - for (const listener of contextListeners) + for (const listener of this._browser.options.contextListeners) await listener.onContextDidDestroy(this); this._didCloseInternal(); } diff --git a/src/server/browserType.ts b/src/server/browserType.ts index b24dbf7220ab8..db6ed75572916 100644 --- a/src/server/browserType.ts +++ b/src/server/browserType.ts @@ -21,7 +21,7 @@ import * as util from 'util'; import { BrowserContext, normalizeProxySettings, validateBrowserContextOptions } from './browserContext'; import * as browserPaths from '../utils/browserPaths'; import { ConnectionTransport } from './transport'; -import { BrowserOptions, Browser, BrowserProcess } from './browser'; +import { BrowserOptions, Browser, BrowserProcess, PlaywrightOptions } from './browser'; import { launchProcess, Env, envArrayToObject } from './processLauncher'; import { PipeTransport } from './pipeTransport'; import { Progress, ProgressController } from './progress'; @@ -42,8 +42,10 @@ export abstract class BrowserType { private _executablePath: string; private _browserDescriptor: browserPaths.BrowserDescriptor; readonly _browserPath: string; + readonly _playwrightOptions: PlaywrightOptions; - constructor(packagePath: string, browser: browserPaths.BrowserDescriptor) { + constructor(packagePath: string, browser: browserPaths.BrowserDescriptor, playwrightOptions: PlaywrightOptions) { + this._playwrightOptions = playwrightOptions; this._name = browser.name; const browsersPath = browserPaths.browsersPath(packagePath); this._browserDescriptor = browser; @@ -87,6 +89,7 @@ export abstract class BrowserType { if ((options as any).__testHookBeforeCreateBrowser) await (options as any).__testHookBeforeCreateBrowser(); const browserOptions: BrowserOptions = { + ...this._playwrightOptions, name: this._name, isChromium: this._name === 'chromium', slowMo: options.slowMo, diff --git a/src/server/chromium/chromium.ts b/src/server/chromium/chromium.ts index f7b3ab52f2da4..490137af3b8c7 100644 --- a/src/server/chromium/chromium.ts +++ b/src/server/chromium/chromium.ts @@ -24,15 +24,15 @@ import { BrowserType } from '../browserType'; import { ConnectionTransport, ProtocolRequest } from '../transport'; import type { BrowserDescriptor } from '../../utils/browserPaths'; import { CRDevTools } from './crDevTools'; -import { BrowserOptions } from '../browser'; +import { BrowserOptions, PlaywrightOptions } from '../browser'; import * as types from '../types'; import { isDebugMode } from '../../utils/utils'; export class Chromium extends BrowserType { private _devtools: CRDevTools | undefined; - constructor(packagePath: string, browser: BrowserDescriptor) { - super(packagePath, browser); + constructor(packagePath: string, browser: BrowserDescriptor, playwrightOptions: PlaywrightOptions) { + super(packagePath, browser, playwrightOptions); if (isDebugMode()) this._devtools = this._createDevTools(); } diff --git a/src/server/chromium/crBrowser.ts b/src/server/chromium/crBrowser.ts index a1c394d28fece..aab3e7866c165 100644 --- a/src/server/chromium/crBrowser.ts +++ b/src/server/chromium/crBrowser.ts @@ -98,7 +98,7 @@ export class CRBrowser extends Browser { } async newContext(options: types.BrowserContextOptions = {}): Promise { - validateBrowserContextOptions(options, this._options); + validateBrowserContextOptions(options, this.options); const { browserContextId } = await this._session.send('Target.createBrowserContext', { disposeOnDetach: true, proxyServer: options.proxy ? options.proxy.server : undefined, @@ -119,7 +119,7 @@ export class CRBrowser extends Browser { } isClank(): boolean { - return this._options.name === 'clank'; + return this.options.name === 'clank'; } _onAttachedToTarget({targetInfo, sessionId, waitingForDebugger}: Protocol.Target.attachedToTargetPayload) { @@ -293,11 +293,11 @@ export class CRBrowserContext extends BrowserContext { async _initialize() { assert(!Array.from(this._browser._crPages.values()).some(page => page._browserContext === this)); const promises: Promise[] = [ super._initialize() ]; - if (this._browser._options.downloadsPath) { + if (this._browser.options.downloadsPath) { promises.push(this._browser._session.send('Browser.setDownloadBehavior', { behavior: this._options.acceptDownloads ? 'allowAndName' : 'deny', browserContextId: this._browserContextId, - downloadPath: this._browser._options.downloadsPath + downloadPath: this._browser.options.downloadsPath })); } if (this._options.permissions) diff --git a/src/server/chromium/crPage.ts b/src/server/chromium/crPage.ts index b4dbb96bf0dfb..cb575c87ba257 100644 --- a/src/server/chromium/crPage.ts +++ b/src/server/chromium/crPage.ts @@ -845,7 +845,7 @@ class FrameSession { ]; if (this._windowId) { let insets = { width: 0, height: 0 }; - if (this._crPage._browserContext._browser._options.headful) { + if (this._crPage._browserContext._browser.options.headful) { // TODO: popup windows have their own insets. insets = { width: 24, height: 88 }; if (process.platform === 'win32') diff --git a/src/server/electron/electron.ts b/src/server/electron/electron.ts index f3f14a78f3783..26b8b326469f7 100644 --- a/src/server/electron/electron.ts +++ b/src/server/electron/electron.ts @@ -29,7 +29,7 @@ import type {BrowserWindow} from 'electron'; import { Progress, ProgressController, runAbortableTask } from '../progress'; import { EventEmitter } from 'events'; import { helper } from '../helper'; -import { BrowserOptions, BrowserProcess } from '../browser'; +import { BrowserOptions, BrowserProcess, PlaywrightOptions } from '../browser'; import * as childProcess from 'child_process'; import * as readline from 'readline'; import { RecentLogsCollector } from '../../utils/debugLogger'; @@ -139,6 +139,12 @@ export class ElectronApplication extends EventEmitter { } export class Electron { + private _playwrightOptions: PlaywrightOptions; + + constructor(playwrightOptions: PlaywrightOptions) { + this._playwrightOptions = playwrightOptions; + } + async launch(executablePath: string, options: ElectronLaunchOptionsBase = {}): Promise { const { args = [], @@ -190,6 +196,7 @@ export class Electron { kill }; const browserOptions: BrowserOptions = { + ...this._playwrightOptions, name: 'electron', isChromium: true, headful: true, diff --git a/src/server/firefox/ffBrowser.ts b/src/server/firefox/ffBrowser.ts index 40dd62cb08d30..5a820f0818a58 100644 --- a/src/server/firefox/ffBrowser.ts +++ b/src/server/firefox/ffBrowser.ts @@ -72,7 +72,7 @@ export class FFBrowser extends Browser { } async newContext(options: types.BrowserContextOptions = {}): Promise { - validateBrowserContextOptions(options, this._options); + validateBrowserContextOptions(options, this.options); if (options.isMobile) throw new Error('options.isMobile is not supported in Firefox'); const { browserContextId } = await this._connection.send('Browser.createBrowserContext', { removeOnDetach: true }); @@ -149,12 +149,12 @@ export class FFBrowserContext extends BrowserContext { assert(!this._ffPages().length); const browserContextId = this._browserContextId; const promises: Promise[] = [ super._initialize() ]; - if (this._browser._options.downloadsPath) { + if (this._browser.options.downloadsPath) { promises.push(this._browser._connection.send('Browser.setDownloadOptions', { browserContextId, downloadOptions: { behavior: this._options.acceptDownloads ? 'saveToDisk' : 'cancel', - downloadsDir: this._browser._options.downloadsPath, + downloadsDir: this._browser.options.downloadsPath, }, })); } diff --git a/src/server/page.ts b/src/server/page.ts index 400c4500a13ce..4633993ba2e19 100644 --- a/src/server/page.ts +++ b/src/server/page.ts @@ -198,7 +198,7 @@ export class Page extends EventEmitter { } async _doSlowMo() { - const slowMo = this._browserContext._browser._options.slowMo; + const slowMo = this._browserContext._browser.options.slowMo; if (!slowMo) return; await new Promise(x => setTimeout(x, slowMo)); diff --git a/src/server/playwright.ts b/src/server/playwright.ts index 4d5979d1da136..aab14b89fd6d1 100644 --- a/src/server/playwright.ts +++ b/src/server/playwright.ts @@ -14,6 +14,8 @@ * limitations under the License. */ +import * as path from 'path'; +import { Tracer } from '../trace/tracer'; import * as browserPaths from '../utils/browserPaths'; import { Android } from './android/android'; import { AdbBackend } from './android/backendAdb'; @@ -21,6 +23,8 @@ import { Chromium } from './chromium/chromium'; import { Electron } from './electron/electron'; import { Firefox } from './firefox/firefox'; import { serverSelectors } from './selectors'; +import { HarTracer } from './supplements/har/harTracer'; +import { InspectorController } from './supplements/inspectorController'; import { WebKit } from './webkit/webkit'; export class Playwright { @@ -30,18 +34,29 @@ export class Playwright { readonly electron: Electron; readonly firefox: Firefox; readonly webkit: WebKit; + readonly options = { + contextListeners: [ + new InspectorController(), + new Tracer(), + new HarTracer() + ] + }; constructor(packagePath: string, browsers: browserPaths.BrowserDescriptor[]) { const chromium = browsers.find(browser => browser.name === 'chromium'); - this.chromium = new Chromium(packagePath, chromium!); + this.chromium = new Chromium(packagePath, chromium!, this.options); const firefox = browsers.find(browser => browser.name === 'firefox'); - this.firefox = new Firefox(packagePath, firefox!); + this.firefox = new Firefox(packagePath, firefox!, this.options); const webkit = browsers.find(browser => browser.name === 'webkit'); - this.webkit = new WebKit(packagePath, webkit!); + this.webkit = new WebKit(packagePath, webkit!, this.options); - this.electron = new Electron(); - this.android = new Android(new AdbBackend()); + this.electron = new Electron(this.options); + this.android = new Android(new AdbBackend(), this.options); } } + +export function createPlaywright() { + return new Playwright(path.join(__dirname, '..', '..'), require('../../browsers.json')['browsers']); +} diff --git a/src/trace/har.ts b/src/server/supplements/har/har.ts similarity index 100% rename from src/trace/har.ts rename to src/server/supplements/har/har.ts diff --git a/src/trace/harTracer.ts b/src/server/supplements/har/harTracer.ts similarity index 95% rename from src/trace/harTracer.ts rename to src/server/supplements/har/harTracer.ts index 39d64aa2f0cd2..8b0d83396b9a6 100644 --- a/src/trace/harTracer.ts +++ b/src/server/supplements/har/harTracer.ts @@ -16,19 +16,15 @@ import * as fs from 'fs'; import * as util from 'util'; -import { BrowserContext, ContextListener, contextListeners } from '../server/browserContext'; -import { helper } from '../server/helper'; -import * as network from '../server/network'; -import { Page } from '../server/page'; +import { BrowserContext, ContextListener } from '../../browserContext'; +import { helper } from '../../helper'; +import * as network from '../../network'; +import { Page } from '../../page'; import * as har from './har'; const fsWriteFileAsync = util.promisify(fs.writeFile.bind(fs)); -export function installHarTracer() { - contextListeners.add(new HarTracer()); -} - -class HarTracer implements ContextListener { +export class HarTracer implements ContextListener { private _contextTracers = new Map(); async onContextCreated(context: BrowserContext): Promise { @@ -68,10 +64,10 @@ class HarContextTracer { version: '1.2', creator: { name: 'Playwright', - version: require('../../package.json')['version'], + version: require('../../../../package.json')['version'], }, browser: { - name: context._browser._options.name, + name: context._browser.options.name, version: context._browser.version() }, pages: [], diff --git a/src/server/supplements/inspectorController.ts b/src/server/supplements/inspectorController.ts index bb6efa1c016fc..bc17c402461b5 100644 --- a/src/server/supplements/inspectorController.ts +++ b/src/server/supplements/inspectorController.ts @@ -14,18 +14,14 @@ * limitations under the License. */ -import { BrowserContext, ContextListener, contextListeners } from '../browserContext'; +import { BrowserContext, ContextListener } from '../browserContext'; import { isDebugMode } from '../../utils/utils'; import { ConsoleApiSupplement } from './consoleApiSupplement'; import { RecorderSupplement } from './recorderSupplement'; import { Page } from '../page'; import { ConsoleMessage } from '../console'; -export function installInspectorController() { - contextListeners.add(new InspectorController()); -} - -class InspectorController implements ContextListener { +export class InspectorController implements ContextListener { async onContextCreated(context: BrowserContext): Promise { if (isDebugMode()) { const consoleApi = new ConsoleApiSupplement(context); diff --git a/src/server/supplements/recorderSupplement.ts b/src/server/supplements/recorderSupplement.ts index 72ce01ab02094..40fc581913369 100644 --- a/src/server/supplements/recorderSupplement.ts +++ b/src/server/supplements/recorderSupplement.ts @@ -92,7 +92,7 @@ export class RecorderSupplement { this._output.setEnabled(app === 'codegen'); context.on(BrowserContext.Events.BeforeClose, () => this._output.flush()); - const generator = new CodeGenerator(context._browser._options.name, app === 'codegen', params.launchOptions || {}, params.contextOptions || {}, this._output, languageGenerator, params.device, params.saveStorage); + const generator = new CodeGenerator(context._browser.options.name, app === 'codegen', params.launchOptions || {}, params.contextOptions || {}, this._output, languageGenerator, params.device, params.saveStorage); this._generator = generator; } diff --git a/src/server/types.ts b/src/server/types.ts index e0970e79ff424..dd4da26659f8a 100644 --- a/src/server/types.ts +++ b/src/server/types.ts @@ -271,7 +271,7 @@ type LaunchOptionsBase = { chromiumSandbox?: boolean, slowMo?: number, }; -export type LaunchOptions = LaunchOptionsBase & UIOptions & { +export type LaunchOptions = LaunchOptionsBase & { firefoxUserPrefs?: { [key: string]: string | number | boolean }, }; export type LaunchPersistentOptions = LaunchOptionsBase & BrowserContextOptions; @@ -326,10 +326,6 @@ export type Error = { stack?: string, }; -export type UIOptions = { - slowMo?: number; -}; - export type NameValueList = { name: string; value: string; diff --git a/src/server/webkit/wkBrowser.ts b/src/server/webkit/wkBrowser.ts index c120047c8d5b0..51aa7fa34666b 100644 --- a/src/server/webkit/wkBrowser.ts +++ b/src/server/webkit/wkBrowser.ts @@ -74,7 +74,7 @@ export class WKBrowser extends Browser { } async newContext(options: types.BrowserContextOptions = {}): Promise { - validateBrowserContextOptions(options, this._options); + validateBrowserContextOptions(options, this.options); const createOptions = options.proxy ? { proxyServer: options.proxy.server, proxyBypassList: options.proxy.bypass @@ -208,10 +208,10 @@ export class WKBrowserContext extends BrowserContext { assert(!this._wkPages().length); const browserContextId = this._browserContextId; const promises: Promise[] = [ super._initialize() ]; - if (this._browser._options.downloadsPath) { + if (this._browser.options.downloadsPath) { promises.push(this._browser._browserSession.send('Playwright.setDownloadBehavior', { behavior: this._options.acceptDownloads ? 'allow' : 'deny', - downloadPath: this._browser._options.downloadsPath, + downloadPath: this._browser.options.downloadsPath, browserContextId })); } diff --git a/src/trace/tracer.ts b/src/trace/tracer.ts index 8483834e1f0ad..69a50717a603e 100644 --- a/src/trace/tracer.ts +++ b/src/trace/tracer.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { ActionListener, ActionMetadata, BrowserContext, ContextListener, contextListeners, Video } from '../server/browserContext'; +import { ActionListener, ActionMetadata, BrowserContext, ContextListener, Video } from '../server/browserContext'; import type { SnapshotterResource as SnapshotterResource, SnapshotterBlob, SnapshotterDelegate } from './snapshotter'; import * as trace from './traceTypes'; import * as path from 'path'; @@ -34,11 +34,7 @@ const fsAppendFileAsync = util.promisify(fs.appendFile.bind(fs)); const fsAccessAsync = util.promisify(fs.access.bind(fs)); const envTrace = getFromENV('PW_TRACE_DIR'); -export function installTracer() { - contextListeners.add(new Tracer()); -} - -class Tracer implements ContextListener { +export class Tracer implements ContextListener { private _contextTracers = new Map(); async onContextCreated(context: BrowserContext): Promise { @@ -93,7 +89,7 @@ class ContextTracer implements SnapshotterDelegate, ActionListener { const event: trace.ContextCreatedTraceEvent = { timestamp: monotonicTime(), type: 'context-created', - browserName: context._browser._options.name, + browserName: context._browser.options.name, contextId: this._contextId, isMobile: !!context._options.isMobile, deviceScaleFactor: context._options.deviceScaleFactor || 1, diff --git a/test/har.spec.ts b/test/har.spec.ts index 522e8fee2f53d..b64fb3cd6b921 100644 --- a/test/har.spec.ts +++ b/test/har.spec.ts @@ -17,7 +17,7 @@ import { folio as baseFolio } from './fixtures'; import * as fs from 'fs'; -import type * as har from '../src/trace/har'; +import type * as har from '../src/server/supplements/har/har'; import type { BrowserContext, Page } from '../index'; const builder = baseFolio.extend<{ diff --git a/utils/check_deps.js b/utils/check_deps.js index f4c7bc9baf9ca..bacde9ed4ffa3 100644 --- a/utils/check_deps.js +++ b/utils/check_deps.js @@ -138,7 +138,7 @@ DEPS['src/server/injected/'] = ['src/server/common/']; DEPS['src/server/android/'] = [...DEPS['src/server/'], 'src/server/chromium/', 'src/protocol/']; DEPS['src/server/electron/'] = [...DEPS['src/server/'], 'src/server/chromium/']; -DEPS['src/server/playwright.ts'] = [...DEPS['src/server/'], 'src/server/chromium/', 'src/server/webkit/', 'src/server/firefox/', 'src/server/android/', 'src/server/electron/']; +DEPS['src/server/playwright.ts'] = [...DEPS['src/server/'], 'src/trace/', 'src/server/chromium/', 'src/server/webkit/', 'src/server/firefox/', 'src/server/android/', 'src/server/electron/']; DEPS['src/cli/driver.ts'] = DEPS['src/inprocess.ts'] = DEPS['src/browserServerImpl.ts'] = ['src/**']; // Tracing is a client/server plugin, nothing should depend on it.