chore: simplify logging a bit (#2512)

This merges a few classes and stops inheriting from InnerLogger.
This commit is contained in:
Dmitry Gozman 2020-06-09 16:11:17 -07:00 committed by GitHub
parent 492a65f9d3
commit 80705ff5e9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 111 additions and 153 deletions

View file

@ -20,7 +20,7 @@ import { EventEmitter } from 'events';
import { Download } from './download'; import { Download } from './download';
import type { BrowserServer } from './server/browserServer'; import type { BrowserServer } from './server/browserServer';
import { Events } from './events'; import { Events } from './events';
import { InnerLogger, Log } from './logger'; import { InnerLogger } from './logger';
import { ProxySettings } from './types'; import { ProxySettings } from './types';
export type BrowserOptions = { export type BrowserOptions = {
@ -41,7 +41,7 @@ export interface Browser extends EventEmitter {
close(): Promise<void>; close(): Promise<void>;
} }
export abstract class BrowserBase extends EventEmitter implements Browser, InnerLogger { export abstract class BrowserBase extends EventEmitter implements Browser {
readonly _options: BrowserOptions; readonly _options: BrowserOptions;
private _downloads = new Map<string, Download>(); private _downloads = new Map<string, Download>();
_defaultContext: BrowserContextBase | null = null; _defaultContext: BrowserContextBase | null = null;
@ -93,13 +93,5 @@ export abstract class BrowserBase extends EventEmitter implements Browser, Inner
if (this.isConnected()) if (this.isConnected())
await new Promise(x => this.once(Events.Browser.Disconnected, x)); 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);
}
} }

View file

