diff --git a/src/browser.ts b/src/browser.ts index 2ecfd25323..55d9d1c375 100644 --- a/src/browser.ts +++ b/src/browser.ts @@ -20,7 +20,7 @@ import { EventEmitter } from 'events'; import { Download } from './download'; import type { BrowserServer } from './server/browserServer'; import { Events } from './events'; -import { InnerLogger, Log } from './logger'; +import { InnerLogger } from './logger'; import { ProxySettings } from './types'; export type BrowserOptions = { @@ -41,7 +41,7 @@ export interface Browser extends EventEmitter { close(): Promise; } -export abstract class BrowserBase extends EventEmitter implements Browser, InnerLogger { +export abstract class BrowserBase extends EventEmitter implements Browser { readonly _options: BrowserOptions; private _downloads = new Map(); _defaultContext: BrowserContextBase | null = null; @@ -93,13 +93,5 @@ export abstract class BrowserBase extends EventEmitter implements Browser, Inner if (this.isConnected()) await new Promise(x => this.once(Events.Browser.Disconnected, x)); } - - _isLogEnabled(log: Log): boolean { - return this._options.logger._isLogEnabled(log); - } - - _log(log: Log, message: string | Error, ...args: any[]) { - return this._options.logger._log(log, message, ...args); - } } diff --git a/src/browserContext.ts b/src/browserContext.ts index 9ea552630b..e7a361b974 100644 --- a/src/browserContext.ts +++ b/src/browserContext.ts @@ -24,7 +24,7 @@ import { Events } from './events'; import { ExtendedEventEmitter } from './extendedEventEmitter'; import { Download } from './download'; import { BrowserBase } from './browser'; -import { Log, InnerLogger, Logger, RootLogger } from './logger'; +import { InnerLogger, Logger } from './logger'; import { FunctionWithSource } from './frames'; import * as debugSupport from './debug/debugSupport'; @@ -53,7 +53,7 @@ export type BrowserContextOptions = CommonContextOptions & { logger?: Logger, }; -export interface BrowserContext extends InnerLogger { +export interface BrowserContext { setDefaultNavigationTimeout(timeout: number): void; setDefaultTimeout(timeout: number): void; pages(): Page[]; @@ -87,13 +87,13 @@ export abstract class BrowserContextBase extends ExtendedEventEmitter implements readonly _permissions = new Map(); readonly _downloads = new Set(); readonly _browserBase: BrowserBase; - private _logger: InnerLogger; + readonly _logger: InnerLogger; constructor(browserBase: BrowserBase, options: BrowserContextOptions) { super(); this._browserBase = browserBase; this._options = options; - this._logger = options.logger ? new RootLogger(options.logger) : browserBase; + this._logger = options.logger ? new InnerLogger(options.logger) : browserBase._options.logger; this._closePromise = new Promise(fulfill => this._closePromiseFulfill = fulfill); } @@ -188,14 +188,6 @@ export abstract class BrowserContextBase extends ExtendedEventEmitter implements this._timeoutSettings.setDefaultTimeout(timeout); } - _isLogEnabled(log: Log): boolean { - return this._logger._isLogEnabled(log); - } - - _log(log: Log, message: string | Error, ...args: any[]) { - return this._logger._log(log, message, ...args); - } - async _loadDefaultContext() { if (!this.pages().length) await this.waitForEvent('page'); diff --git a/src/chromium/crBrowser.ts b/src/chromium/crBrowser.ts index b7174be1ba..595dde1108 100644 --- a/src/chromium/crBrowser.ts +++ b/src/chromium/crBrowser.ts @@ -264,7 +264,7 @@ class CRServiceWorker extends Worker { readonly _browserContext: CRBrowserContext; constructor(browserContext: CRBrowserContext, session: CRSession, url: string) { - super(browserContext, url); + super(url); this._browserContext = browserContext; session.once('Runtime.executionContextCreated', event => { this._createExecutionContext(new CRExecutionContext(session, event.context)); diff --git a/src/chromium/crConnection.ts b/src/chromium/crConnection.ts index e0233601bd..dd33f32ec0 100644 --- a/src/chromium/crConnection.ts +++ b/src/chromium/crConnection.ts @@ -62,15 +62,15 @@ export class CRConnection extends EventEmitter { const message: ProtocolRequest = { id, method, params }; if (sessionId) message.sessionId = sessionId; - if (this._logger._isLogEnabled(protocolLog)) - this._logger._log(protocolLog, 'SEND ► ' + rewriteInjectedScriptEvaluationLog(message)); + if (this._logger.isLogEnabled(protocolLog)) + this._logger.log(protocolLog, 'SEND ► ' + rewriteInjectedScriptEvaluationLog(message)); this._transport.send(message); return id; } async _onMessage(message: ProtocolResponse) { - if (this._logger._isLogEnabled(protocolLog)) - this._logger._log(protocolLog, '◀ RECV ' + JSON.stringify(message)); + if (this._logger.isLogEnabled(protocolLog)) + this._logger.log(protocolLog, '◀ RECV ' + JSON.stringify(message)); if (message.id === kBrowserCloseMessageId) return; if (message.method === 'Target.attachedToTarget') { @@ -168,7 +168,7 @@ export class CRSession extends EventEmitter { _sendMayFail(method: T, params?: Protocol.CommandParameters[T]): Promise { return this.send(method, params).catch(error => { if (this._connection) - this._connection._logger._log(errorLog, error, []); + this._connection._logger.log(errorLog, error, []); }); } diff --git a/src/chromium/crPage.ts b/src/chromium/crPage.ts index 07c50a06d1..698d881535 100644 --- a/src/chromium/crPage.ts +++ b/src/chromium/crPage.ts @@ -566,7 +566,7 @@ class FrameSession { } const url = event.targetInfo.url; - const worker = new Worker(this._page, url); + const worker = new Worker(url); this._page._addWorker(event.sessionId, worker); session.once('Runtime.executionContextCreated', async event => { worker._createExecutionContext(new CRExecutionContext(session, event.context)); diff --git a/src/dom.ts b/src/dom.ts index c24fd5d63e..0e8903a756 100644 --- a/src/dom.ts +++ b/src/dom.ts @@ -28,7 +28,7 @@ import { Page } from './page'; import { selectors } from './selectors'; import * as types from './types'; import { NotConnectedError } from './errors'; -import { logError, apiLog } from './logger'; +import { apiLog } from './logger'; import { Progress, runAbortableTask } from './progress'; export type PointerActionOptions = { @@ -45,7 +45,7 @@ export class FrameExecutionContext extends js.ExecutionContext { private _injectedPromise?: Promise; constructor(delegate: js.ExecutionContextDelegate, frame: frames.Frame) { - super(delegate, frame._page); + super(delegate); this.frame = frame; } @@ -234,7 +234,7 @@ export class ElementHandle extends js.JSHandle { private async _offsetPoint(offset: types.Point): Promise { const [box, border] = await Promise.all([ this.boundingBox(), - this._evaluateInUtility(([injected, node]) => injected.getElementBorderWidth(node), {}).catch(logError(this._context._logger)), + this._evaluateInUtility(([injected, node]) => injected.getElementBorderWidth(node), {}).catch(e => {}), ]); if (!box || !border) return 'invisible'; @@ -317,7 +317,7 @@ export class ElementHandle extends js.JSHandle { } hover(options: PointerActionOptions & types.PointerActionWaitOptions = {}): Promise { - return runAbortableTask(progress => this._hover(progress, options), this._page, this._page._timeoutSettings.timeout(options)); + return runAbortableTask(progress => this._hover(progress, options), this._page._logger, this._page._timeoutSettings.timeout(options)); } _hover(progress: Progress, options: PointerActionOptions & types.PointerActionWaitOptions): Promise { @@ -325,7 +325,7 @@ export class ElementHandle extends js.JSHandle { } click(options: ClickOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions = {}): Promise { - return runAbortableTask(progress => this._click(progress, options), this._page, this._page._timeoutSettings.timeout(options)); + return runAbortableTask(progress => this._click(progress, options), this._page._logger, this._page._timeoutSettings.timeout(options)); } _click(progress: Progress, options: ClickOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions): Promise { @@ -333,7 +333,7 @@ export class ElementHandle extends js.JSHandle { } dblclick(options: MultiClickOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions = {}): Promise { - return runAbortableTask(progress => this._dblclick(progress, options), this._page, this._page._timeoutSettings.timeout(options)); + return runAbortableTask(progress => this._dblclick(progress, options), this._page._logger, this._page._timeoutSettings.timeout(options)); } _dblclick(progress: Progress, options: MultiClickOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions): Promise { @@ -341,7 +341,7 @@ export class ElementHandle extends js.JSHandle { } async selectOption(values: string | ElementHandle | types.SelectOption | string[] | ElementHandle[] | types.SelectOption[], options: types.NavigatingActionWaitOptions = {}): Promise { - return runAbortableTask(progress => this._selectOption(progress, values, options), this._page, this._page._timeoutSettings.timeout(options)); + return runAbortableTask(progress => this._selectOption(progress, values, options), this._page._logger, this._page._timeoutSettings.timeout(options)); } async _selectOption(progress: Progress, values: string | ElementHandle | types.SelectOption | string[] | ElementHandle[] | types.SelectOption[], options: types.NavigatingActionWaitOptions): Promise { @@ -368,7 +368,7 @@ export class ElementHandle extends js.JSHandle { } async fill(value: string, options: types.NavigatingActionWaitOptions = {}): Promise { - return runAbortableTask(progress => this._fill(progress, value, options), this._page, this._page._timeoutSettings.timeout(options)); + return runAbortableTask(progress => this._fill(progress, value, options), this._page._logger, this._page._timeoutSettings.timeout(options)); } async _fill(progress: Progress, value: string, options: types.NavigatingActionWaitOptions): Promise { @@ -391,13 +391,14 @@ export class ElementHandle extends js.JSHandle { } async selectText(): Promise { - this._page._log(apiLog, `elementHandle.selectText()`); - const injectedResult = await this._evaluateInUtility(([injected, node]) => injected.selectText(node), {}); - handleInjectedResult(injectedResult); + return runAbortableTask(async progress => { + const injectedResult = await this._evaluateInUtility(([injected, node]) => injected.selectText(node), {}); + handleInjectedResult(injectedResult); + }, this._page._logger, 0); } async setInputFiles(files: string | types.FilePayload | string[] | types.FilePayload[], options: types.NavigatingActionWaitOptions = {}) { - return runAbortableTask(async progress => this._setInputFiles(progress, files, options), this._page, this._page._timeoutSettings.timeout(options)); + return runAbortableTask(async progress => this._setInputFiles(progress, files, options), this._page._logger, this._page._timeoutSettings.timeout(options)); } async _setInputFiles(progress: Progress, files: string | types.FilePayload | string[] | types.FilePayload[], options: types.NavigatingActionWaitOptions) { @@ -435,31 +436,34 @@ export class ElementHandle extends js.JSHandle { } async focus() { - this._page._log(apiLog, `elementHandle.focus()`); + return runAbortableTask(progress => this._focus(progress), this._page._logger, 0); + } + + async _focus(progress: Progress) { const injectedResult = await this._evaluateInUtility(([injected, node]) => injected.focusNode(node), {}); handleInjectedResult(injectedResult); } async type(text: string, options: { delay?: number } & types.NavigatingActionWaitOptions = {}) { - return runAbortableTask(progress => this._type(progress, text, options), this._page, this._page._timeoutSettings.timeout(options)); + return runAbortableTask(progress => this._type(progress, text, options), this._page._logger, this._page._timeoutSettings.timeout(options)); } async _type(progress: Progress, text: string, options: { delay?: number } & types.NavigatingActionWaitOptions) { progress.log(apiLog, `elementHandle.type("${text}")`); return this._page._frameManager.waitForSignalsCreatedBy(progress, options.noWaitAfter, async () => { - await this.focus(); + await this._focus(progress); await this._page.keyboard.type(text, options); }, 'input'); } async press(key: string, options: { delay?: number } & types.NavigatingActionWaitOptions = {}) { - return runAbortableTask(progress => this._press(progress, key, options), this._page, this._page._timeoutSettings.timeout(options)); + return runAbortableTask(progress => this._press(progress, key, options), this._page._logger, this._page._timeoutSettings.timeout(options)); } async _press(progress: Progress, key: string, options: { delay?: number } & types.NavigatingActionWaitOptions) { progress.log(apiLog, `elementHandle.press("${key}")`); return this._page._frameManager.waitForSignalsCreatedBy(progress, options.noWaitAfter, async () => { - await this.focus(); + await this._focus(progress); await this._page.keyboard.press(key, options); }, 'input'); } @@ -468,14 +472,14 @@ export class ElementHandle extends js.JSHandle { return runAbortableTask(async progress => { progress.log(apiLog, `elementHandle.check()`); await this._setChecked(progress, true, options); - }, this._page, this._page._timeoutSettings.timeout(options)); + }, this._page._logger, this._page._timeoutSettings.timeout(options)); } async uncheck(options: types.PointerActionWaitOptions & types.NavigatingActionWaitOptions = {}) { return runAbortableTask(async progress => { progress.log(apiLog, `elementHandle.uncheck()`); await this._setChecked(progress, false, options); - }, this._page, this._page._timeoutSettings.timeout(options)); + }, this._page._logger, this._page._timeoutSettings.timeout(options)); } async _setChecked(progress: Progress, state: boolean, options: types.PointerActionWaitOptions & types.NavigatingActionWaitOptions) { diff --git a/src/firefox/ffConnection.ts b/src/firefox/ffConnection.ts index 35d98a7a7a..c9229c35a2 100644 --- a/src/firefox/ffConnection.ts +++ b/src/firefox/ffConnection.ts @@ -79,14 +79,14 @@ export class FFConnection extends EventEmitter { } _rawSend(message: ProtocolRequest) { - if (this._logger._isLogEnabled(protocolLog)) - this._logger._log(protocolLog, 'SEND ► ' + rewriteInjectedScriptEvaluationLog(message)); + if (this._logger.isLogEnabled(protocolLog)) + this._logger.log(protocolLog, 'SEND ► ' + rewriteInjectedScriptEvaluationLog(message)); this._transport.send(message); } async _onMessage(message: ProtocolResponse) { - if (this._logger._isLogEnabled(protocolLog)) - this._logger._log(protocolLog, '◀ RECV ' + JSON.stringify(message)); + if (this._logger.isLogEnabled(protocolLog)) + this._logger.log(protocolLog, '◀ RECV ' + JSON.stringify(message)); if (message.id === kBrowserCloseMessageId) return; if (message.sessionId) { @@ -187,7 +187,7 @@ export class FFSession extends EventEmitter { sendMayFail(method: T, params?: Protocol.CommandParameters[T]): Promise { return this.send(method, params).catch(error => { - this._connection._logger._log(errorLog, error, []); + this._connection._logger.log(errorLog, error, []); }); } diff --git a/src/firefox/ffPage.ts b/src/firefox/ffPage.ts index f3105c6320..76e6e43317 100644 --- a/src/firefox/ffPage.ts +++ b/src/firefox/ffPage.ts @@ -211,7 +211,7 @@ export class FFPage implements PageDelegate { async _onWorkerCreated(event: Protocol.Page.workerCreatedPayload) { const workerId = event.workerId; - const worker = new Worker(this._page, event.url); + const worker = new Worker(event.url); const workerSession = new FFSession(this._session._connection, 'worker', workerId, (message: any) => { this._session.send('Page.sendMessageToWorker', { frameId: event.frameId, diff --git a/src/frames.ts b/src/frames.ts index bb2ac21d09..82a6a6ebde 100644 --- a/src/frames.ts +++ b/src/frames.ts @@ -342,7 +342,7 @@ export class Frame { } async goto(url: string, options: GotoOptions = {}): Promise { - const progressController = new ProgressController(this._page, this._page._timeoutSettings.navigationTimeout(options)); + const progressController = new ProgressController(this._page._logger, this._page._timeoutSettings.navigationTimeout(options)); abortProgressOnFrameDetach(progressController, this); return progressController.run(async progress => { progress.log(apiLog, `navigating to "${url}", waiting until "${options.waitUntil || 'load'}"`); @@ -377,7 +377,7 @@ export class Frame { } async waitForNavigation(options: types.WaitForNavigationOptions = {}): Promise { - const progressController = new ProgressController(this._page, this._page._timeoutSettings.navigationTimeout(options)); + const progressController = new ProgressController(this._page._logger, this._page._timeoutSettings.navigationTimeout(options)); abortProgressOnFrameDetach(progressController, this); return progressController.run(async progress => { const toUrl = typeof options.url === 'string' ? ` to "${options.url}"` : ''; @@ -396,7 +396,7 @@ export class Frame { } async waitForLoadState(state: types.LifecycleEvent = 'load', options: types.TimeoutOptions = {}): Promise { - const progressController = new ProgressController(this._page, this._page._timeoutSettings.navigationTimeout(options)); + const progressController = new ProgressController(this._page._logger, this._page._timeoutSettings.navigationTimeout(options)); abortProgressOnFrameDetach(progressController, this); return progressController.run(progress => this._waitForLoadState(progress, state)); } @@ -469,7 +469,7 @@ export class Frame { return adopted; } return handle; - }, this._page, this._page._timeoutSettings.timeout(options)); + }, this._page._logger, this._page._timeoutSettings.timeout(options)); } async dispatchEvent(selector: string, type: string, eventInit?: Object, options: types.TimeoutOptions = {}): Promise { @@ -478,7 +478,7 @@ export class Frame { progress.log(apiLog, `Dispatching "${type}" event on selector "${selector}"...`); const result = await this._scheduleRerunnableTask(progress, 'main', task); result.dispose(); - }, this._page, this._page._timeoutSettings.timeout(options)); + }, this._page._logger, this._page._timeoutSettings.timeout(options)); } async $eval(selector: string, pageFunction: types.FuncOn, arg: Arg): Promise; @@ -520,7 +520,7 @@ export class Frame { } async setContent(html: string, options: types.NavigateOptions = {}): Promise { - const progressController = new ProgressController(this._page, this._page._timeoutSettings.navigationTimeout(options)); + const progressController = new ProgressController(this._page._logger, this._page._timeoutSettings.navigationTimeout(options)); abortProgressOnFrameDetach(progressController, this); return progressController.run(async progress => { const waitUntil = options.waitUntil === undefined ? 'load' : options.waitUntil; @@ -726,7 +726,7 @@ export class Frame { } } return undefined as any; - }, this._page, this._page._timeoutSettings.timeout(options)); + }, this._page._logger, this._page._timeoutSettings.timeout(options)); } async click(selector: string, options: dom.ClickOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions = {}) { @@ -742,7 +742,7 @@ export class Frame { } async focus(selector: string, options: types.TimeoutOptions = {}) { - await this._retryWithSelectorIfNotConnected(selector, options, (progress, handle) => handle.focus()); + await this._retryWithSelectorIfNotConnected(selector, options, (progress, handle) => handle._focus(progress)); } async textContent(selector: string, options: types.TimeoutOptions = {}): Promise { @@ -814,7 +814,7 @@ export class Frame { }; return runAbortableTask( progress => this._scheduleRerunnableTask(progress, 'main', task), - this._page, this._page._timeoutSettings.timeout(options)); + this._page._logger, this._page._timeoutSettings.timeout(options)); } async title(): Promise { diff --git a/src/hints.ts b/src/hints.ts index b0b72c7f8d..25107a90ca 100644 --- a/src/hints.ts +++ b/src/hints.ts @@ -27,7 +27,7 @@ export function waitForTimeoutWasUsed(page: Page) { if (waitForTimeoutWasUsedReported) return; waitForTimeoutWasUsedReported = true; - page._log(hintsLog, `WARNING: page.waitForTimeout(timeout) should only be used for debugging. + page._logger.log(hintsLog, `WARNING: page.waitForTimeout(timeout) should only be used for debugging. Tests using the timer in production are going to be flaky. Use signals such as network events, selectors becoming visible, etc. instead.`); } diff --git a/src/javascript.ts b/src/javascript.ts index c8d1c35143..405af1c783 100644 --- a/src/javascript.ts +++ b/src/javascript.ts @@ -17,7 +17,6 @@ import * as types from './types'; import * as dom from './dom'; import * as utilityScriptSource from './generated/utilityScriptSource'; -import { InnerLogger } from './logger'; import * as debugSupport from './debug/debugSupport'; import { serializeAsCallArgument } from './utilityScriptSerializers'; import { helper } from './helper'; @@ -38,12 +37,10 @@ export interface ExecutionContextDelegate { export class ExecutionContext { readonly _delegate: ExecutionContextDelegate; - readonly _logger: InnerLogger; private _utilityScriptPromise: Promise | undefined; - constructor(delegate: ExecutionContextDelegate, logger: InnerLogger) { + constructor(delegate: ExecutionContextDelegate) { this._delegate = delegate; - this._logger = logger; } adoptIfNeeded(handle: JSHandle): Promise | null { diff --git a/src/logger.ts b/src/logger.ts index f959b70321..99eb11a2a1 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -29,34 +29,35 @@ export interface Logger { log(name: string, severity: LoggerSeverity, message: string | Error, args: any[], hints: { color?: string }): void; } -export interface InnerLogger { - _isLogEnabled(log: Log): boolean; - _log(log: Log, message: string | Error, ...args: any[]): void; -} - export const errorLog: Log = { name: 'generic', severity: 'error' }; export const apiLog: Log = { name: 'api', color: 'cyan' }; export function logError(logger: InnerLogger): (error: Error) => void { - return error => logger._log(errorLog, error, []); + return error => logger.log(errorLog, error, []); } -export class RootLogger implements InnerLogger { - private _logger = new MultiplexingLogger(); +export class InnerLogger { + private _userSink: Logger | undefined; + private _debugSink: DebugLogger; constructor(userSink: Logger | undefined) { - if (userSink) - this._logger.add('user', userSink); - this._logger.add('debug', new DebugLogger()); + this._userSink = userSink; + this._debugSink = new DebugLogger(); } - _isLogEnabled(log: Log): boolean { - return this._logger.isEnabled(log.name, log.severity || 'info'); + isLogEnabled(log: Log): boolean { + const severity = log.severity || 'info'; + if (this._userSink && this._userSink.isEnabled(log.name, severity)) + return true; + return this._debugSink.isEnabled(log.name, severity); } - _log(log: Log, message: string | Error, ...args: any[]) { - if (this._logger.isEnabled(log.name, log.severity || 'info')) - this._logger.log(log.name, log.severity || 'info', message, args, log.color ? { color: log.color } : {}); + log(log: Log, message: string | Error, ...args: any[]) { + const severity = log.severity || 'info'; + const hints = log.color ? { color: log.color } : {}; + if (this._userSink && this._userSink.isEnabled(log.name, severity)) + this._userSink.log(log.name, severity, message, args, hints); + this._debugSink.log(log.name, severity, message, args, hints); } } @@ -70,36 +71,7 @@ const colorMap = new Map([ ['reset', 0], ]); -class MultiplexingLogger implements Logger { - private _loggers = new Map(); - - add(id: string, logger: Logger) { - this._loggers.set(id, logger); - } - - get(id: string): Logger | 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()) - logger.log(name, severity, message, args, hints); - } -} - -class DebugLogger implements Logger { +class DebugLogger { private _debuggers = new Map(); isEnabled(name: string, severity: LoggerSeverity): boolean { diff --git a/src/page.ts b/src/page.ts index 59488fcaba..c451590609 100644 --- a/src/page.ts +++ b/src/page.ts @@ -31,7 +31,7 @@ import * as accessibility from './accessibility'; import { ExtendedEventEmitter } from './extendedEventEmitter'; import { EventEmitter } from 'events'; import { FileChooser } from './fileChooser'; -import { logError, InnerLogger, Log } from './logger'; +import { logError, InnerLogger } from './logger'; export interface PageDelegate { readonly rawMouse: input.RawMouse; @@ -88,7 +88,7 @@ type PageState = { extraHTTPHeaders: network.Headers | null; }; -export class Page extends ExtendedEventEmitter implements InnerLogger { +export class Page extends ExtendedEventEmitter { private _closed = false; private _closedCallback: () => void; private _closedPromise: Promise; @@ -100,6 +100,7 @@ export class Page extends ExtendedEventEmitter implements InnerLogger { readonly mouse: input.Mouse; readonly _timeoutSettings: TimeoutSettings; readonly _delegate: PageDelegate; + readonly _logger: InnerLogger; readonly _state: PageState; readonly _pageBindings = new Map(); readonly _screenshotter: Screenshotter; @@ -114,6 +115,7 @@ export class Page extends ExtendedEventEmitter implements InnerLogger { constructor(delegate: PageDelegate, browserContext: BrowserContextBase) { super(); this._delegate = delegate; + this._logger = browserContext._logger; this._closedCallback = () => {}; this._closedPromise = new Promise(f => this._closedCallback = f); this._disconnectedCallback = () => {}; @@ -141,7 +143,7 @@ export class Page extends ExtendedEventEmitter implements InnerLogger { } protected _getLogger(): InnerLogger { - return this; + return this._logger; } protected _getTimeoutSettings(): TimeoutSettings { @@ -552,33 +554,23 @@ export class Page extends ExtendedEventEmitter implements InnerLogger { this._delegate.setFileChooserIntercepted(false); return this; } - - _isLogEnabled(log: Log): boolean { - return this._browserContext._isLogEnabled(log); - } - - _log(log: Log, message: string | Error, ...args: any[]) { - return this._browserContext._log(log, message, ...args); - } } export class Worker extends EventEmitter { - private _logger: InnerLogger; private _url: string; private _executionContextPromise: Promise; private _executionContextCallback: (value?: js.ExecutionContext) => void; _existingExecutionContext: js.ExecutionContext | null = null; - constructor(logger: InnerLogger, url: string) { + constructor(url: string) { super(); - this._logger = logger; this._url = url; this._executionContextCallback = () => {}; this._executionContextPromise = new Promise(x => this._executionContextCallback = x); } _createExecutionContext(delegate: js.ExecutionContextDelegate) { - this._existingExecutionContext = new js.ExecutionContext(delegate, this._logger); + this._existingExecutionContext = new js.ExecutionContext(delegate); this._executionContextCallback(this._existingExecutionContext); } @@ -627,7 +619,7 @@ export class PageBinding { else expression = helper.evaluationString(deliverErrorValue, name, seq, error); } - context.evaluateInternal(expression).catch(logError(page)); + context.evaluateInternal(expression).catch(logError(page._logger)); function deliverResult(name: string, seq: number, result: any) { (window as any)[name]['callbacks'].get(seq).resolve(result); diff --git a/src/progress.ts b/src/progress.ts index da7c444042..caf26966e1 100644 --- a/src/progress.ts +++ b/src/progress.ts @@ -84,13 +84,13 @@ export class ProgressController { log: (log: Log, message: string | Error) => { if (this._state === 'running') { this._logRecording.push(message.toString()); - this._logger._log(log, ' ' + message); + this._logger.log(log, ' ' + message); } else { - this._logger._log(log, message); + this._logger.log(log, message); } }, }; - this._logger._log(apiLog, `=> ${this._apiName} started`); + this._logger.log(apiLog, `=> ${this._apiName} started`); const timeoutError = new TimeoutError(`Timeout ${this._timeout}ms exceeded during ${this._apiName}.`); const timer = setTimeout(() => this._forceAbort(timeoutError), progress.timeUntilDeadline()); @@ -100,7 +100,7 @@ export class ProgressController { clearTimeout(timer); this._state = 'finished'; this._logRecording = []; - this._logger._log(apiLog, `<= ${this._apiName} succeeded`); + this._logger.log(apiLog, `<= ${this._apiName} succeeded`); return result; } catch (e) { this._aborted(); @@ -108,7 +108,7 @@ export class ProgressController { clearTimeout(timer); this._state = 'aborted'; this._logRecording = []; - this._logger._log(apiLog, `<= ${this._apiName} failed`); + this._logger.log(apiLog, `<= ${this._apiName} failed`); await Promise.all(this._cleanups.splice(0).map(cleanup => runCleanup(cleanup))); throw e; } diff --git a/src/server/browserType.ts b/src/server/browserType.ts index a9ba96f80b..737a101a08 100644 --- a/src/server/browserType.ts +++ b/src/server/browserType.ts @@ -21,7 +21,7 @@ import * as util from 'util'; import { BrowserContext, PersistentContextOptions, verifyProxySettings, validateBrowserContextOptions } from '../browserContext'; import { BrowserServer, WebSocketWrapper } from './browserServer'; import * as browserPaths from '../install/browserPaths'; -import { Logger, RootLogger, InnerLogger } from '../logger'; +import { Logger, InnerLogger } from '../logger'; import { ConnectionTransport, WebSocketTransport } from '../transport'; import { BrowserBase, BrowserOptions, Browser } from '../browser'; import { assert } from '../helper'; @@ -108,7 +108,7 @@ export abstract class BrowserTypeBase implements BrowserType { async launch(options: LaunchOptions = {}): Promise { 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.'); - const logger = new RootLogger(options.logger); + const logger = new InnerLogger(options.logger); const browser = await runAbortableTask(progress => this._innerLaunch(progress, options, logger, undefined), logger, TimeoutSettings.timeout(options)); return browser; } @@ -116,12 +116,12 @@ export abstract class BrowserTypeBase implements BrowserType { async launchPersistentContext(userDataDir: string, options: LaunchOptions & PersistentContextOptions = {}): Promise { assert(!(options as any).port, 'Cannot specify a port without launching as a server.'); const persistent = validateBrowserContextOptions(options); - const logger = new RootLogger(options.logger); + const logger = new InnerLogger(options.logger); const browser = await runAbortableTask(progress => this._innerLaunch(progress, options, logger, persistent, userDataDir), logger, TimeoutSettings.timeout(options)); return browser._defaultContext!; } - async _innerLaunch(progress: Progress, options: LaunchOptions, logger: RootLogger, persistent: PersistentContextOptions | undefined, userDataDir?: string): Promise { + async _innerLaunch(progress: Progress, options: LaunchOptions, logger: InnerLogger, persistent: PersistentContextOptions | undefined, userDataDir?: string): Promise { options.proxy = options.proxy ? verifyProxySettings(options.proxy) : undefined; const { browserServer, downloadsPath, transport } = await this._launchServer(progress, options, !!persistent, logger, userDataDir); if ((options as any).__testHookBeforeCreateBrowser) @@ -147,7 +147,7 @@ export abstract class BrowserTypeBase implements BrowserType { async launchServer(options: LaunchServerOptions = {}): Promise { assert(!(options as any).userDataDir, 'userDataDir option is not supported in `browserType.launchServer`. Use `browserType.launchPersistentContext` instead'); const { port = 0 } = options; - const logger = new RootLogger(options.logger); + const logger = new InnerLogger(options.logger); return runAbortableTask(async progress => { const { browserServer, transport } = await this._launchServer(progress, options, false, logger); browserServer._webSocketWrapper = this._wrapTransportWithWebSocket(transport, logger, port); @@ -156,7 +156,7 @@ export abstract class BrowserTypeBase implements BrowserType { } async connect(options: ConnectOptions): Promise { - const logger = new RootLogger(options.logger); + const logger = new InnerLogger(options.logger); return runAbortableTask(async progress => { const transport = await WebSocketTransport.connect(progress, options.wsEndpoint); progress.cleanupWhenAborted(() => transport.closeAndWait()); @@ -167,7 +167,7 @@ export abstract class BrowserTypeBase implements BrowserType { }, logger, TimeoutSettings.timeout(options)); } - private async _launchServer(progress: Progress, options: LaunchServerOptions, isPersistent: boolean, logger: RootLogger, userDataDir?: string): Promise<{ browserServer: BrowserServer, downloadsPath: string, transport: ConnectionTransport }> { + private async _launchServer(progress: Progress, options: LaunchServerOptions, isPersistent: boolean, logger: InnerLogger, userDataDir?: string): Promise<{ browserServer: BrowserServer, downloadsPath: string, transport: ConnectionTransport }> { const { ignoreDefaultArgs = false, args = [], diff --git a/src/server/electron.ts b/src/server/electron.ts index 0c654a3cec..dca1779311 100644 --- a/src/server/electron.ts +++ b/src/server/electron.ts @@ -21,7 +21,7 @@ import { CRExecutionContext } from '../chromium/crExecutionContext'; import { Events } from '../events'; import { ExtendedEventEmitter } from '../extendedEventEmitter'; import * as js from '../javascript'; -import { InnerLogger, Logger, RootLogger } from '../logger'; +import { InnerLogger, Logger } from '../logger'; import { Page } from '../page'; import { TimeoutSettings } from '../timeoutSettings'; import { WebSocketTransport } from '../transport'; @@ -130,7 +130,7 @@ export class ElectronApplication extends ExtendedEventEmitter { async _init() { this._nodeSession.once('Runtime.executionContextCreated', event => { - this._nodeExecutionContext = new js.ExecutionContext(new CRExecutionContext(this._nodeSession, event.context), this._logger); + this._nodeExecutionContext = new js.ExecutionContext(new CRExecutionContext(this._nodeSession, event.context)); }); await this._nodeSession.send('Runtime.enable', {}).catch(e => {}); this._nodeElectronHandle = await js.evaluate(this._nodeExecutionContext!, false /* returnByValue */, () => { @@ -171,7 +171,7 @@ export class Electron { handleSIGTERM = true, handleSIGHUP = true, } = options; - const logger = new RootLogger(options.logger); + const logger = new InnerLogger(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]; diff --git a/src/webkit/wkConnection.ts b/src/webkit/wkConnection.ts index dab4086417..103970914f 100644 --- a/src/webkit/wkConnection.ts +++ b/src/webkit/wkConnection.ts @@ -55,14 +55,14 @@ export class WKConnection { } rawSend(message: ProtocolRequest) { - if (this._logger._isLogEnabled(protocolLog)) - this._logger._log(protocolLog, 'SEND ► ' + rewriteInjectedScriptEvaluationLog(message)); + if (this._logger.isLogEnabled(protocolLog)) + this._logger.log(protocolLog, 'SEND ► ' + rewriteInjectedScriptEvaluationLog(message)); this._transport.send(message); } private _dispatchMessage(message: ProtocolResponse) { - if (this._logger._isLogEnabled(protocolLog)) - this._logger._log(protocolLog, '◀ RECV ' + JSON.stringify(message)); + if (this._logger.isLogEnabled(protocolLog)) + this._logger.log(protocolLog, '◀ RECV ' + JSON.stringify(message)); if (message.id === kBrowserCloseMessageId) return; if (message.pageProxyId) { @@ -139,7 +139,7 @@ export class WKSession extends EventEmitter { sendMayFail(method: T, params?: Protocol.CommandParameters[T]): Promise { return this.send(method, params).catch(error => { - this.connection._logger._log(errorLog, error, []); + this.connection._logger.log(errorLog, error, []); }); } diff --git a/src/webkit/wkPage.ts b/src/webkit/wkPage.ts index 23580631f5..ebaf458a2b 100644 --- a/src/webkit/wkPage.ts +++ b/src/webkit/wkPage.ts @@ -37,7 +37,6 @@ import { selectors } from '../selectors'; import * as jpeg from 'jpeg-js'; import * as png from 'pngjs'; import { NotConnectedError } from '../errors'; -import { logError } from '../logger'; import { ConsoleMessageLocation } from '../console'; import { JSHandle } from '../javascript'; @@ -364,7 +363,7 @@ export class WKPage implements PageDelegate { // as well to always be in sync with the backend. if (this._provisionalPage) sessions.push(this._provisionalPage._session); - await Promise.all(sessions.map(session => callback(session).catch(logError(this._page)))); + await Promise.all(sessions.map(session => callback(session).catch(e => {}))); } private _onFrameScheduledNavigation(frameId: string) { diff --git a/src/webkit/wkWorkers.ts b/src/webkit/wkWorkers.ts index ba11d078cc..3bd6fe4e56 100644 --- a/src/webkit/wkWorkers.ts +++ b/src/webkit/wkWorkers.ts @@ -34,7 +34,7 @@ export class WKWorkers { this.clear(); this._sessionListeners = [ helper.addEventListener(session, 'Worker.workerCreated', (event: Protocol.Worker.workerCreatedPayload) => { - const worker = new Worker(this._page, event.url); + const worker = new Worker(event.url); const workerSession = new WKSession(session.connection, event.workerId, 'Most likely the worker has been closed.', (message: any) => { session.send('Worker.sendMessageToWorker', { workerId: event.workerId, diff --git a/test/elementhandle.spec.js b/test/elementhandle.spec.js index 0785f02386..4cbc278182 100644 --- a/test/elementhandle.spec.js +++ b/test/elementhandle.spec.js @@ -426,3 +426,13 @@ describe('ElementHandle.selectOption', function() { expect(await page.evaluate(() => result.onChange)).toEqual(['blue']); }); }); + +describe('ElementHandle.focus', function() { + it('should focus a button', async({page, server}) => { + await page.goto(server.PREFIX + '/input/button.html'); + const button = await page.$('button'); + expect(await button.evaluate(button => document.activeElement === button)).toBe(false); + await button.focus(); + expect(await button.evaluate(button => document.activeElement === button)).toBe(true); + }); +});