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