@ -24,7 +24,7 @@ import { Events } from './events';
import { ExtendedEventEmitter } from './extendedEventEmitter'; import { ExtendedEventEmitter } from './extendedEventEmitter';
import { Download } from './download'; import { Download } from './download';
import { BrowserBase } from './browser'; import { BrowserBase } from './browser';
import { Log, InnerLogger, Logger, RootLogger } from './logger'; import { InnerLogger, Logger } from './logger';
import { FunctionWithSource } from './frames'; import { FunctionWithSource } from './frames';
import * as debugSupport from './debug/debugSupport'; import * as debugSupport from './debug/debugSupport';
@ -53,7 +53,7 @@ export type BrowserContextOptions = CommonContextOptions & {
logger?: Logger, logger?: Logger,
}; };
export interface BrowserContext extends InnerLogger { export interface BrowserContext {
setDefaultNavigationTimeout(timeout: number): void; setDefaultNavigationTimeout(timeout: number): void;
setDefaultTimeout(timeout: number): void; setDefaultTimeout(timeout: number): void;
pages(): Page[]; pages(): Page[];
@ -87,13 +87,13 @@ export abstract class BrowserContextBase extends ExtendedEventEmitter implements
readonly _permissions = new Map<string, string[]>(); readonly _permissions = new Map<string, string[]>();
readonly _downloads = new Set<Download>(); readonly _downloads = new Set<Download>();
readonly _browserBase: BrowserBase; readonly _browserBase: BrowserBase;
private _logger: InnerLogger; readonly _logger: InnerLogger;
constructor(browserBase: BrowserBase, options: BrowserContextOptions) { constructor(browserBase: BrowserBase, options: BrowserContextOptions) {
super(); super();
this._browserBase = browserBase; this._browserBase = browserBase;
this._options = options; 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); this._closePromise = new Promise(fulfill => this._closePromiseFulfill = fulfill);
} }
@ -188,14 +188,6 @@ export abstract class BrowserContextBase extends ExtendedEventEmitter implements
this._timeoutSettings.setDefaultTimeout(timeout); 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() { async _loadDefaultContext() {
if (!this.pages().length) if (!this.pages().length)
await this.waitForEvent('page'); await this.waitForEvent('page');

View file

@ -264,7 +264,7 @@ class CRServiceWorker extends Worker {
readonly _browserContext: CRBrowserContext; readonly _browserContext: CRBrowserContext;
constructor(browserContext: CRBrowserContext, session: CRSession, url: string) { constructor(browserContext: CRBrowserContext, session: CRSession, url: string) {
super(browserContext, url); super(url);
this._browserContext = browserContext; this._browserContext = browserContext;
session.once('Runtime.executionContextCreated', event => { session.once('Runtime.executionContextCreated', event => {
this._createExecutionContext(new CRExecutionContext(session, event.context)); this._createExecutionContext(new CRExecutionContext(session, event.context));

View file

@ -62,15 +62,15 @@ export class CRConnection extends EventEmitter {
const message: ProtocolRequest = { id, method, params }; const message: ProtocolRequest = { id, method, params };
if (sessionId) if (sessionId)
message.sessionId = sessionId; message.sessionId = sessionId;
if (this._logger._isLogEnabled(protocolLog)) if (this._logger.isLogEnabled(protocolLog))
this._logger._log(protocolLog, 'SEND ► ' + rewriteInjectedScriptEvaluationLog(message)); this._logger.log(protocolLog, 'SEND ► ' + rewriteInjectedScriptEvaluationLog(message));
this._transport.send(message); this._transport.send(message);
return id; return id;
} }
async _onMessage(message: ProtocolResponse) { async _onMessage(message: ProtocolResponse) {
if (this._logger._isLogEnabled(protocolLog)) if (this._logger.isLogEnabled(protocolLog))
this._logger._log(protocolLog, '◀ RECV ' + JSON.stringify(message)); this._logger.log(protocolLog, '◀ RECV ' + JSON.stringify(message));
if (message.id === kBrowserCloseMessageId) if (message.id === kBrowserCloseMessageId)
return; return;
if (message.method === 'Target.attachedToTarget') { if (message.method === 'Target.attachedToTarget') {
@ -168,7 +168,7 @@ export class CRSession extends EventEmitter {
_sendMayFail<T extends keyof Protocol.CommandParameters>(method: T, params?: Protocol.CommandParameters[T]): Promise<Protocol.CommandReturnValues[T] | void> { _sendMayFail<T extends keyof Protocol.CommandParameters>(method: T, params?: Protocol.CommandParameters[T]): Promise<Protocol.CommandReturnValues[T] | void> {
return this.send(method, params).catch(error => { return this.send(method, params).catch(error => {
if (this._connection) if (this._connection)
this._connection._logger._log(errorLog, error, []); this._connection._logger.log(errorLog, error, []);
}); });
} }

View file

@ -566,7 +566,7 @@ class FrameSession {
} }
const url = event.targetInfo.url; const url = event.targetInfo.url;
const worker = new Worker(this._page, url); const worker = new Worker(url);
this._page._addWorker(event.sessionId, worker); this._page._addWorker(event.sessionId, worker);
session.once('Runtime.executionContextCreated', async event => { session.once('Runtime.executionContextCreated', async event => {
worker._createExecutionContext(new CRExecutionContext(session, event.context)); worker._createExecutionContext(new CRExecutionContext(session, event.context));

View file

@ -28,7 +28,7 @@ import { Page } from './page';
import { selectors } from './selectors'; import { selectors } from './selectors';
import * as types from './types'; import * as types from './types';
import { NotConnectedError } from './errors'; import { NotConnectedError } from './errors';
import { logError, apiLog } from './logger'; import { apiLog } from './logger';
import { Progress, runAbortableTask } from './progress'; import { Progress, runAbortableTask } from './progress';
export type PointerActionOptions = { export type PointerActionOptions = {
@ -45,7 +45,7 @@ export class FrameExecutionContext extends js.ExecutionContext {
private _injectedPromise?: Promise<js.JSHandle>; private _injectedPromise?: Promise<js.JSHandle>;
constructor(delegate: js.ExecutionContextDelegate, frame: frames.Frame) { constructor(delegate: js.ExecutionContextDelegate, frame: frames.Frame) {
super(delegate, frame._page); super(delegate);
this.frame = frame; this.frame = frame;
} }
@ -234,7 +234,7 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
private async _offsetPoint(offset: types.Point): Promise<types.Point | 'invisible'> { private async _offsetPoint(offset: types.Point): Promise<types.Point | 'invisible'> {
const [box, border] = await Promise.all([ const [box, border] = await Promise.all([
this.boundingBox(), 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) if (!box || !border)
return 'invisible'; return 'invisible';
@ -317,7 +317,7 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
} }
hover(options: PointerActionOptions & types.PointerActionWaitOptions = {}): Promise<void> { hover(options: PointerActionOptions & types.PointerActionWaitOptions = {}): Promise<void> {
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<void> { _hover(progress: Progress, options: PointerActionOptions & types.PointerActionWaitOptions): Promise<void> {
@ -325,7 +325,7 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
} }
click(options: ClickOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions = {}): Promise<void> { click(options: ClickOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions = {}): Promise<void> {
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<void> { _click(progress: Progress, options: ClickOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions): Promise<void> {
@ -333,7 +333,7 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
} }
dblclick(options: MultiClickOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions = {}): Promise<void> { dblclick(options: MultiClickOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions = {}): Promise<void> {
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<void> { _dblclick(progress: Progress, options: MultiClickOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions): Promise<void> {
@ -341,7 +341,7 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
} }
async selectOption(values: string | ElementHandle | types.SelectOption | string[] | ElementHandle[] | types.SelectOption[], options: types.NavigatingActionWaitOptions = {}): Promise<string[]> { async selectOption(values: string | ElementHandle | types.SelectOption | string[] | ElementHandle[] | types.SelectOption[], options: types.NavigatingActionWaitOptions = {}): Promise<string[]> {
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<string[]> { async _selectOption(progress: Progress, values: string | ElementHandle | types.SelectOption | string[] | ElementHandle[] | types.SelectOption[], options: types.NavigatingActionWaitOptions): Promise<string[]> {
@ -368,7 +368,7 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
} }
async fill(value: string, options: types.NavigatingActionWaitOptions = {}): Promise<void> { async fill(value: string, options: types.NavigatingActionWaitOptions = {}): Promise<void> {
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<void> { async _fill(progress: Progress, value: string, options: types.NavigatingActionWaitOptions): Promise<void> {
@ -391,13 +391,14 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
} }
async selectText(): Promise<void> { async selectText(): Promise<void> {
this._page._log(apiLog, `elementHandle.selectText()`); return runAbortableTask(async progress => {
const injectedResult = await this._evaluateInUtility(([injected, node]) => injected.selectText(node), {}); const injectedResult = await this._evaluateInUtility(([injected, node]) => injected.selectText(node), {});
handleInjectedResult(injectedResult); handleInjectedResult(injectedResult);
}, this._page._logger, 0);
} }
async setInputFiles(files: string | types.FilePayload | string[] | types.FilePayload[], options: types.NavigatingActionWaitOptions = {}) { 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) { async _setInputFiles(progress: Progress, files: string | types.FilePayload | string[] | types.FilePayload[], options: types.NavigatingActionWaitOptions) {
@ -435,31 +436,34 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
} }
async focus() { 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), {}); const injectedResult = await this._evaluateInUtility(([injected, node]) => injected.focusNode(node), {});
handleInjectedResult(injectedResult); handleInjectedResult(injectedResult);
} }
async type(text: string, options: { delay?: number } & types.NavigatingActionWaitOptions = {}) { 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) { async _type(progress: Progress, text: string, options: { delay?: number } & types.NavigatingActionWaitOptions) {
progress.log(apiLog, `elementHandle.type("${text}")`); progress.log(apiLog, `elementHandle.type("${text}")`);
return this._page._frameManager.waitForSignalsCreatedBy(progress, options.noWaitAfter, async () => { return this._page._frameManager.waitForSignalsCreatedBy(progress, options.noWaitAfter, async () => {
await this.focus(); await this._focus(progress);
await this._page.keyboard.type(text, options); await this._page.keyboard.type(text, options);
}, 'input'); }, 'input');
} }
async press(key: string, options: { delay?: number } & types.NavigatingActionWaitOptions = {}) { 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) { async _press(progress: Progress, key: string, options: { delay?: number } & types.NavigatingActionWaitOptions) {
progress.log(apiLog, `elementHandle.press("${key}")`); progress.log(apiLog, `elementHandle.press("${key}")`);
return this._page._frameManager.waitForSignalsCreatedBy(progress, options.noWaitAfter, async () => { return this._page._frameManager.waitForSignalsCreatedBy(progress, options.noWaitAfter, async () => {
await this.focus(); await this._focus(progress);
await this._page.keyboard.press(key, options); await this._page.keyboard.press(key, options);
}, 'input'); }, 'input');
} }
@ -468,14 +472,14 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
return runAbortableTask(async progress => { return runAbortableTask(async progress => {
progress.log(apiLog, `elementHandle.check()`); progress.log(apiLog, `elementHandle.check()`);
await this._setChecked(progress, true, options); 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 = {}) { async uncheck(options: types.PointerActionWaitOptions & types.NavigatingActionWaitOptions = {}) {
return runAbortableTask(async progress => { return runAbortableTask(async progress => {
progress.log(apiLog, `elementHandle.uncheck()`); progress.log(apiLog, `elementHandle.uncheck()`);
await this._setChecked(progress, false, options); 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) { async _setChecked(progress: Progress, state: boolean, options: types.PointerActionWaitOptions & types.NavigatingActionWaitOptions) {

View file

@ -79,14 +79,14 @@ export class FFConnection extends EventEmitter {
} }
_rawSend(message: ProtocolRequest) { _rawSend(message: ProtocolRequest) {
if (this._logger._isLogEnabled(protocolLog)) if (this._logger.isLogEnabled(protocolLog))
this._logger._log(protocolLog, 'SEND ► ' + rewriteInjectedScriptEvaluationLog(message)); this._logger.log(protocolLog, 'SEND ► ' + rewriteInjectedScriptEvaluationLog(message));
this._transport.send(message); this._transport.send(message);
} }
async _onMessage(message: ProtocolResponse) { async _onMessage(message: ProtocolResponse) {
if (this._logger._isLogEnabled(protocolLog)) if (this._logger.isLogEnabled(protocolLog))
this._logger._log(protocolLog, '◀ RECV ' + JSON.stringify(message)); this._logger.log(protocolLog, '◀ RECV ' + JSON.stringify(message));
if (message.id === kBrowserCloseMessageId) if (message.id === kBrowserCloseMessageId)
return; return;
if (message.sessionId) { if (message.sessionId) {
@ -187,7 +187,7 @@ export class FFSession extends EventEmitter {
sendMayFail<T extends keyof Protocol.CommandParameters>(method: T, params?: Protocol.CommandParameters[T]): Promise<Protocol.CommandReturnValues[T] | void> { sendMayFail<T extends keyof Protocol.CommandParameters>(method: T, params?: Protocol.CommandParameters[T]): Promise<Protocol.CommandReturnValues[T] | void> {
return this.send(method, params).catch(error => { return this.send(method, params).catch(error => {
this._connection._logger._log(errorLog, error, []); this._connection._logger.log(errorLog, error, []);
}); });
} }

View file

@ -211,7 +211,7 @@ export class FFPage implements PageDelegate {
async _onWorkerCreated(event: Protocol.Page.workerCreatedPayload) { async _onWorkerCreated(event: Protocol.Page.workerCreatedPayload) {
const workerId = event.workerId; 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) => { const workerSession = new FFSession(this._session._connection, 'worker', workerId, (message: any) => {
this._session.send('Page.sendMessageToWorker', { this._session.send('Page.sendMessageToWorker', {
frameId: event.frameId, frameId: event.frameId,

View file

@ -342,7 +342,7 @@ export class Frame {
} }
async goto(url: string, options: GotoOptions = {}): Promise<network.Response | null> { async goto(url: string, options: GotoOptions = {}): Promise<network.Response | null> {
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); abortProgressOnFrameDetach(progressController, this);
return progressController.run(async progress => { return progressController.run(async progress => {
progress.log(apiLog, `navigating to "${url}", waiting until "${options.waitUntil || 'load'}"`); progress.log(apiLog, `navigating to "${url}", waiting until "${options.waitUntil || 'load'}"`);
@ -377,7 +377,7 @@ export class Frame {
} }
async waitForNavigation(options: types.WaitForNavigationOptions = {}): Promise<network.Response | null> { async waitForNavigation(options: types.WaitForNavigationOptions = {}): Promise<network.Response | null> {
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); abortProgressOnFrameDetach(progressController, this);
return progressController.run(async progress => { return progressController.run(async progress => {
const toUrl = typeof options.url === 'string' ? ` to "${options.url}"` : ''; 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<void> { async waitForLoadState(state: types.LifecycleEvent = 'load', options: types.TimeoutOptions = {}): Promise<void> {
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); abortProgressOnFrameDetach(progressController, this);
return progressController.run(progress => this._waitForLoadState(progress, state)); return progressController.run(progress => this._waitForLoadState(progress, state));
} }
@ -469,7 +469,7 @@ export class Frame {
return adopted; return adopted;
} }
return handle; 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<void> { async dispatchEvent(selector: string, type: string, eventInit?: Object, options: types.TimeoutOptions = {}): Promise<void> {
@ -478,7 +478,7 @@ export class Frame {
progress.log(apiLog, `Dispatching "${type}" event on selector "${selector}"...`); progress.log(apiLog, `Dispatching "${type}" event on selector "${selector}"...`);
const result = await this._scheduleRerunnableTask(progress, 'main', task); const result = await this._scheduleRerunnableTask(progress, 'main', task);
result.dispose(); result.dispose();
}, this._page, this._page._timeoutSettings.timeout(options)); }, this._page._logger, this._page._timeoutSettings.timeout(options));
} }
async $eval<R, Arg>(selector: string, pageFunction: types.FuncOn<Element, Arg, R>, arg: Arg): Promise<R>; async $eval<R, Arg>(selector: string, pageFunction: types.FuncOn<Element, Arg, R>, arg: Arg): Promise<R>;
@ -520,7 +520,7 @@ export class Frame {
} }
async setContent(html: string, options: types.NavigateOptions = {}): Promise<void> { async setContent(html: string, options: types.NavigateOptions = {}): Promise<void> {
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); abortProgressOnFrameDetach(progressController, this);
return progressController.run(async progress => { return progressController.run(async progress => {
const waitUntil = options.waitUntil === undefined ? 'load' : options.waitUntil; const waitUntil = options.waitUntil === undefined ? 'load' : options.waitUntil;
@ -726,7 +726,7 @@ export class Frame {
} }
} }
return undefined as any; 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 = {}) { 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 = {}) { 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<null|string> { async textContent(selector: string, options: types.TimeoutOptions = {}): Promise<null|string> {
@ -814,7 +814,7 @@ export class Frame {
}; };
return runAbortableTask( return runAbortableTask(
progress => this._scheduleRerunnableTask(progress, 'main', task), progress => this._scheduleRerunnableTask(progress, 'main', task),
this._page, this._page._timeoutSettings.timeout(options)); this._page._logger, this._page._timeoutSettings.timeout(options));
} }
async title(): Promise<string> { async title(): Promise<string> {

View file

@ -27,7 +27,7 @@ export function waitForTimeoutWasUsed(page: Page) {
if (waitForTimeoutWasUsedReported) if (waitForTimeoutWasUsedReported)
return; return;
waitForTimeoutWasUsedReported = true; 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. Tests using the timer in production are going to be flaky.
Use signals such as network events, selectors becoming visible, etc. instead.`); Use signals such as network events, selectors becoming visible, etc. instead.`);
} }

View file

@ -17,7 +17,6 @@
import * as types from './types'; import * as types from './types';
import * as dom from './dom'; import * as dom from './dom';
import * as utilityScriptSource from './generated/utilityScriptSource'; import * as utilityScriptSource from './generated/utilityScriptSource';
import { InnerLogger } from './logger';
import * as debugSupport from './debug/debugSupport'; import * as debugSupport from './debug/debugSupport';
import { serializeAsCallArgument } from './utilityScriptSerializers'; import { serializeAsCallArgument } from './utilityScriptSerializers';
import { helper } from './helper'; import { helper } from './helper';
@ -38,12 +37,10 @@ export interface ExecutionContextDelegate {
export class ExecutionContext { export class ExecutionContext {
readonly _delegate: ExecutionContextDelegate; readonly _delegate: ExecutionContextDelegate;
readonly _logger: InnerLogger;
private _utilityScriptPromise: Promise<JSHandle> | undefined; private _utilityScriptPromise: Promise<JSHandle> | undefined;
constructor(delegate: ExecutionContextDelegate, logger: InnerLogger) { constructor(delegate: ExecutionContextDelegate) {
this._delegate = delegate; this._delegate = delegate;
this._logger = logger;
} }
adoptIfNeeded(handle: JSHandle): Promise<JSHandle> | null { adoptIfNeeded(handle: JSHandle): Promise<JSHandle> | null {

View file

@ -29,34 +29,35 @@ export interface Logger {
log(name: string, severity: LoggerSeverity, message: string | Error, args: any[], hints: { color?: string }): void; 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 errorLog: Log = { name: 'generic', severity: 'error' };
export const apiLog: Log = { name: 'api', color: 'cyan' }; export const apiLog: Log = { name: 'api', color: 'cyan' };
export function logError(logger: InnerLogger): (error: Error) => void { 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 { export class InnerLogger {
private _logger = new MultiplexingLogger(); private _userSink: Logger | undefined;
private _debugSink: DebugLogger;
constructor(userSink: Logger | undefined) { constructor(userSink: Logger | undefined) {
if (userSink) this._userSink = userSink;
this._logger.add('user', userSink); this._debugSink = new DebugLogger();
this._logger.add('debug', new DebugLogger());
} }
_isLogEnabled(log: Log): boolean { isLogEnabled(log: Log): boolean {
return this._logger.isEnabled(log.name, log.severity || 'info'); 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[]) { log(log: Log, message: string | Error, ...args: any[]) {
if (this._logger.isEnabled(log.name, log.severity || 'info')) const severity = log.severity || 'info';
this._logger.log(log.name, log.severity || 'info', message, args, log.color ? { color: log.color } : {}); 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<string, number>([
['reset', 0], ['reset', 0],
]); ]);
class MultiplexingLogger implements Logger { class DebugLogger {
private _loggers = new Map<string, Logger>();
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 {
private _debuggers = new Map<string, debug.IDebugger>(); private _debuggers = new Map<string, debug.IDebugger>();
isEnabled(name: string, severity: LoggerSeverity): boolean { isEnabled(name: string, severity: LoggerSeverity): boolean {

View file

@ -31,7 +31,7 @@ import * as accessibility from './accessibility';
import { ExtendedEventEmitter } from './extendedEventEmitter'; import { ExtendedEventEmitter } from './extendedEventEmitter';
import { EventEmitter } from 'events'; import { EventEmitter } from 'events';
import { FileChooser } from './fileChooser'; import { FileChooser } from './fileChooser';
import { logError, InnerLogger, Log } from './logger'; import { logError, InnerLogger } from './logger';
export interface PageDelegate { export interface PageDelegate {
readonly rawMouse: input.RawMouse; readonly rawMouse: input.RawMouse;
@ -88,7 +88,7 @@ type PageState = {
extraHTTPHeaders: network.Headers | null; extraHTTPHeaders: network.Headers | null;
}; };
export class Page extends ExtendedEventEmitter implements InnerLogger { export class Page extends ExtendedEventEmitter {
private _closed = false; private _closed = false;
private _closedCallback: () => void; private _closedCallback: () => void;
private _closedPromise: Promise<void>; private _closedPromise: Promise<void>;
@ -100,6 +100,7 @@ export class Page extends ExtendedEventEmitter implements InnerLogger {
readonly mouse: input.Mouse; readonly mouse: input.Mouse;
readonly _timeoutSettings: TimeoutSettings; readonly _timeoutSettings: TimeoutSettings;
readonly _delegate: PageDelegate; readonly _delegate: PageDelegate;
readonly _logger: InnerLogger;
readonly _state: PageState; readonly _state: PageState;
readonly _pageBindings = new Map<string, PageBinding>(); readonly _pageBindings = new Map<string, PageBinding>();
readonly _screenshotter: Screenshotter; readonly _screenshotter: Screenshotter;
@ -114,6 +115,7 @@ export class Page extends ExtendedEventEmitter implements InnerLogger {
constructor(delegate: PageDelegate, browserContext: BrowserContextBase) { constructor(delegate: PageDelegate, browserContext: BrowserContextBase) {
super(); super();
this._delegate = delegate; this._delegate = delegate;
this._logger = browserContext._logger;
this._closedCallback = () => {}; this._closedCallback = () => {};
this._closedPromise = new Promise(f => this._closedCallback = f); this._closedPromise = new Promise(f => this._closedCallback = f);
this._disconnectedCallback = () => {}; this._disconnectedCallback = () => {};
@ -141,7 +143,7 @@ export class Page extends ExtendedEventEmitter implements InnerLogger {
} }
protected _getLogger(): InnerLogger { protected _getLogger(): InnerLogger {
return this; return this._logger;
} }
protected _getTimeoutSettings(): TimeoutSettings { protected _getTimeoutSettings(): TimeoutSettings {
@ -552,33 +554,23 @@ export class Page extends ExtendedEventEmitter implements InnerLogger {
this._delegate.setFileChooserIntercepted(false); this._delegate.setFileChooserIntercepted(false);
return this; 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 { export class Worker extends EventEmitter {
private _logger: InnerLogger;
private _url: string; private _url: string;
private _executionContextPromise: Promise<js.ExecutionContext>; private _executionContextPromise: Promise<js.ExecutionContext>;
private _executionContextCallback: (value?: js.ExecutionContext) => void; private _executionContextCallback: (value?: js.ExecutionContext) => void;
_existingExecutionContext: js.ExecutionContext | null = null; _existingExecutionContext: js.ExecutionContext | null = null;
constructor(logger: InnerLogger, url: string) { constructor(url: string) {
super(); super();
this._logger = logger;
this._url = url; this._url = url;
this._executionContextCallback = () => {}; this._executionContextCallback = () => {};
this._executionContextPromise = new Promise(x => this._executionContextCallback = x); this._executionContextPromise = new Promise(x => this._executionContextCallback = x);
} }
_createExecutionContext(delegate: js.ExecutionContextDelegate) { _createExecutionContext(delegate: js.ExecutionContextDelegate) {
this._existingExecutionContext = new js.ExecutionContext(delegate, this._logger); this._existingExecutionContext = new js.ExecutionContext(delegate);
this._executionContextCallback(this._existingExecutionContext); this._executionContextCallback(this._existingExecutionContext);
} }
@ -627,7 +619,7 @@ export class PageBinding {
else else
expression = helper.evaluationString(deliverErrorValue, name, seq, error); 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) { function deliverResult(name: string, seq: number, result: any) {
(window as any)[name]['callbacks'].get(seq).resolve(result); (window as any)[name]['callbacks'].get(seq).resolve(result);

View file

@ -84,13 +84,13 @@ export class ProgressController {
log: (log: Log, message: string | Error) => { log: (log: Log, message: string | Error) => {
if (this._state === 'running') { if (this._state === 'running') {
this._logRecording.push(message.toString()); this._logRecording.push(message.toString());
this._logger._log(log, ' ' + message); this._logger.log(log, ' ' + message);
} else { } 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 timeoutError = new TimeoutError(`Timeout ${this._timeout}ms exceeded during ${this._apiName}.`);
const timer = setTimeout(() => this._forceAbort(timeoutError), progress.timeUntilDeadline()); const timer = setTimeout(() => this._forceAbort(timeoutError), progress.timeUntilDeadline());
@ -100,7 +100,7 @@ export class ProgressController {
clearTimeout(timer); clearTimeout(timer);
this._state = 'finished'; this._state = 'finished';
this._logRecording = []; this._logRecording = [];
this._logger._log(apiLog, `<= ${this._apiName} succeeded`); this._logger.log(apiLog, `<= ${this._apiName} succeeded`);
return result; return result;
} catch (e) { } catch (e) {
this._aborted(); this._aborted();
@ -108,7 +108,7 @@ export class ProgressController {
clearTimeout(timer); clearTimeout(timer);
this._state = 'aborted'; this._state = 'aborted';
this._logRecording = []; 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))); await Promise.all(this._cleanups.splice(0).map(cleanup => runCleanup(cleanup)));
throw e; throw e;
} }

View file

@ -21,7 +21,7 @@ import * as util from 'util';
import { BrowserContext, PersistentContextOptions, verifyProxySettings, validateBrowserContextOptions } from '../browserContext'; import { BrowserContext, PersistentContextOptions, verifyProxySettings, validateBrowserContextOptions } from '../browserContext';
import { BrowserServer, WebSocketWrapper } from './browserServer'; import { BrowserServer, WebSocketWrapper } from './browserServer';
import * as browserPaths from '../install/browserPaths'; import * as browserPaths from '../install/browserPaths';
import { Logger, RootLogger, InnerLogger } from '../logger'; import { Logger, InnerLogger } from '../logger';
import { ConnectionTransport, WebSocketTransport } from '../transport'; import { ConnectionTransport, WebSocketTransport } from '../transport';
import { BrowserBase, BrowserOptions, Browser } from '../browser'; import { BrowserBase, BrowserOptions, Browser } from '../browser';
import { assert } from '../helper'; import { assert } from '../helper';
@ -108,7 +108,7 @@ export abstract class BrowserTypeBase implements BrowserType {
async launch(options: LaunchOptions = {}): Promise<Browser> { async launch(options: LaunchOptions = {}): Promise<Browser> {
assert(!(options as any).userDataDir, 'userDataDir option is not supported in `browserType.launch`. Use `browserType.launchPersistentContext` instead'); 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.'); 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)); const browser = await runAbortableTask(progress => this._innerLaunch(progress, options, logger, undefined), logger, TimeoutSettings.timeout(options));
return browser; return browser;
} }
@ -116,12 +116,12 @@ export abstract class BrowserTypeBase implements BrowserType {
async launchPersistentContext(userDataDir: string, options: LaunchOptions & PersistentContextOptions = {}): Promise<BrowserContext> { async launchPersistentContext(userDataDir: string, options: LaunchOptions & PersistentContextOptions = {}): Promise<BrowserContext> {
assert(!(options as any).port, 'Cannot specify a port without launching as a server.'); assert(!(options as any).port, 'Cannot specify a port without launching as a server.');
const persistent = validateBrowserContextOptions(options); 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)); const browser = await runAbortableTask(progress => this._innerLaunch(progress, options, logger, persistent, userDataDir), logger, TimeoutSettings.timeout(options));
return browser._defaultContext!; return browser._defaultContext!;
} }
async _innerLaunch(progress: Progress, options: LaunchOptions, logger: RootLogger, persistent: PersistentContextOptions | undefined, userDataDir?: string): Promise<BrowserBase> { async _innerLaunch(progress: Progress, options: LaunchOptions, logger: InnerLogger, persistent: PersistentContextOptions | undefined, userDataDir?: string): Promise<BrowserBase> {
options.proxy = options.proxy ? verifyProxySettings(options.proxy) : undefined; options.proxy = options.proxy ? verifyProxySettings(options.proxy) : undefined;
const { browserServer, downloadsPath, transport } = await this._launchServer(progress, options, !!persistent, logger, userDataDir); const { browserServer, downloadsPath, transport } = await this._launchServer(progress, options, !!persistent, logger, userDataDir);
if ((options as any).__testHookBeforeCreateBrowser) if ((options as any).__testHookBeforeCreateBrowser)
@ -147,7 +147,7 @@ export abstract class BrowserTypeBase implements BrowserType {
async launchServer(options: LaunchServerOptions = {}): Promise<BrowserServer> { async launchServer(options: LaunchServerOptions = {}): Promise<BrowserServer> {
assert(!(options as any).userDataDir, 'userDataDir option is not supported in `browserType.launchServer`. Use `browserType.launchPersistentContext` instead'); assert(!(options as any).userDataDir, 'userDataDir option is not supported in `browserType.launchServer`. Use `browserType.launchPersistentContext` instead');
const { port = 0 } = options; const { port = 0 } = options;
const logger = new RootLogger(options.logger); const logger = new InnerLogger(options.logger);
return runAbortableTask(async progress => { return runAbortableTask(async progress => {
const { browserServer, transport } = await this._launchServer(progress, options, false, logger); const { browserServer, transport } = await this._launchServer(progress, options, false, logger);
browserServer._webSocketWrapper = this._wrapTransportWithWebSocket(transport, logger, port); browserServer._webSocketWrapper = this._wrapTransportWithWebSocket(transport, logger, port);
@ -156,7 +156,7 @@ export abstract class BrowserTypeBase implements BrowserType {
} }
async connect(options: ConnectOptions): Promise<Browser> { async connect(options: ConnectOptions): Promise<Browser> {
const logger = new RootLogger(options.logger); const logger = new InnerLogger(options.logger);
return runAbortableTask(async progress => { return runAbortableTask(async progress => {
const transport = await WebSocketTransport.connect(progress, options.wsEndpoint); const transport = await WebSocketTransport.connect(progress, options.wsEndpoint);
progress.cleanupWhenAborted(() => transport.closeAndWait()); progress.cleanupWhenAborted(() => transport.closeAndWait());
@ -167,7 +167,7 @@ export abstract class BrowserTypeBase implements BrowserType {
}, logger, TimeoutSettings.timeout(options)); }, 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 { const {
ignoreDefaultArgs = false, ignoreDefaultArgs = false,
args = [], args = [],

View file

@ -21,7 +21,7 @@ import { CRExecutionContext } from '../chromium/crExecutionContext';
import { Events } from '../events'; import { Events } from '../events';
import { ExtendedEventEmitter } from '../extendedEventEmitter'; import { ExtendedEventEmitter } from '../extendedEventEmitter';
import * as js from '../javascript'; import * as js from '../javascript';
import { InnerLogger, Logger, RootLogger } from '../logger'; import { InnerLogger, Logger } from '../logger';
import { Page } from '../page'; import { Page } from '../page';
import { TimeoutSettings } from '../timeoutSettings'; import { TimeoutSettings } from '../timeoutSettings';
import { WebSocketTransport } from '../transport'; import { WebSocketTransport } from '../transport';
@ -130,7 +130,7 @@ export class ElectronApplication extends ExtendedEventEmitter {
async _init() { async _init() {
this._nodeSession.once('Runtime.executionContextCreated', event => { 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 => {}); await this._nodeSession.send('Runtime.enable', {}).catch(e => {});
this._nodeElectronHandle = await js.evaluate(this._nodeExecutionContext!, false /* returnByValue */, () => { this._nodeElectronHandle = await js.evaluate(this._nodeExecutionContext!, false /* returnByValue */, () => {
@ -171,7 +171,7 @@ export class Electron {
handleSIGTERM = true, handleSIGTERM = true,
handleSIGHUP = true, handleSIGHUP = true,
} = options; } = options;
const logger = new RootLogger(options.logger); const logger = new InnerLogger(options.logger);
return runAbortableTask(async progress => { return runAbortableTask(async progress => {
let app: ElectronApplication | undefined = undefined; let app: ElectronApplication | undefined = undefined;
const electronArguments = ['--inspect=0', '--remote-debugging-port=0', '--require', path.join(__dirname, 'electronLoader.js'), ...args]; const electronArguments = ['--inspect=0', '--remote-debugging-port=0', '--require', path.join(__dirname, 'electronLoader.js'), ...args];

View file

@ -55,14 +55,14 @@ export class WKConnection {
} }
rawSend(message: ProtocolRequest) { rawSend(message: ProtocolRequest) {
if (this._logger._isLogEnabled(protocolLog)) if (this._logger.isLogEnabled(protocolLog))
this._logger._log(protocolLog, 'SEND ► ' + rewriteInjectedScriptEvaluationLog(message)); this._logger.log(protocolLog, 'SEND ► ' + rewriteInjectedScriptEvaluationLog(message));
this._transport.send(message); this._transport.send(message);
} }
private _dispatchMessage(message: ProtocolResponse) { private _dispatchMessage(message: ProtocolResponse) {
if (this._logger._isLogEnabled(protocolLog)) if (this._logger.isLogEnabled(protocolLog))
this._logger._log(protocolLog, '◀ RECV ' + JSON.stringify(message)); this._logger.log(protocolLog, '◀ RECV ' + JSON.stringify(message));
if (message.id === kBrowserCloseMessageId) if (message.id === kBrowserCloseMessageId)
return; return;
if (message.pageProxyId) { if (message.pageProxyId) {
@ -139,7 +139,7 @@ export class WKSession extends EventEmitter {
sendMayFail<T extends keyof Protocol.CommandParameters>(method: T, params?: Protocol.CommandParameters[T]): Promise<Protocol.CommandReturnValues[T] | void> { sendMayFail<T extends keyof Protocol.CommandParameters>(method: T, params?: Protocol.CommandParameters[T]): Promise<Protocol.CommandReturnValues[T] | void> {
return this.send(method, params).catch(error => { return this.send(method, params).catch(error => {
this.connection._logger._log(errorLog, error, []); this.connection._logger.log(errorLog, error, []);
}); });
} }

View file

@ -37,7 +37,6 @@ import { selectors } from '../selectors';
import * as jpeg from 'jpeg-js'; import * as jpeg from 'jpeg-js';
import * as png from 'pngjs'; import * as png from 'pngjs';
import { NotConnectedError } from '../errors'; import { NotConnectedError } from '../errors';
import { logError } from '../logger';
import { ConsoleMessageLocation } from '../console'; import { ConsoleMessageLocation } from '../console';
import { JSHandle } from '../javascript'; import { JSHandle } from '../javascript';
@ -364,7 +363,7 @@ export class WKPage implements PageDelegate {
// as well to always be in sync with the backend. // as well to always be in sync with the backend.
if (this._provisionalPage) if (this._provisionalPage)
sessions.push(this._provisionalPage._session); 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) { private _onFrameScheduledNavigation(frameId: string) {

View file

@ -34,7 +34,7 @@ export class WKWorkers {
this.clear(); this.clear();
this._sessionListeners = [ this._sessionListeners = [
helper.addEventListener(session, 'Worker.workerCreated', (event: Protocol.Worker.workerCreatedPayload) => { 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) => { const workerSession = new WKSession(session.connection, event.workerId, 'Most likely the worker has been closed.', (message: any) => {
session.send('Worker.sendMessageToWorker', { session.send('Worker.sendMessageToWorker', {
workerId: event.workerId, workerId: event.workerId,

View file

@ -426,3 +426,13 @@ describe('ElementHandle.selectOption', function() {
expect(await page.evaluate(() => result.onChange)).toEqual(['blue']); 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);
});
});