diff --git a/src/browser.ts b/src/browser.ts index fd36606b6c..8df824eb44 100644 --- a/src/browser.ts +++ b/src/browser.ts @@ -20,9 +20,7 @@ import { Page } from './page'; import { EventEmitter } from 'events'; import { Download } from './download'; import { Events } from './events'; -import { Loggers } from './logger'; import { ProxySettings } from './types'; -import { LoggerSink } from './loggerSink'; import { ChildProcess } from 'child_process'; export interface BrowserProcess { @@ -34,7 +32,6 @@ export interface BrowserProcess { export type BrowserOptions = { name: string, - loggers: Loggers, downloadsPath?: string, headful?: boolean, persistent?: types.BrowserContextOptions, // Undefined means no persistent context. @@ -43,7 +40,7 @@ export type BrowserOptions = { proxy?: ProxySettings, }; -export type BrowserContextOptions = types.BrowserContextOptions & { logger?: LoggerSink }; +export type BrowserContextOptions = types.BrowserContextOptions; export interface Browser extends EventEmitter { newContext(options?: BrowserContextOptions): Promise; diff --git a/src/browserContext.ts b/src/browserContext.ts index 3568aa0c8d..3faba57a14 100644 --- a/src/browserContext.ts +++ b/src/browserContext.ts @@ -24,11 +24,9 @@ import * as types from './types'; import { Events } from './events'; import { Download } from './download'; import { BrowserBase } from './browser'; -import { Loggers, Logger } from './logger'; import { EventEmitter } from 'events'; import { ProgressController } from './progress'; import { DebugController } from './debug/debugController'; -import { LoggerSink } from './loggerSink'; export interface BrowserContext extends EventEmitter { setDefaultNavigationTimeout(timeout: number): void; @@ -53,12 +51,10 @@ export interface BrowserContext extends EventEmitter { close(): Promise; } -type BrowserContextOptions = types.BrowserContextOptions & { logger?: LoggerSink }; - export abstract class BrowserContextBase extends EventEmitter implements BrowserContext { readonly _timeoutSettings = new TimeoutSettings(); readonly _pageBindings = new Map(); - readonly _options: BrowserContextOptions; + readonly _options: types.BrowserContextOptions; _routes: { url: types.URLMatch, handler: network.RouteHandler }[] = []; private _isPersistentContext: boolean; private _closedStatus: 'open' | 'closing' | 'closed' = 'open'; @@ -67,14 +63,11 @@ export abstract class BrowserContextBase extends EventEmitter implements Browser readonly _permissions = new Map(); readonly _downloads = new Set(); readonly _browserBase: BrowserBase; - readonly _apiLogger: Logger; - constructor(browserBase: BrowserBase, options: BrowserContextOptions, isPersistentContext: boolean) { + constructor(browserBase: BrowserBase, options: types.BrowserContextOptions, isPersistentContext: boolean) { super(); this._browserBase = browserBase; this._options = options; - const loggers = options.logger ? new Loggers(options.logger) : browserBase._options.loggers; - this._apiLogger = loggers.api; this._isPersistentContext = isPersistentContext; this._closePromise = new Promise(fulfill => this._closePromiseFulfill = fulfill); } @@ -86,7 +79,7 @@ export abstract class BrowserContextBase extends EventEmitter implements Browser async waitForEvent(event: string, optionsOrPredicate: types.WaitForEventOptions = {}): Promise { const options = typeof optionsOrPredicate === 'function' ? { predicate: optionsOrPredicate } : optionsOrPredicate; - const progressController = new ProgressController(this._apiLogger, this._timeoutSettings.timeout(options)); + const progressController = new ProgressController(this._timeoutSettings.timeout(options)); if (event !== Events.BrowserContext.Close) this._closePromise.then(error => progressController.abort(error)); return progressController.run(progress => helper.waitForEvent(progress, this, event, options.predicate).promise); @@ -248,10 +241,10 @@ export function assertBrowserContextIsNotOwned(context: BrowserContextBase) { } } -export function validateBrowserContextOptions(options: BrowserContextOptions): BrowserContextOptions { +export function validateBrowserContextOptions(options: types.BrowserContextOptions): types.BrowserContextOptions { // Copy all fields manually to strip any extra junk. // Especially useful when we share context and launch options for launchPersistent. - const result: BrowserContextOptions = { + const result: types.BrowserContextOptions = { ignoreHTTPSErrors: options.ignoreHTTPSErrors, bypassCSP: options.bypassCSP, locale: options.locale, @@ -269,7 +262,6 @@ export function validateBrowserContextOptions(options: BrowserContextOptions): B deviceScaleFactor: options.deviceScaleFactor, isMobile: options.isMobile, hasTouch: options.hasTouch, - logger: options.logger, }; if (result.viewport === null && result.deviceScaleFactor !== undefined) throw new Error(`"deviceScaleFactor" option is not supported with null "viewport"`); diff --git a/src/chromium/crBrowser.ts b/src/chromium/crBrowser.ts index 7e7389ee79..ba9a17e2e0 100644 --- a/src/chromium/crBrowser.ts +++ b/src/chromium/crBrowser.ts @@ -48,7 +48,7 @@ export class CRBrowser extends BrowserBase { private _tracingClient: CRSession | undefined; static async connect(transport: ConnectionTransport, options: BrowserOptions, devtools?: CRDevTools): Promise { - const connection = new CRConnection(SlowMoTransport.wrap(transport, options.slowMo), options.loggers); + const connection = new CRConnection(SlowMoTransport.wrap(transport, options.slowMo)); const browser = new CRBrowser(connection, options); browser._devtools = devtools; const session = connection.rootSession; diff --git a/src/chromium/crConnection.ts b/src/chromium/crConnection.ts index 6ac2ae5348..47acc41ee2 100644 --- a/src/chromium/crConnection.ts +++ b/src/chromium/crConnection.ts @@ -15,11 +15,10 @@ * limitations under the License. */ -import { assert } from '../helper'; +import { assert, debugLogger } from '../helper'; import { ConnectionTransport, ProtocolRequest, ProtocolResponse } from '../transport'; import { Protocol } from './protocol'; import { EventEmitter } from 'events'; -import { Loggers, Logger } from '../logger'; import { rewriteErrorMessage } from '../utils/stackTrace'; export const ConnectionEvents = { @@ -36,19 +35,16 @@ export class CRConnection extends EventEmitter { private readonly _sessions = new Map(); readonly rootSession: CRSession; _closed = false; - readonly _logger: Logger; - constructor(transport: ConnectionTransport, loggers: Loggers) { + constructor(transport: ConnectionTransport) { super(); this._transport = transport; - this._logger = loggers.protocol; this._transport.onmessage = this._onMessage.bind(this); this._transport.onclose = this._onClose.bind(this); this.rootSession = new CRSession(this, '', 'browser', ''); this._sessions.set('', this.rootSession); } - static fromSession(session: CRSession): CRConnection { return session._connection!; } @@ -62,15 +58,15 @@ export class CRConnection extends EventEmitter { const message: ProtocolRequest = { id, method, params }; if (sessionId) message.sessionId = sessionId; - if (this._logger.isEnabled()) - this._logger.info('SEND ► ' + rewriteInjectedScriptEvaluationLog(message)); + if (debugLogger.isEnabled('protocol')) + debugLogger.log('protocol', 'SEND ► ' + rewriteInjectedScriptEvaluationLog(message)); this._transport.send(message); return id; } async _onMessage(message: ProtocolResponse) { - if (this._logger.isEnabled()) - this._logger.info('◀ RECV ' + JSON.stringify(message)); + if (debugLogger.isEnabled('protocol')) + debugLogger.log('protocol', '◀ RECV ' + JSON.stringify(message)); if (message.id === kBrowserCloseMessageId) return; if (message.method === 'Target.attachedToTarget') { @@ -167,10 +163,7 @@ export class CRSession extends EventEmitter { } _sendMayFail(method: T, params?: Protocol.CommandParameters[T]): Promise { - return this.send(method, params).catch((error: Error) => { - if (this._connection) - this._connection._logger.error(error); - }); + return this.send(method, params).catch((error: Error) => debugLogger.log('error', error)); } _onMessage(object: ProtocolResponse) { diff --git a/src/chromium/crPage.ts b/src/chromium/crPage.ts index b99f011403..a30f3c1923 100644 --- a/src/chromium/crPage.ts +++ b/src/chromium/crPage.ts @@ -664,7 +664,6 @@ class FrameSession { _onDialog(event: Protocol.Page.javascriptDialogOpeningPayload) { this._page.emit(Events.Page.Dialog, new dialog.Dialog( - this._page._logger, event.type, event.message, async (accept: boolean, promptText?: string) => { diff --git a/src/dialog.ts b/src/dialog.ts index 5f7db6adef..2fb35ede5e 100644 --- a/src/dialog.ts +++ b/src/dialog.ts @@ -15,28 +15,25 @@ * limitations under the License. */ -import { assert } from './helper'; -import { Logger } from './logger'; +import { assert, debugLogger } from './helper'; type OnHandle = (accept: boolean, promptText?: string) => Promise; export type DialogType = 'alert' | 'beforeunload' | 'confirm' | 'prompt'; export class Dialog { - private _logger: Logger; private _type: string; private _message: string; private _onHandle: OnHandle; private _handled = false; private _defaultValue: string; - constructor(logger: Logger, type: string, message: string, onHandle: OnHandle, defaultValue?: string) { - this._logger = logger; + constructor(type: string, message: string, onHandle: OnHandle, defaultValue?: string) { this._type = type; this._message = message; this._onHandle = onHandle; this._defaultValue = defaultValue || ''; - this._logger.info(` ${this._preview()} was shown`); + debugLogger.log('api', ` ${this._preview()} was shown`); } type(): string { @@ -54,14 +51,14 @@ export class Dialog { async accept(promptText: string | undefined) { assert(!this._handled, 'Cannot accept dialog which is already handled!'); this._handled = true; - this._logger.info(` ${this._preview()} was accepted`); + debugLogger.log('api', ` ${this._preview()} was accepted`); await this._onHandle(true, promptText); } async dismiss() { assert(!this._handled, 'Cannot dismiss dialog which is already handled!'); this._handled = true; - this._logger.info(` ${this._preview()} was dismissed`); + debugLogger.log('api', ` ${this._preview()} was dismissed`); await this._onHandle(false); } diff --git a/src/dom.ts b/src/dom.ts index e294d45fd6..903dd358a7 100644 --- a/src/dom.ts +++ b/src/dom.ts @@ -291,25 +291,25 @@ export class ElementHandle extends js.JSHandle { options: types.PointerActionOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions): Promise<'error:notconnected' | 'done'> { let first = true; while (progress.isRunning()) { - progress.logger.info(`${first ? 'attempting' : 'retrying'} ${actionName} action`); + progress.log(`${first ? 'attempting' : 'retrying'} ${actionName} action`); const result = await this._performPointerAction(progress, actionName, waitForEnabled, action, options); first = false; if (result === 'error:notvisible') { if (options.force) throw new Error('Element is not visible'); - progress.logger.info(' element is not visible'); + progress.log(' element is not visible'); continue; } if (result === 'error:notinviewport') { if (options.force) throw new Error('Element is outside of the viewport'); - progress.logger.info(' element is outside of the viewport'); + progress.log(' element is outside of the viewport'); continue; } if (typeof result === 'object' && 'hitTargetDescription' in result) { if (options.force) throw new Error(`Element does not receive pointer events, ${result.hitTargetDescription} intercepts them`); - progress.logger.info(` ${result.hitTargetDescription} intercepts pointer events`); + progress.log(` ${result.hitTargetDescription} intercepts pointer events`); continue; } return result; @@ -329,12 +329,12 @@ export class ElementHandle extends js.JSHandle { if ((options as any).__testHookAfterStable) await (options as any).__testHookAfterStable(); - progress.logger.info(' scrolling into view if needed'); + progress.log(' scrolling into view if needed'); progress.throwIfAborted(); // Avoid action that has side-effects. const scrolled = await this._scrollRectIntoViewIfNeeded(position ? { x: position.x, y: position.y, width: 0, height: 0 } : undefined); if (scrolled !== 'done') return scrolled; - progress.logger.info(' done scrolling'); + progress.log(' done scrolling'); const maybePoint = position ? await this._offsetPoint(position) : await this._clickablePoint(); if (typeof maybePoint === 'string') @@ -344,11 +344,11 @@ export class ElementHandle extends js.JSHandle { if (!force) { if ((options as any).__testHookBeforeHitTarget) await (options as any).__testHookBeforeHitTarget(); - progress.logger.info(` checking that element receives pointer events at (${point.x},${point.y})`); + progress.log(` checking that element receives pointer events at (${point.x},${point.y})`); const hitTargetResult = await this._checkHitTargetAt(point); if (hitTargetResult !== 'done') return hitTargetResult; - progress.logger.info(` element does receive pointer events`); + progress.log(` element does receive pointer events`); } await this._page._frameManager.waitForSignalsCreatedBy(progress, options.noWaitAfter, async () => { @@ -358,16 +358,16 @@ export class ElementHandle extends js.JSHandle { let restoreModifiers: types.KeyboardModifier[] | undefined; if (options && options.modifiers) restoreModifiers = await this._page.keyboard._ensureModifiers(options.modifiers); - progress.logger.info(` performing ${actionName} action`); + progress.log(` performing ${actionName} action`); await action(point); - progress.logger.info(` ${actionName} action done`); - progress.logger.info(' waiting for scheduled navigations to finish'); + progress.log(` ${actionName} action done`); + progress.log(' waiting for scheduled navigations to finish'); if ((options as any).__testHookAfterPointerAction) await (options as any).__testHookAfterPointerAction(); if (restoreModifiers) await this._page.keyboard._ensureModifiers(restoreModifiers); }, 'input'); - progress.logger.info(' navigations have finished'); + progress.log(' navigations have finished'); return 'done'; } @@ -448,10 +448,10 @@ export class ElementHandle extends js.JSHandle { } async _fill(progress: Progress, value: string, options: types.NavigatingActionWaitOptions): Promise<'error:notconnected' | 'done'> { - progress.logger.info(`elementHandle.fill("${value}")`); + progress.log(`elementHandle.fill("${value}")`); assert(helper.isString(value), `value: expected string, got ${typeof value}`); return this._page._frameManager.waitForSignalsCreatedBy(progress, options.noWaitAfter, async () => { - progress.logger.info(' waiting for element to be visible, enabled and editable'); + progress.log(' waiting for element to be visible, enabled and editable'); const poll = await this._evaluateHandleInUtility(([injected, node, value]) => { return injected.waitForEnabledAndFill(node, value); }, value); @@ -460,7 +460,7 @@ export class ElementHandle extends js.JSHandle { progress.throwIfAborted(); // Avoid action that has side-effects. if (filled === 'error:notconnected') return filled; - progress.logger.info(' element is visible, enabled and editable'); + progress.log(' element is visible, enabled and editable'); if (filled === 'needsinput') { progress.throwIfAborted(); // Avoid action that has side-effects. if (value) @@ -534,7 +534,7 @@ export class ElementHandle extends js.JSHandle { } async _type(progress: Progress, text: string, options: { delay?: number } & types.NavigatingActionWaitOptions): Promise<'error:notconnected' | 'done'> { - progress.logger.info(`elementHandle.type("${text}")`); + progress.log(`elementHandle.type("${text}")`); return this._page._frameManager.waitForSignalsCreatedBy(progress, options.noWaitAfter, async () => { const result = await this._focus(progress, true /* resetSelectionIfNotFocused */); if (result !== 'done') @@ -553,7 +553,7 @@ export class ElementHandle extends js.JSHandle { } async _press(progress: Progress, key: string, options: { delay?: number } & types.NavigatingActionWaitOptions): Promise<'error:notconnected' | 'done'> { - progress.logger.info(`elementHandle.press("${key}")`); + progress.log(`elementHandle.press("${key}")`); return this._page._frameManager.waitForSignalsCreatedBy(progress, options.noWaitAfter, async () => { const result = await this._focus(progress, true /* resetSelectionIfNotFocused */); if (result !== 'done') @@ -644,7 +644,7 @@ export class ElementHandle extends js.JSHandle { const info = selectors._parseSelector(selector); const task = waitForSelectorTask(info, state, this); return this._page._runAbortableTask(async progress => { - progress.logger.info(`waiting for selector "${selector}"${state === 'attached' ? '' : ' to be ' + state}`); + progress.log(`waiting for selector "${selector}"${state === 'attached' ? '' : ' to be ' + state}`); const context = await this._context.frame._context(info.world); const injected = await context.injectedScript(); const pollHandler = new InjectedScriptPollHandler(progress, await task(injected)); @@ -669,9 +669,9 @@ export class ElementHandle extends js.JSHandle { async _waitForDisplayedAtStablePosition(progress: Progress, waitForEnabled: boolean): Promise<'error:notconnected' | 'done'> { if (waitForEnabled) - progress.logger.info(` waiting for element to be visible, enabled and not moving`); + progress.log(` waiting for element to be visible, enabled and not moving`); else - progress.logger.info(` waiting for element to be visible and not moving`); + progress.log(` waiting for element to be visible and not moving`); const rafCount = this._page._delegate.rafCountForStablePosition(); const poll = this._evaluateHandleInUtility(([injected, node, { rafCount, waitForEnabled }]) => { return injected.waitForDisplayedAtStablePosition(node, rafCount, waitForEnabled); @@ -679,9 +679,9 @@ export class ElementHandle extends js.JSHandle { const pollHandler = new InjectedScriptPollHandler(progress, await poll); const result = await pollHandler.finish(); if (waitForEnabled) - progress.logger.info(' element is visible, enabled and does not move'); + progress.log(' element is visible, enabled and does not move'); else - progress.logger.info(' element is visible and does not move'); + progress.log(' element is visible and does not move'); return result; } @@ -722,7 +722,7 @@ export class InjectedScriptPollHandler { if (!this._poll || !this._progress.isRunning()) return; for (const message of messages) - this._progress.logger.info(message); + this._progress.log(message); } } @@ -752,7 +752,7 @@ export class InjectedScriptPollHandler { // Retrieve all the logs before continuing. const messages = await this._poll.evaluate(poll => poll.takeLastLogs()).catch(e => [] as string[]); for (const message of messages) - this._progress.logger.info(message); + this._progress.log(message); } async cancel() { diff --git a/src/firefox/ffBrowser.ts b/src/firefox/ffBrowser.ts index f4a07ed0d6..5452dcb120 100644 --- a/src/firefox/ffBrowser.ts +++ b/src/firefox/ffBrowser.ts @@ -36,7 +36,7 @@ export class FFBrowser extends BrowserBase { private _version = ''; static async connect(transport: ConnectionTransport, options: BrowserOptions): Promise { - const connection = new FFConnection(SlowMoTransport.wrap(transport, options.slowMo), options.loggers); + const connection = new FFConnection(SlowMoTransport.wrap(transport, options.slowMo)); const browser = new FFBrowser(connection, options); const promises: Promise[] = [ connection.send('Browser.enable', { attachToDefaultContext: !!options.persistent }), diff --git a/src/firefox/ffConnection.ts b/src/firefox/ffConnection.ts index 53e8a1a7ba..8c057bc05a 100644 --- a/src/firefox/ffConnection.ts +++ b/src/firefox/ffConnection.ts @@ -16,10 +16,9 @@ */ import { EventEmitter } from 'events'; -import { assert } from '../helper'; +import { assert, debugLogger } from '../helper'; import { ConnectionTransport, ProtocolRequest, ProtocolResponse } from '../transport'; import { Protocol } from './protocol'; -import { Loggers, Logger } from '../logger'; import { rewriteErrorMessage } from '../utils/stackTrace'; export const ConnectionEvents = { @@ -34,7 +33,6 @@ export class FFConnection extends EventEmitter { private _lastId: number; private _callbacks: Map; private _transport: ConnectionTransport; - readonly _logger: Logger; readonly _sessions: Map; _closed: boolean; @@ -44,10 +42,9 @@ export class FFConnection extends EventEmitter { removeListener: (event: T, listener: (payload: T extends symbol ? any : Protocol.Events[T extends keyof Protocol.Events ? T : never]) => void) => this; once: (event: T, listener: (payload: T extends symbol ? any : Protocol.Events[T extends keyof Protocol.Events ? T : never]) => void) => this; - constructor(transport: ConnectionTransport, loggers: Loggers) { + constructor(transport: ConnectionTransport) { super(); this._transport = transport; - this._logger = loggers.protocol; this._lastId = 0; this._callbacks = new Map(); @@ -79,14 +76,14 @@ export class FFConnection extends EventEmitter { } _rawSend(message: ProtocolRequest) { - if (this._logger.isEnabled()) - this._logger.info('SEND ► ' + rewriteInjectedScriptEvaluationLog(message)); + if (debugLogger.isEnabled('protocol')) + debugLogger.log('protocol', 'SEND ► ' + rewriteInjectedScriptEvaluationLog(message)); this._transport.send(message); } async _onMessage(message: ProtocolResponse) { - if (this._logger.isEnabled()) - this._logger.info('◀ RECV ' + JSON.stringify(message)); + if (debugLogger.isEnabled('protocol')) + debugLogger.log('protocol', '◀ RECV ' + JSON.stringify(message)); if (message.id === kBrowserCloseMessageId) return; if (message.sessionId) { @@ -186,9 +183,7 @@ export class FFSession extends EventEmitter { } sendMayFail(method: T, params?: Protocol.CommandParameters[T]): Promise { - return this.send(method, params).catch(error => { - this._connection._logger.error(error); - }); + return this.send(method, params).catch(error => debugLogger.log('error', error)); } dispatchMessage(object: ProtocolResponse) { diff --git a/src/firefox/ffPage.ts b/src/firefox/ffPage.ts index fd03f8cabe..bccc9f38c3 100644 --- a/src/firefox/ffPage.ts +++ b/src/firefox/ffPage.ts @@ -186,7 +186,6 @@ export class FFPage implements PageDelegate { _onDialogOpened(params: Protocol.Page.dialogOpenedPayload) { this._page.emit(Events.Page.Dialog, new dialog.Dialog( - this._page._logger, params.type, params.message, async (accept: boolean, promptText?: string) => { diff --git a/src/frames.ts b/src/frames.ts index cfa5f484d3..a4d2b0005c 100644 --- a/src/frames.ts +++ b/src/frames.ts @@ -20,7 +20,7 @@ import * as util from 'util'; import { ConsoleMessage } from './console'; import * as dom from './dom'; import { Events } from './events'; -import { assert, helper, RegisteredListener, assertMaxArguments } from './helper'; +import { assert, helper, RegisteredListener, assertMaxArguments, debugLogger } from './helper'; import * as js from './javascript'; import * as network from './network'; import { Page } from './page'; @@ -201,7 +201,7 @@ export class FrameManager { const navigationEvent: NavigationEvent = { url, name, newDocument: frame._currentDocument }; frame._eventEmitter.emit(kNavigationEvent, navigationEvent); if (!initial) { - this._page._logger.info(` navigated to "${url}"`); + debugLogger.log('api', ` navigated to "${url}"`); this._page.emit(Events.Page.FrameNavigated, frame); } // Restore pending if any - see comments above about keepPending. @@ -215,7 +215,7 @@ export class FrameManager { frame._url = url; const navigationEvent: NavigationEvent = { url, name: frame._name }; frame._eventEmitter.emit(kNavigationEvent, navigationEvent); - this._page._logger.info(` navigated to "${url}"`); + debugLogger.log('api', ` navigated to "${url}"`); this._page.emit(Events.Page.FrameNavigated, frame); } @@ -414,7 +414,7 @@ export class Frame { if (!this._subtreeLifecycleEvents.has(event)) { this._eventEmitter.emit(kAddLifecycleEvent, event); if (this === mainFrame && this._url !== 'about:blank') - this._page._logger.info(` "${event}" event fired`); + debugLogger.log('api', ` "${event}" event fired`); if (this === mainFrame && event === 'load') this._page.emit(Events.Page.Load); if (this === mainFrame && event === 'domcontentloaded') @@ -431,7 +431,7 @@ export class Frame { async goto(url: string, options: types.GotoOptions = {}): Promise { return runNavigationTask(this, options, async progress => { const waitUntil = verifyLifecycle('waitUntil', options.waitUntil === undefined ? 'load' : options.waitUntil); - progress.logger.info(`navigating to "${url}", waiting until "${waitUntil}"`); + progress.log(`navigating to "${url}", waiting until "${waitUntil}"`); const headers = (this._page._state.extraHTTPHeaders || {}); let referer = headers['referer'] || headers['Referer']; if (options.referer !== undefined) { @@ -476,13 +476,13 @@ export class Frame { return runNavigationTask(this, options, async progress => { const toUrl = typeof options.url === 'string' ? ` to "${options.url}"` : ''; const waitUntil = verifyLifecycle('waitUntil', options.waitUntil === undefined ? 'load' : options.waitUntil); - progress.logger.info(`waiting for navigation${toUrl} until "${waitUntil}"`); + progress.log(`waiting for navigation${toUrl} until "${waitUntil}"`); const navigationEvent: NavigationEvent = await helper.waitForEvent(progress, this._eventEmitter, kNavigationEvent, (event: NavigationEvent) => { // Any failed navigation results in a rejection. if (event.error) return true; - progress.logger.info(` navigated to "${this._url}"`); + progress.log(` navigated to "${this._url}"`); return helper.urlMatches(this._url, options.url); }).promise; if (navigationEvent.error) @@ -565,7 +565,7 @@ export class Frame { const info = selectors._parseSelector(selector); const task = dom.waitForSelectorTask(info, state); return this._page._runAbortableTask(async progress => { - progress.logger.info(`waiting for selector "${selector}"${state === 'attached' ? '' : ' to be ' + state}`); + progress.log(`waiting for selector "${selector}"${state === 'attached' ? '' : ' to be ' + state}`); const result = await this._scheduleRerunnableHandleTask(progress, info.world, task); if (!result.asElement()) { result.dispose(); @@ -580,7 +580,7 @@ export class Frame { const info = selectors._parseSelector(selector); const task = dom.dispatchEventTask(info, type, eventInit || {}); return this._page._runAbortableTask(async progress => { - progress.logger.info(`Dispatching "${type}" event on selector "${selector}"...`); + progress.log(`Dispatching "${type}" event on selector "${selector}"...`); // Note: we always dispatch events in the main world. await this._scheduleRerunnableTask(progress, 'main', task); }, this._page._timeoutSettings.timeout(options)); @@ -635,7 +635,7 @@ export class Frame { async setContent(html: string, options: types.NavigateOptions = {}): Promise { return runNavigationTask(this, options, async progress => { const waitUntil = options.waitUntil === undefined ? 'load' : options.waitUntil; - progress.logger.info(`setting frame content, waiting until "${waitUntil}"`); + progress.log(`setting frame content, waiting until "${waitUntil}"`); const tag = `--playwright--set--content--${this._id}--${++this._setContentCounter}--`; const context = await this._utilityContext(); const lifecyclePromise = new Promise((resolve, reject) => { @@ -822,7 +822,7 @@ export class Frame { const info = selectors._parseSelector(selector); return this._page._runAbortableTask(async progress => { while (progress.isRunning()) { - progress.logger.info(`waiting for selector "${selector}"`); + progress.log(`waiting for selector "${selector}"`); const task = dom.waitForSelectorTask(info, 'attached'); const handle = await this._scheduleRerunnableHandleTask(progress, info.world, task); const element = handle.asElement() as dom.ElementHandle; @@ -834,7 +834,7 @@ export class Frame { const result = await action(progress, element); element.dispose(); if (result === 'error:notconnected') { - progress.logger.info('element was detached from the DOM, retrying'); + progress.log('element was detached from the DOM, retrying'); continue; } return result; @@ -863,7 +863,7 @@ export class Frame { const info = selectors._parseSelector(selector); const task = dom.textContentTask(info); return this._page._runAbortableTask(async progress => { - progress.logger.info(` retrieving textContent from "${selector}"`); + progress.log(` retrieving textContent from "${selector}"`); return this._scheduleRerunnableTask(progress, info.world, task); }, this._page._timeoutSettings.timeout(options)); } @@ -872,7 +872,7 @@ export class Frame { const info = selectors._parseSelector(selector); const task = dom.innerTextTask(info); return this._page._runAbortableTask(async progress => { - progress.logger.info(` retrieving innerText from "${selector}"`); + progress.log(` retrieving innerText from "${selector}"`); const result = dom.throwFatalDOMError(await this._scheduleRerunnableTask(progress, info.world, task)); return result.innerText; }, this._page._timeoutSettings.timeout(options)); @@ -882,7 +882,7 @@ export class Frame { const info = selectors._parseSelector(selector); const task = dom.innerHTMLTask(info); return this._page._runAbortableTask(async progress => { - progress.logger.info(` retrieving innerHTML from "${selector}"`); + progress.log(` retrieving innerHTML from "${selector}"`); return this._scheduleRerunnableTask(progress, info.world, task); }, this._page._timeoutSettings.timeout(options)); } @@ -891,7 +891,7 @@ export class Frame { const info = selectors._parseSelector(selector); const task = dom.getAttributeTask(info, name); return this._page._runAbortableTask(async progress => { - progress.logger.info(` retrieving attribute "${name}" from "${selector}"`); + progress.log(` retrieving attribute "${name}" from "${selector}"`); return this._scheduleRerunnableTask(progress, info.world, task); }, this._page._timeoutSettings.timeout(options)); } @@ -1104,7 +1104,7 @@ class SignalBarrier { this.retain(); const waiter = helper.waitForEvent(null, frame._eventEmitter, kNavigationEvent, (e: NavigationEvent) => { if (!e.error && this._progress) - this._progress.logger.info(` navigated to "${frame._url}"`); + this._progress.log(` navigated to "${frame._url}"`); return true; }); await Promise.race([ @@ -1130,7 +1130,7 @@ class SignalBarrier { async function runNavigationTask(frame: Frame, options: types.TimeoutOptions, task: (progress: Progress) => Promise): Promise { const page = frame._page; - const controller = new ProgressController(page._logger, page._timeoutSettings.navigationTimeout(options)); + const controller = new ProgressController(page._timeoutSettings.navigationTimeout(options)); page._disconnectedPromise.then(() => controller.abort(new Error('Navigation failed because page was closed!'))); page._crashedPromise.then(() => controller.abort(new Error('Navigation failed because page crashed!'))); frame._detachedPromise.then(() => controller.abort(new Error('Navigating frame was detached!'))); diff --git a/src/helper.ts b/src/helper.ts index df0b1c4b3d..05a1a0024f 100644 --- a/src/helper.ts +++ b/src/helper.ts @@ -23,6 +23,7 @@ import * as removeFolder from 'rimraf'; import * as util from 'util'; import * as types from './types'; import { Progress } from './progress'; +import * as debug from 'debug'; const removeFolderAsync = util.promisify(removeFolder); const readFileAsync = util.promisify(fs.readFile.bind(fs)); @@ -384,3 +385,48 @@ export function logPolitely(toBeLogged: string) { const escapeGlobChars = new Set(['/', '$', '^', '+', '.', '(', ')', '=', '!', '|']); export const helper = Helper; + +const debugLoggerColorMap = { + 'api': 45, // cyan + 'protocol': 34, // green + 'browser': 0, // reset + 'error': 160, // red, + 'channel:command': 33, // blue + 'channel:response': 202, // orange + 'channel:event': 207, // magenta +}; +export type LogName = keyof typeof debugLoggerColorMap; + +export class DebugLogger { + private _debuggers = new Map(); + + constructor() { + if (process.env.DEBUG_FILE) { + const ansiRegex = new RegExp([ + '[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)', + '(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))' + ].join('|'), 'g'); + const stream = fs.createWriteStream(process.env.DEBUG_FILE); + (debug as any).log = (data: string) => { + stream.write(data.replace(ansiRegex, '')); + stream.write('\n'); + }; + } + } + + log(name: LogName, message: string | Error | object) { + let cachedDebugger = this._debuggers.get(name); + if (!cachedDebugger) { + cachedDebugger = debug(`pw:${name}`); + this._debuggers.set(name, cachedDebugger); + (cachedDebugger as any).color = debugLoggerColorMap[name]; + } + cachedDebugger(message); + } + + isEnabled(name: LogName) { + return debug.enabled(`pw:${name}`); + } +} + +export const debugLogger = new DebugLogger(); diff --git a/src/logger.ts b/src/logger.ts deleted file mode 100644 index 246424a588..0000000000 --- a/src/logger.ts +++ /dev/null @@ -1,190 +0,0 @@ -/** - * Copyright Microsoft Corporation. All rights reserved. - * - * 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 * as debug from 'debug'; -import * as fs from 'fs'; -import { helper } from './helper'; -import { LoggerSink, LoggerSeverity } from './loggerSink'; - -export function logError(logger: Logger): (error: Error) => void { - return error => logger.error(error); -} - -export class Logger { - private _loggerSink: LoggerSink; - private _name: string; - private _hints: { color?: string; }; - private _scopeName: string | undefined; - private _recording: string[] | undefined; - - constructor(loggerSink: LoggerSink, name: string, hints: { color?: string }, scopeName?: string, record?: boolean) { - this._loggerSink = loggerSink; - this._name = name; - this._hints = hints; - this._scopeName = scopeName; - if (record) - this._recording = []; - } - - isEnabled(severity?: LoggerSeverity): boolean { - return this._loggerSink.isEnabled(this._name, severity || 'info'); - } - - verbose(message: string, ...args: any[]) { - return this._innerLog('verbose', message, args); - } - - info(message: string, ...args: any[]) { - return this._innerLog('info', message, args); - } - - warn(message: string, ...args: any[]) { - return this._innerLog('warning', message, args); - } - - error(message: string | Error, ...args: any[]) { - return this._innerLog('error', message, args); - } - - createScope(scopeName: string | undefined, record?: boolean): Logger { - if (scopeName) - this._loggerSink.log(this._name, 'info', `=> ${scopeName} started`, [], this._hints); - return new Logger(this._loggerSink, this._name, this._hints, scopeName, record); - } - - endScope(status: string) { - if (this._scopeName) - this._loggerSink.log(this._name, 'info', `<= ${this._scopeName} ${status}`, [], this._hints); - } - - private _innerLog(severity: LoggerSeverity, message: string | Error, ...args: any[]) { - if (this._recording) - this._recording.push(`[${this._name}] ${message}`); - this._loggerSink.log(this._name, severity, message, args, this._hints); - } - - recording(): string[] { - return this._recording ? this._recording.slice() : []; - } -} - -export class Loggers { - readonly api: Logger; - readonly browser: Logger; - readonly protocol: Logger; - - constructor(userSink: LoggerSink | undefined) { - const loggerSink = new MultiplexingLoggerSink(); - if (userSink) - loggerSink.add('user', userSink); - if (helper.isDebugMode()) - loggerSink.add('pwdebug', new PwDebugLoggerSink()); - loggerSink.add('debug', new DebugLoggerSink()); - - this.api = new Logger(loggerSink, 'api', { color: 'cyan' }); - this.browser = new Logger(loggerSink, 'browser', {}); - this.protocol = new Logger(loggerSink, 'protocol', { color: 'green' }); - } -} - -class MultiplexingLoggerSink implements LoggerSink { - private _loggers = new Map(); - - add(id: string, logger: LoggerSink) { - this._loggers.set(id, logger); - } - - get(id: string): LoggerSink | undefined { - return this._loggers.get(id); - } - - remove(id: string) { - this._loggers.delete(id); - } - - isEnabled(name: string, severity: LoggerSeverity): boolean { - for (const logger of this._loggers.values()) { - if (logger.isEnabled(name, severity)) - return true; - } - return false; - } - - log(name: string, severity: LoggerSeverity, message: string | Error, args: any[], hints: { color?: string }) { - for (const logger of this._loggers.values()) { - if (logger.isEnabled(name, severity)) - logger.log(name, severity, message, args, hints); - } - } -} - -const colorMap = new Map([ - ['red', 160], - ['green', 34], - ['yellow', 172], - ['blue', 33], - ['magenta', 207], - ['cyan', 45], - ['reset', 0], -]); - -export class DebugLoggerSink { - private _debuggers = new Map(); - constructor() { - if (process.env.DEBUG_FILE) { - const ansiRegex = new RegExp([ - '[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)', - '(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))' - ].join('|'), 'g'); - const stream = fs.createWriteStream(process.env.DEBUG_FILE); - (debug as any).log = (data: string) => { - stream.write(data.replace(ansiRegex, '')); - stream.write('\n'); - }; - } - } - - isEnabled(name: string, severity: LoggerSeverity): boolean { - return debug.enabled(`pw:${name}`); - } - - log(name: string, severity: LoggerSeverity, message: string | Error, args: any[], hints: { color?: string }) { - let cachedDebugger = this._debuggers.get(name); - if (!cachedDebugger) { - cachedDebugger = debug(`pw:${name}`); - this._debuggers.set(name, cachedDebugger); - - let color = hints.color || 'reset'; - switch (severity) { - case 'error': color = 'red'; break; - case 'warning': color = 'yellow'; break; - } - const escaped = colorMap.get(color) || 0; - if (escaped) - (cachedDebugger as any).color = String(escaped); - } - cachedDebugger(message, ...args); - } -} - -class PwDebugLoggerSink { - isEnabled(name: string, severity: LoggerSeverity): boolean { - return false; - } - - log(name: string, severity: LoggerSeverity, message: string | Error, args: any[], hints: { color?: string }) { - } -} diff --git a/src/loggerSink.ts b/src/loggerSink.ts deleted file mode 100644 index 976d233048..0000000000 --- a/src/loggerSink.ts +++ /dev/null @@ -1,25 +0,0 @@ -/** - * Copyright Microsoft Corporation. All rights reserved. - * - * 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. - */ - -export type LoggerSeverity = 'verbose' | 'info' | 'warning' | 'error'; - -export interface LoggerSink { - isEnabled(name: string, severity: LoggerSeverity): boolean; - log(name: string, severity: LoggerSeverity, message: string | Error, args: any[], hints: { color?: string }): void; -} - -// This is a workaround for the documentation generation. -export interface Logger extends LoggerSink {} diff --git a/src/page.ts b/src/page.ts index 95f4b1c6a9..572666a994 100644 --- a/src/page.ts +++ b/src/page.ts @@ -17,7 +17,7 @@ import * as dom from './dom'; import * as frames from './frames'; -import { assert, helper, Listener, assertMaxArguments } from './helper'; +import { assert, helper, Listener, assertMaxArguments, debugLogger } from './helper'; import * as input from './input'; import * as js from './javascript'; import * as network from './network'; @@ -30,7 +30,6 @@ import { ConsoleMessage } from './console'; import * as accessibility from './accessibility'; import { EventEmitter } from 'events'; import { FileChooser } from './fileChooser'; -import { logError, Logger } from './logger'; import { ProgressController, Progress, runAbortableTask } from './progress'; export interface PageDelegate { @@ -105,7 +104,6 @@ export class Page extends EventEmitter { readonly mouse: input.Mouse; readonly _timeoutSettings: TimeoutSettings; readonly _delegate: PageDelegate; - readonly _logger: Logger; readonly _state: PageState; readonly _pageBindings = new Map(); readonly _evaluateOnNewDocumentSources: string[] = []; @@ -121,7 +119,6 @@ export class Page extends EventEmitter { constructor(delegate: PageDelegate, browserContext: BrowserContextBase) { super(); this._delegate = delegate; - this._logger = browserContext._apiLogger; this._closedCallback = () => {}; this._closedPromise = new Promise(f => this._closedCallback = f); this._disconnectedCallback = () => {}; @@ -170,7 +167,7 @@ export class Page extends EventEmitter { async _runAbortableTask(task: (progress: Progress) => Promise, timeout: number): Promise { return runAbortableTask(async progress => { return task(progress); - }, this._logger, timeout); + }, timeout); } async _onFileChooserOpened(handle: dom.ElementHandle) { @@ -278,7 +275,7 @@ export class Page extends EventEmitter { async waitForEvent(event: string, optionsOrPredicate: types.WaitForEventOptions = {}): Promise { const options = typeof optionsOrPredicate === 'function' ? { predicate: optionsOrPredicate } : optionsOrPredicate; - const progressController = new ProgressController(this._logger, this._timeoutSettings.timeout(options)); + const progressController = new ProgressController(this._timeoutSettings.timeout(options)); this._disconnectedPromise.then(error => progressController.abort(error)); if (event !== Events.Page.Crash) this._crashedPromise.then(error => progressController.abort(error)); @@ -524,12 +521,12 @@ export class PageBinding { if (!binding) binding = page._browserContext._pageBindings.get(name); const result = await binding!.playwrightFunction({ frame: context.frame, page, context: page._browserContext }, ...args); - context.evaluateInternal(deliverResult, { name, seq, result }).catch(logError(page._logger)); + context.evaluateInternal(deliverResult, { name, seq, result }).catch(e => debugLogger.log('error', e)); } catch (error) { if (helper.isError(error)) - context.evaluateInternal(deliverError, { name, seq, message: error.message, stack: error.stack }).catch(logError(page._logger)); + context.evaluateInternal(deliverError, { name, seq, message: error.message, stack: error.stack }).catch(e => debugLogger.log('error', e)); else - context.evaluateInternal(deliverErrorValue, { name, seq, error }).catch(logError(page._logger)); + context.evaluateInternal(deliverErrorValue, { name, seq, error }).catch(e => debugLogger.log('error', e)); } function deliverResult(arg: { name: string, seq: number, result: any }) { diff --git a/src/progress.ts b/src/progress.ts index 73aac79613..ae3ff0fe2d 100644 --- a/src/progress.ts +++ b/src/progress.ts @@ -14,22 +14,21 @@ * limitations under the License. */ -import { Logger } from './logger'; import { TimeoutError } from './errors'; -import { assert } from './helper'; +import { assert, debugLogger, LogName } from './helper'; import { rewriteErrorMessage } from './utils/stackTrace'; export interface Progress { readonly aborted: Promise; - readonly logger: Logger; + log(message: string): void; timeUntilDeadline(): number; isRunning(): boolean; cleanupWhenAborted(cleanup: () => any): void; throwIfAborted(): void; } -export async function runAbortableTask(task: (progress: Progress) => Promise, logger: Logger, timeout: number): Promise { - const controller = new ProgressController(logger, timeout); +export async function runAbortableTask(task: (progress: Progress) => Promise, timeout: number, logName: LogName = 'api'): Promise { + const controller = new ProgressController(timeout, logName); return controller.run(task); } @@ -47,14 +46,14 @@ export class ProgressController { // Cleanups to be run only in the case of abort. private _cleanups: (() => any)[] = []; - private _logger: Logger; + private _logName: LogName; private _state: 'before' | 'running' | 'aborted' | 'finished' = 'before'; private _deadline: number; private _timeout: number; + private _logRecordring: string[] = []; - constructor(logger: Logger, timeout: number) { - this._logger = logger; - + constructor(timeout: number, logName: LogName = 'api') { + this._logName = logName; this._timeout = timeout; this._deadline = timeout ? monotonicTime() + timeout : 0; @@ -67,11 +66,13 @@ export class ProgressController { assert(this._state === 'before'); this._state = 'running'; - const loggerScope = this._logger.createScope(undefined, true); - const progress: Progress = { aborted: this._abortedPromise, - logger: loggerScope, + log: message => { + if (this._state === 'running') + this._logRecordring.push(message); + debugLogger.log(this._logName, message); + }, timeUntilDeadline: () => this._deadline ? this._deadline - monotonicTime() : 2147483647, // 2^31-1 safe setTimeout in Node. isRunning: () => this._state === 'running', cleanupWhenAborted: (cleanup: () => any) => { @@ -93,17 +94,17 @@ export class ProgressController { const result = await Promise.race([promise, this._forceAbortPromise]); clearTimeout(timer); this._state = 'finished'; - loggerScope.endScope('succeeded'); + this._logRecordring = []; return result; } catch (e) { this._aborted(); rewriteErrorMessage(e, e.message + - formatLogRecording(loggerScope.recording()) + + formatLogRecording(this._logRecordring) + kLoggingNote); clearTimeout(timer); this._state = 'aborted'; - loggerScope.endScope(`failed`); + this._logRecordring = []; await Promise.all(this._cleanups.splice(0).map(cleanup => runCleanup(cleanup))); throw e; } diff --git a/src/rpc/client/api.ts b/src/rpc/client/api.ts index 9ade7ef1ee..2997005f76 100644 --- a/src/rpc/client/api.ts +++ b/src/rpc/client/api.ts @@ -24,7 +24,7 @@ export { Dialog } from './dialog'; export { Download } from './download'; export { ElementHandle } from './elementHandle'; export { FileChooser } from './fileChooser'; -export { Logger } from '../../loggerSink'; +export { Logger } from './types'; export { TimeoutError } from '../../errors'; export { Frame } from './frame'; export { Keyboard, Mouse } from './input'; diff --git a/src/rpc/client/channelOwner.ts b/src/rpc/client/channelOwner.ts index d0c2bcbb6d..666df912ac 100644 --- a/src/rpc/client/channelOwner.ts +++ b/src/rpc/client/channelOwner.ts @@ -18,7 +18,7 @@ import { EventEmitter } from 'events'; import type { Channel } from '../channels'; import type { Connection } from './connection'; import type { LoggerSink } from './types'; -import { DebugLoggerSink } from '../../logger'; +import { debugLogger } from '../../helper'; export abstract class ChannelOwner extends EventEmitter { private _connection: Connection; @@ -119,10 +119,8 @@ export abstract class ChannelOwner { constructor(connection: Connection) { @@ -73,7 +73,7 @@ export class Connection { const id = ++this._lastId; const validated = method === 'debugScopeState' ? params : validateParams(type, method, params); const converted = { id, guid, method, params: validated }; - debug('pw:channel:command')(converted); + debugLogger.log('channel:command', converted); this.onmessage(converted); return new Promise((resolve, reject) => this._callbacks.set(id, { resolve, reject })); } @@ -85,7 +85,7 @@ export class Connection { dispatch(message: object) { const { id, guid, method, params, result, error } = message as any; if (id) { - debug('pw:channel:response')(message); + debugLogger.log('channel:response', message); const callback = this._callbacks.get(id)!; this._callbacks.delete(id); if (error) @@ -95,7 +95,7 @@ export class Connection { return; } - debug('pw:channel:event')(message); + debugLogger.log('channel:event', message); if (method === '__create__') { this._createRemoteObject(guid, params.type, params.guid, params.initializer); return; diff --git a/src/server/browserType.ts b/src/server/browserType.ts index dce38a9662..886e7780a7 100644 --- a/src/server/browserType.ts +++ b/src/server/browserType.ts @@ -20,7 +20,6 @@ import * as path from 'path'; import * as util from 'util'; import { BrowserContext, verifyProxySettings, validateBrowserContextOptions } from '../browserContext'; import * as browserPaths from '../install/browserPaths'; -import { Loggers } from '../logger'; import { ConnectionTransport, WebSocketTransport } from '../transport'; import { BrowserBase, BrowserOptions, Browser, BrowserProcess } from '../browser'; import { assert, helper } from '../helper'; @@ -29,16 +28,13 @@ import { PipeTransport } from './pipeTransport'; import { Progress, runAbortableTask } from '../progress'; import * as types from '../types'; import { TimeoutSettings } from '../timeoutSettings'; -import { LoggerSink } from '../loggerSink'; import { validateHostRequirements } from './validateDependencies'; type FirefoxPrefsOptions = { firefoxUserPrefs?: { [key: string]: string | number | boolean } }; -type LaunchOptions = types.LaunchOptions & { logger?: LoggerSink }; - -export type LaunchNonPersistentOptions = LaunchOptions & FirefoxPrefsOptions; -type LaunchPersistentOptions = LaunchOptions & types.BrowserContextOptions; -type LaunchServerOptions = types.LaunchServerOptions & { logger?: LoggerSink } & FirefoxPrefsOptions; +export type LaunchNonPersistentOptions = types.LaunchOptions & FirefoxPrefsOptions; +type LaunchPersistentOptions = types.LaunchOptions & types.BrowserContextOptions; +type LaunchServerOptions = types.LaunchServerOptions & FirefoxPrefsOptions; export interface BrowserType { executablePath(): string; @@ -84,8 +80,7 @@ export abstract class BrowserTypeBase implements BrowserType { assert(!(options as any).userDataDir, 'userDataDir option is not supported in `browserType.launch`. Use `browserType.launchPersistentContext` instead'); assert(!(options as any).port, 'Cannot specify a port without launching as a server.'); options = validateLaunchOptions(options); - const loggers = new Loggers(options.logger); - const browser = await runAbortableTask(progress => this._innerLaunch(progress, options, loggers, undefined), loggers.browser, TimeoutSettings.timeout(options)).catch(e => { throw this._rewriteStartupError(e); }); + const browser = await runAbortableTask(progress => this._innerLaunch(progress, options, undefined), TimeoutSettings.timeout(options), 'browser').catch(e => { throw this._rewriteStartupError(e); }); return browser; } @@ -93,14 +88,13 @@ export abstract class BrowserTypeBase implements BrowserType { assert(!(options as any).port, 'Cannot specify a port without launching as a server.'); options = validateLaunchOptions(options); const persistent = validateBrowserContextOptions(options); - const loggers = new Loggers(options.logger); - const browser = await runAbortableTask(progress => this._innerLaunch(progress, options, loggers, persistent, userDataDir), loggers.browser, TimeoutSettings.timeout(options)).catch(e => { throw this._rewriteStartupError(e); }); + const browser = await runAbortableTask(progress => this._innerLaunch(progress, options, persistent, userDataDir), TimeoutSettings.timeout(options), 'browser').catch(e => { throw this._rewriteStartupError(e); }); return browser._defaultContext!; } - async _innerLaunch(progress: Progress, options: LaunchOptions, logger: Loggers, persistent: types.BrowserContextOptions | undefined, userDataDir?: string): Promise { + async _innerLaunch(progress: Progress, options: types.LaunchOptions, persistent: types.BrowserContextOptions | undefined, userDataDir?: string): Promise { options.proxy = options.proxy ? verifyProxySettings(options.proxy) : undefined; - const { browserProcess, downloadsPath, transport } = await this._launchProcess(progress, options, !!persistent, logger, userDataDir); + const { browserProcess, downloadsPath, transport } = await this._launchProcess(progress, options, !!persistent, userDataDir); if ((options as any).__testHookBeforeCreateBrowser) await (options as any).__testHookBeforeCreateBrowser(); const browserOptions: BrowserOptions = { @@ -108,7 +102,6 @@ export abstract class BrowserTypeBase implements BrowserType { slowMo: options.slowMo, persistent, headful: !options.headless, - loggers: logger, downloadsPath, browserProcess, proxy: options.proxy, @@ -122,7 +115,7 @@ export abstract class BrowserTypeBase implements BrowserType { return browser; } - private async _launchProcess(progress: Progress, options: LaunchServerOptions, isPersistent: boolean, loggers: Loggers, userDataDir?: string): Promise<{ browserProcess: BrowserProcess, downloadsPath: string, transport: ConnectionTransport }> { + private async _launchProcess(progress: Progress, options: LaunchServerOptions, isPersistent: boolean, userDataDir?: string): Promise<{ browserProcess: BrowserProcess, downloadsPath: string, transport: ConnectionTransport }> { const { ignoreDefaultArgs = false, args = [], @@ -213,7 +206,7 @@ export abstract class BrowserTypeBase implements BrowserType { transport = await WebSocketTransport.connect(progress, innerEndpoint); } else { const stdio = launchedProcess.stdio as unknown as [NodeJS.ReadableStream, NodeJS.WritableStream, NodeJS.WritableStream, NodeJS.WritableStream, NodeJS.ReadableStream]; - transport = new PipeTransport(stdio[3], stdio[4], loggers.browser); + transport = new PipeTransport(stdio[3], stdio[4]); } return { browserProcess, downloadsPath, transport }; } diff --git a/src/server/electron.ts b/src/server/electron.ts index aecac18fa8..c49d98e24a 100644 --- a/src/server/electron.ts +++ b/src/server/electron.ts @@ -20,7 +20,6 @@ import { CRConnection, CRSession } from '../chromium/crConnection'; import { CRExecutionContext } from '../chromium/crExecutionContext'; import { Events } from '../events'; import * as js from '../javascript'; -import { Loggers, Logger } from '../logger'; import { Page } from '../page'; import { TimeoutSettings } from '../timeoutSettings'; import { WebSocketTransport } from '../transport'; @@ -31,7 +30,6 @@ import type {BrowserWindow} from 'electron'; import { runAbortableTask, ProgressController } from '../progress'; import { EventEmitter } from 'events'; import { helper } from '../helper'; -import { LoggerSink } from '../loggerSink'; import { BrowserProcess } from '../browser'; export type ElectronLaunchOptionsBase = { @@ -57,7 +55,6 @@ export interface ElectronPage extends Page { } export class ElectronApplication extends EventEmitter { - private _apiLogger: Logger; private _browserContext: CRBrowserContext; private _nodeConnection: CRConnection; private _nodeSession: CRSession; @@ -67,9 +64,8 @@ export class ElectronApplication extends EventEmitter { private _lastWindowId = 0; readonly _timeoutSettings = new TimeoutSettings(); - constructor(logger: Loggers, browser: CRBrowser, nodeConnection: CRConnection) { + constructor(browser: CRBrowser, nodeConnection: CRConnection) { super(); - this._apiLogger = logger.api; this._browserContext = browser._defaultContext as CRBrowserContext; this._browserContext.on(Events.BrowserContext.Close, () => { // Emit application closed after context closed. @@ -136,7 +132,7 @@ export class ElectronApplication extends EventEmitter { async waitForEvent(event: string, optionsOrPredicate: types.WaitForEventOptions = {}): Promise { const options = typeof optionsOrPredicate === 'function' ? { predicate: optionsOrPredicate } : optionsOrPredicate; - const progressController = new ProgressController(this._apiLogger, this._timeoutSettings.timeout(options)); + const progressController = new ProgressController(this._timeoutSettings.timeout(options)); if (event !== ElectronEvents.ElectronApplication.Close) this._browserContext._closePromise.then(error => progressController.abort(error)); return progressController.run(progress => helper.waitForEvent(progress, this, event, options.predicate).promise); @@ -169,7 +165,7 @@ export class ElectronApplication extends EventEmitter { } export class Electron { - async launch(executablePath: string, options: ElectronLaunchOptionsBase & { logger?: LoggerSink } = {}): Promise { + async launch(executablePath: string, options: ElectronLaunchOptionsBase = {}): Promise { const { args = [], env = process.env, @@ -177,7 +173,6 @@ export class Electron { handleSIGTERM = true, handleSIGHUP = true, } = options; - const loggers = new Loggers(options.logger); return runAbortableTask(async progress => { let app: ElectronApplication | undefined = undefined; const electronArguments = ['--inspect=0', '--remote-debugging-port=0', '--require', path.join(__dirname, 'electronLoader.js'), ...args]; @@ -198,7 +193,7 @@ export class Electron { const nodeMatch = await waitForLine(progress, launchedProcess, launchedProcess.stderr, /^Debugger listening on (ws:\/\/.*)$/); const nodeTransport = await WebSocketTransport.connect(progress, nodeMatch[1]); - const nodeConnection = new CRConnection(nodeTransport, loggers); + const nodeConnection = new CRConnection(nodeTransport); const chromeMatch = await waitForLine(progress, launchedProcess, launchedProcess.stderr, /^DevTools listening on (ws:\/\/.*)$/); const chromeTransport = await WebSocketTransport.connect(progress, chromeMatch[1]); @@ -208,10 +203,10 @@ export class Electron { close: gracefullyClose, kill }; - const browser = await CRBrowser.connect(chromeTransport, { name: 'electron', headful: true, loggers, persistent: { viewport: null }, browserProcess }); - app = new ElectronApplication(loggers, browser, nodeConnection); + const browser = await CRBrowser.connect(chromeTransport, { name: 'electron', headful: true, persistent: { viewport: null }, browserProcess }); + app = new ElectronApplication(browser, nodeConnection); await app._init(); return app; - }, loggers.browser, TimeoutSettings.timeout(options)); + }, TimeoutSettings.timeout(options), 'browser'); } } diff --git a/src/server/pipeTransport.ts b/src/server/pipeTransport.ts index 47876f142a..0e6a0a546f 100644 --- a/src/server/pipeTransport.ts +++ b/src/server/pipeTransport.ts @@ -15,9 +15,8 @@ * limitations under the License. */ -import { helper, RegisteredListener } from '../helper'; +import { helper, RegisteredListener, debugLogger } from '../helper'; import { ConnectionTransport, ProtocolRequest, ProtocolResponse } from '../transport'; -import { logError, Logger } from '../logger'; export class PipeTransport implements ConnectionTransport { private _pipeWrite: NodeJS.WritableStream; @@ -29,7 +28,7 @@ export class PipeTransport implements ConnectionTransport { onmessage?: (message: ProtocolResponse) => void; onclose?: () => void; - constructor(pipeWrite: NodeJS.WritableStream, pipeRead: NodeJS.ReadableStream, logger: Logger) { + constructor(pipeWrite: NodeJS.WritableStream, pipeRead: NodeJS.ReadableStream) { this._pipeWrite = pipeWrite; this._eventListeners = [ helper.addEventListener(pipeRead, 'data', buffer => this._dispatch(buffer)), @@ -39,8 +38,8 @@ export class PipeTransport implements ConnectionTransport { if (this.onclose) this.onclose.call(null); }), - helper.addEventListener(pipeRead, 'error', logError(logger)), - helper.addEventListener(pipeWrite, 'error', logError(logger)), + helper.addEventListener(pipeRead, 'error', e => debugLogger.log('error', e)), + helper.addEventListener(pipeWrite, 'error', e => debugLogger.log('error', e)), ]; this.onmessage = undefined; this.onclose = undefined; diff --git a/src/server/processLauncher.ts b/src/server/processLauncher.ts index 23aecf579c..7a638a7273 100644 --- a/src/server/processLauncher.ts +++ b/src/server/processLauncher.ts @@ -60,7 +60,7 @@ export async function launchProcess(options: LaunchProcessOptions): Promise ${options.executablePath} ${options.args.join(' ')}`); + progress.log(` ${options.executablePath} ${options.args.join(' ')}`); const spawnedProcess = childProcess.spawn( options.executablePath, options.args, @@ -82,16 +82,16 @@ export async function launchProcess(options: LaunchProcessOptions): Promise failedPromise).then(e => Promise.reject(e)); } - progress.logger.info(` pid=${spawnedProcess.pid}`); + progress.log(` pid=${spawnedProcess.pid}`); const stdout = readline.createInterface({ input: spawnedProcess.stdout }); stdout.on('line', (data: string) => { - progress.logger.info(data); + progress.log('[out] ' + data); }); const stderr = readline.createInterface({ input: spawnedProcess.stderr }); stderr.on('line', (data: string) => { - progress.logger.warn(data); + progress.log('[err] ' + data); }); let processClosed = false; @@ -100,7 +100,7 @@ export async function launchProcess(options: LaunchProcessOptions): Promise {}; const waitForCleanup = new Promise(f => fulfillCleanup = f); spawnedProcess.once('exit', (exitCode, signal) => { - progress.logger.info(``); + progress.log(``); processClosed = true; helper.removeEventListeners(listeners); gracefullyCloseSet.delete(gracefullyClose); @@ -136,21 +136,21 @@ export async function launchProcess(options: LaunchProcessOptions): Promise`); + progress.log(``); killProcess(); await waitForClose; // Ensure the process is dead and we called options.onkill. return; } gracefullyClosing = true; - progress.logger.info(``); + progress.log(``); await options.attemptToGracefullyClose().catch(() => killProcess()); await waitForCleanup; // Ensure the process is dead and we have cleaned up. - progress.logger.info(``); + progress.log(``); } // This method has to be sync to be used as 'exit' event handler. function killProcess() { - progress.logger.info(``); + progress.log(``); helper.removeEventListeners(listeners); if (spawnedProcess.pid && !spawnedProcess.killed && !processClosed) { // Force kill the browser. diff --git a/src/transport.ts b/src/transport.ts index a19ba01f62..55ad9f7d68 100644 --- a/src/transport.ts +++ b/src/transport.ts @@ -126,7 +126,7 @@ export class WebSocketTransport implements ConnectionTransport { onclose?: () => void; static async connect(progress: Progress, url: string): Promise { - progress.logger.info(` ${url}`); + progress.log(` ${url}`); const transport = new WebSocketTransport(progress, url); let success = false; progress.aborted.then(() => { @@ -135,11 +135,11 @@ export class WebSocketTransport implements ConnectionTransport { }); await new Promise((fulfill, reject) => { transport._ws.addEventListener('open', async () => { - progress.logger.info(` ${url}`); + progress.log(` ${url}`); fulfill(transport); }); transport._ws.addEventListener('error', event => { - progress.logger.info(` ${url} ${event.message}`); + progress.log(` ${url} ${event.message}`); reject(new Error('WebSocket error: ' + event.message)); transport._ws.close(); }); @@ -169,7 +169,7 @@ export class WebSocketTransport implements ConnectionTransport { }); this._ws.addEventListener('close', event => { - this._progress && this._progress.logger.info(` ${url}`); + this._progress && this._progress.log(` ${url}`); if (this.onclose) this.onclose.call(null); }); @@ -182,7 +182,7 @@ export class WebSocketTransport implements ConnectionTransport { } close() { - this._progress && this._progress.logger.info(` ${this._ws.url}`); + this._progress && this._progress.log(` ${this._ws.url}`); this._ws.close(); } diff --git a/src/webkit/wkBrowser.ts b/src/webkit/wkBrowser.ts index d93d3f4625..409a1d94ec 100644 --- a/src/webkit/wkBrowser.ts +++ b/src/webkit/wkBrowser.ts @@ -52,7 +52,7 @@ export class WKBrowser extends BrowserBase { constructor(transport: ConnectionTransport, options: BrowserOptions) { super(options); - this._connection = new WKConnection(transport, options.loggers, this._onDisconnect.bind(this)); + this._connection = new WKConnection(transport, this._onDisconnect.bind(this)); this._browserSession = this._connection.browserSession; this._eventListeners = [ helper.addEventListener(this._browserSession, 'Playwright.pageProxyCreated', this._onPageProxyCreated.bind(this)), diff --git a/src/webkit/wkConnection.ts b/src/webkit/wkConnection.ts index 0554c0d180..15f7abae0d 100644 --- a/src/webkit/wkConnection.ts +++ b/src/webkit/wkConnection.ts @@ -16,10 +16,9 @@ */ import { EventEmitter } from 'events'; -import { assert } from '../helper'; +import { assert, debugLogger } from '../helper'; import { ConnectionTransport, ProtocolRequest, ProtocolResponse } from '../transport'; import { Protocol } from './protocol'; -import { Loggers, Logger } from '../logger'; import { rewriteErrorMessage } from '../utils/stackTrace'; // WKPlaywright uses this special id to issue Browser.close command which we @@ -37,11 +36,9 @@ export class WKConnection { private _lastId = 0; private _closed = false; readonly browserSession: WKSession; - readonly _logger: Logger; - constructor(transport: ConnectionTransport, loggers: Loggers, onDisconnect: () => void) { + constructor(transport: ConnectionTransport, onDisconnect: () => void) { this._transport = transport; - this._logger = loggers.protocol; this._transport.onmessage = this._dispatchMessage.bind(this); this._transport.onclose = this._onClose.bind(this); this._onDisconnect = onDisconnect; @@ -55,14 +52,14 @@ export class WKConnection { } rawSend(message: ProtocolRequest) { - if (this._logger.isEnabled()) - this._logger.info('SEND ► ' + rewriteInjectedScriptEvaluationLog(message)); + if (debugLogger.isEnabled('protocol')) + debugLogger.log('protocol', 'SEND ► ' + rewriteInjectedScriptEvaluationLog(message)); this._transport.send(message); } private _dispatchMessage(message: ProtocolResponse) { - if (this._logger.isEnabled()) - this._logger.info('◀ RECV ' + JSON.stringify(message)); + if (debugLogger.isEnabled('protocol')) + debugLogger.log('protocol', '◀ RECV ' + JSON.stringify(message)); if (message.id === kBrowserCloseMessageId) return; if (message.pageProxyId) { @@ -138,9 +135,7 @@ export class WKSession extends EventEmitter { } sendMayFail(method: T, params?: Protocol.CommandParameters[T]): Promise { - return this.send(method, params).catch(error => { - this.connection._logger.error(error); - }); + return this.send(method, params).catch(error => debugLogger.log('error', error)); } markAsCrashed() { diff --git a/src/webkit/wkPage.ts b/src/webkit/wkPage.ts index 780807c2a5..64886e84d3 100644 --- a/src/webkit/wkPage.ts +++ b/src/webkit/wkPage.ts @@ -522,7 +522,6 @@ export class WKPage implements PageDelegate { _onDialog(event: Protocol.Dialog.javascriptDialogOpeningPayload) { this._page.emit(Events.Page.Dialog, new dialog.Dialog( - this._page._logger, event.type as dialog.DialogType, event.message, async (accept: boolean, promptText?: string) => { diff --git a/test/browsertype-launch.spec.ts b/test/browsertype-launch.spec.ts index 5fceee5eed..5fa364c0b6 100644 --- a/test/browsertype-launch.spec.ts +++ b/test/browsertype-launch.spec.ts @@ -64,8 +64,8 @@ it.skip(WIRE)('should handle timeout', async({browserType, defaultBrowserOptions const options = { ...defaultBrowserOptions, timeout: 5000, __testHookBeforeCreateBrowser: () => new Promise(f => setTimeout(f, 6000)) }; const error = await browserType.launch(options).catch(e => e); expect(error.message).toContain(`browserType.launch: Timeout 5000ms exceeded.`); - expect(error.message).toContain(`[browser] `); - expect(error.message).toContain(`[browser] pid=`); + expect(error.message).toContain(``); + expect(error.message).toContain(` pid=`); }); it.skip(WIRE)('should handle exception', async({browserType, defaultBrowserOptions}) => {