Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support for browserContext.on('pageerror') #24452

Merged
merged 7 commits into from
Aug 17, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions packages/playwright-core/src/client/browserContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import { rewriteErrorMessage } from '../utils/stackTrace';
import { HarRouter } from './harRouter';
import { ConsoleMessage } from './consoleMessage';
import { Dialog } from './dialog';
import { parseError } from '../protocol/serializers';

export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel> implements api.BrowserContext {
_pages = new Set<Page>();
Expand Down Expand Up @@ -100,6 +101,12 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel>
if (page)
page.emit(Events.Page.Console, consoleMessage);
});
this._channel.on('pageError', ({ error, page }) => {
this.emit(Events.BrowserContext.PageError, parseError(error));
vigneshshanmugam marked this conversation as resolved.
Show resolved Hide resolved
const pageObject = Page.fromNullable(page);
vigneshshanmugam marked this conversation as resolved.
Show resolved Hide resolved
if (pageObject)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is never null as otherwise Page.from would throw, remove this check.

pageObject.emit(Events.Page.PageError, parseError(error));
});
this._channel.on('dialog', ({ dialog }) => {
const dialogObject = Dialog.from(dialog);
let hasListeners = this.emit(Events.BrowserContext.Dialog, dialogObject);
Expand Down
3 changes: 3 additions & 0 deletions packages/playwright-core/src/client/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ export const Events = {
Close: 'close',
Dialog: 'dialog',
Page: 'page',
// Can't use just 'error' due to node.js special treatment of error events.
// @see https://nodejs.org/api/events.html#events_error_events
PageError: 'pageerror',
BackgroundPage: 'backgroundpage',
ServiceWorker: 'serviceworker',
Request: 'request',
Expand Down
3 changes: 1 addition & 2 deletions packages/playwright-core/src/client/page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import { isSafeCloseError, kBrowserOrContextClosedError } from '../common/errors
import { urlMatches } from '../utils/network';
import { TimeoutSettings } from '../common/timeoutSettings';
import type * as channels from '@protocol/channels';
import { parseError, serializeError } from '../protocol/serializers';
import { serializeError } from '../protocol/serializers';
vigneshshanmugam marked this conversation as resolved.
Show resolved Hide resolved
import { assert, headersObjectToArray, isObject, isRegExp, isString, LongStandingScope, urlMatchesEqual } from '../utils';
import { mkdirIfNeeded } from '../utils/fileUtils';
import { Accessibility } from './accessibility';
Expand Down Expand Up @@ -130,7 +130,6 @@ export class Page extends ChannelOwner<channels.PageChannel> implements api.Page
this._channel.on('fileChooser', ({ element, isMultiple }) => this.emit(Events.Page.FileChooser, new FileChooser(this, ElementHandle.from(element), isMultiple)));
this._channel.on('frameAttached', ({ frame }) => this._onFrameAttached(Frame.from(frame)));
this._channel.on('frameDetached', ({ frame }) => this._onFrameDetached(Frame.from(frame)));
this._channel.on('pageError', ({ error }) => this.emit(Events.Page.PageError, parseError(error)));
this._channel.on('route', ({ route }) => this._onRoute(Route.from(route)));
this._channel.on('video', ({ artifact }) => {
const artifactObject = Artifact.from(artifact);
Expand Down
7 changes: 4 additions & 3 deletions packages/playwright-core/src/protocol/validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -770,6 +770,10 @@ scheme.BrowserContextDialogEvent = tObject({
scheme.BrowserContextPageEvent = tObject({
page: tChannel(['Page']),
});
scheme.BrowserContextPageErrorEvent = tObject({
error: tType('SerializedError'),
page: tOptional(tChannel(['Page'])),
});
scheme.BrowserContextRouteEvent = tObject({
route: tChannel(['Route']),
});
Expand Down Expand Up @@ -957,9 +961,6 @@ scheme.PageFrameAttachedEvent = tObject({
scheme.PageFrameDetachedEvent = tObject({
frame: tChannel(['Frame']),
});
scheme.PagePageErrorEvent = tObject({
error: tType('SerializedError'),
});
scheme.PageRouteEvent = tObject({
route: tChannel(['Route']),
});
Expand Down
3 changes: 3 additions & 0 deletions packages/playwright-core/src/server/browserContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ export abstract class BrowserContext extends SdkObject {
Close: 'close',
Dialog: 'dialog',
Page: 'page',
// Can't use just 'error' due to node.js special treatment of error events.
// @see https://nodejs.org/api/events.html#events_error_events
PageError: 'pageerror',
Request: 'request',
Response: 'response',
RequestFailed: 'requestfailed',
Expand Down
4 changes: 2 additions & 2 deletions packages/playwright-core/src/server/chromium/crPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -756,7 +756,7 @@ class FrameSession {
const args = event.args.map(o => worker._existingExecutionContext!.createHandle(o));
this._page._addConsoleMessage(event.type, args, toConsoleMessageLocation(event.stackTrace));
});
session.on('Runtime.exceptionThrown', exception => this._page.emit(Page.Events.PageError, exceptionToError(exception.exceptionDetails)));
session.on('Runtime.exceptionThrown', exception => this._page.emitOnContext(BrowserContext.Events.PageError, exceptionToError(exception.exceptionDetails)));
// TODO: attribute workers to the right frame.
this._networkManager.instrumentNetworkEvents({ session, workerFrame: this._page._frameManager.frame(this._targetId) ?? undefined });
}
Expand Down Expand Up @@ -859,7 +859,7 @@ class FrameSession {
}

_handleException(exceptionDetails: Protocol.Runtime.ExceptionDetails) {
this._page.firePageError(exceptionToError(exceptionDetails));
this._page.emitOnContext(BrowserContext.Events.PageError, exceptionToError(exceptionDetails));
}

async _onTargetCrashed() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import { DialogDispatcher } from './dialogDispatcher';
import type { Page } from '../page';
import type { Dialog } from '../dialog';
import type { ConsoleMessage } from '../console';
import { serializeError } from '../../protocol/serializers';

export class BrowserContextDispatcher extends Dispatcher<BrowserContext, channels.BrowserContextChannel, DispatcherScope> implements channels.BrowserContextChannel {
_type_EventTarget = true;
Expand Down Expand Up @@ -84,6 +85,9 @@ export class BrowserContextDispatcher extends Dispatcher<BrowserContext, channel
this._dispatchEvent('close');
this._dispose();
});
this.addObjectListener(BrowserContext.Events.PageError, (error: Error, page: Page) => {
this._dispatchEvent('pageError', { error: serializeError(error), page: PageDispatcher.from(this, page) });
});
this.addObjectListener(BrowserContext.Events.Console, (message: ConsoleMessage) => {
if (this._shouldDispatchEvent(message.page(), 'console'))
this._dispatchEvent('console', { message: new ConsoleMessageDispatcher(PageDispatcher.from(this, message.page()), message) });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import type { Frame } from '../frames';
import { Page, Worker } from '../page';
import type * as channels from '@protocol/channels';
import { Dispatcher, existingDispatcher } from './dispatcher';
import { parseError, serializeError } from '../../protocol/serializers';
import { parseError } from '../../protocol/serializers';
import { FrameDispatcher } from './frameDispatcher';
import { RequestDispatcher } from './networkDispatchers';
import { ResponseDispatcher } from './networkDispatchers';
Expand Down Expand Up @@ -85,7 +85,6 @@ export class PageDispatcher extends Dispatcher<Page, channels.PageChannel, Brows
}));
this.addObjectListener(Page.Events.FrameAttached, frame => this._onFrameAttached(frame));
this.addObjectListener(Page.Events.FrameDetached, frame => this._onFrameDetached(frame));
this.addObjectListener(Page.Events.PageError, error => this._dispatchEvent('pageError', { error: serializeError(error) }));
this.addObjectListener(Page.Events.WebSocket, webSocket => this._dispatchEvent('webSocket', { webSocket: new WebSocketDispatcher(this, webSocket) }));
this.addObjectListener(Page.Events.Worker, worker => this._dispatchEvent('worker', { worker: new WorkerDispatcher(this, worker) }));
this.addObjectListener(Page.Events.Video, (artifact: Artifact) => this._dispatchEvent('video', { artifact: ArtifactDispatcher.from(parentScope, artifact) }));
Expand Down
3 changes: 1 addition & 2 deletions packages/playwright-core/src/server/fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,6 @@ type SendRequestOptions = https.RequestOptions & {
export abstract class APIRequestContext extends SdkObject {
static Events = {
Dispose: 'dispose',

Request: 'request',
RequestFinished: 'requestfinished',
};
Expand Down Expand Up @@ -730,4 +729,4 @@ function shouldBypassProxy(url: URL, bypass?: string): boolean {
});
const domain = '.' + url.hostname;
return domains.some(d => domain.endsWith(d));
}
}
vigneshshanmugam marked this conversation as resolved.
Show resolved Hide resolved
2 changes: 1 addition & 1 deletion packages/playwright-core/src/server/firefox/ffPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ export class FFPage implements PageDelegate {
const error = new Error(message);
error.stack = params.message + '\n' + params.stack.split('\n').filter(Boolean).map(a => a.replace(/([^@]*)@(.*)/, ' at $1 ($2)')).join('\n');
error.name = name;
this._page.firePageError(error);
this._page._browserContext.emit(BrowserContext.Events.PageError, error);
}

_onConsole(payload: Protocol.Runtime.consolePayload) {
Expand Down
7 changes: 0 additions & 7 deletions packages/playwright-core/src/server/page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,9 +125,6 @@ export class Page extends SdkObject {
Crash: 'crash',
Download: 'download',
FileChooser: 'filechooser',
// Can't use just 'error' due to node.js special treatment of error events.
// @see https://nodejs.org/api/events.html#events_error_events
PageError: 'pageerror',
FrameAttached: 'frameattached',
FrameDetached: 'framedetached',
InternalFrameNavigatedToNewDocument: 'internalframenavigatedtonewdocument',
Expand Down Expand Up @@ -696,10 +693,6 @@ export class Page extends SdkObject {
this._frameThrottler.recharge();
}

firePageError(error: Error) {
this.emit(Page.Events.PageError, error);
}

async hideHighlight() {
await Promise.all(this.frames().map(frame => frame.hideHighlight().catch(() => {})));
}
Expand Down
2 changes: 1 addition & 1 deletion packages/playwright-core/src/server/webkit/wkPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -555,7 +555,7 @@ export class WKPage implements PageDelegate {
error.stack = stack;
error.name = name;

this._page.firePageError(error);
this._page._browserContext.emit(BrowserContext.Events.PageError, error);
return;
}

Expand Down
64 changes: 64 additions & 0 deletions packages/playwright-core/types/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7591,6 +7591,24 @@ export interface BrowserContext {
*/
on(event: 'dialog', listener: (dialog: Dialog) => void): this;

/**
* Emitted when an uncaught exception happens on any pages created through
* this context. To only listen for the exceptions from a particular page, use
* [page.on('pageerror')](https://playwright.dev/docs/api/class-page#page-event-page-error).
*
* ```js
* // Log all uncaught errors to the terminal
* context.on('pageerror', exception => {
* console.log(`Uncaught exception: "${exception}"`);
* });
*
* // Navigate to a page with an exception.
* await page.goto('data:text/html,<script>throw new Error("Test")</script>');
* ```
*
*/
on(event: 'pageerror', listener: (error: Error) => void): this;

/**
* The event is emitted when a new Page is created in the BrowserContext. The page may still be loading. The event
* will also fire for popup pages. See also
Expand Down Expand Up @@ -7684,6 +7702,11 @@ export interface BrowserContext {
*/
once(event: 'page', listener: (page: Page) => void): this;

/**
* Adds an event listener that will be automatically removed after it is triggered once. See `addListener` for more information about this event.
*/
once(event: 'pageerror', listener: (error: Error) => void): this;

/**
* Adds an event listener that will be automatically removed after it is triggered once. See `addListener` for more information about this event.
*/
Expand Down Expand Up @@ -7795,6 +7818,24 @@ export interface BrowserContext {
*/
addListener(event: 'page', listener: (page: Page) => void): this;

/**
* Emitted when an uncaught exception happens on any pages created through
* this context. To only listen for the exceptions from a particular page, use
* [page.on('pageerror')](https://playwright.dev/docs/api/class-page#page-event-page-error).
*
* ```js
* // Log all uncaught errors to the terminal
* context.on('pageerror', exception => {
* console.log(`Uncaught exception: "${exception}"`);
* });
*
* // Navigate to a page with an exception.
* await page.goto('data:text/html,<script>throw new Error("Test")</script>');
* ```
*
*/
addListener(event: 'pageerror', listener: (error: Error) => void): this;

/**
* Emitted when a request is issued from any pages created through this context. The [request] object is read-only. To
* only listen for requests from a particular page, use
Expand Down Expand Up @@ -7865,6 +7906,11 @@ export interface BrowserContext {
*/
removeListener(event: 'page', listener: (page: Page) => void): this;

/**
* Removes an event listener added by `on` or `addListener`.
*/
removeListener(event: 'pageerror', listener: (error: Error) => void): this;

/**
* Removes an event listener added by `on` or `addListener`.
*/
Expand Down Expand Up @@ -8610,6 +8656,24 @@ export interface BrowserContext {
*/
waitForEvent(event: 'page', optionsOrPredicate?: { predicate?: (page: Page) => boolean | Promise<boolean>, timeout?: number } | ((page: Page) => boolean | Promise<boolean>)): Promise<Page>;

/**
* Emitted when an uncaught exception happens on any pages created through
* this context. To only listen for the exceptions from a particular page, use
* [page.on('pageerror')](https://playwright.dev/docs/api/class-page#page-event-page-error).
*
* ```js
* // Log all uncaught errors to the terminal
* context.on('pageerror', exception => {
* console.log(`Uncaught exception: "${exception}"`);
* });
*
* // Navigate to a page with an exception.
* await page.goto('data:text/html,<script>throw new Error("Test")</script>');
* ```
*
*/
waitForEvent(event: 'pageerror', optionsOrPredicate?: { predicate?: (error: Error) => boolean | Promise<boolean>, timeout?: number } | ((error: Error) => boolean | Promise<boolean>)): Promise<Error>;

/**
* Emitted when a request is issued from any pages created through this context. The [request] object is read-only. To
* only listen for requests from a particular page, use
Expand Down
11 changes: 6 additions & 5 deletions packages/protocol/src/channels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1404,6 +1404,7 @@ export interface BrowserContextEventTarget {
on(event: 'close', callback: (params: BrowserContextCloseEvent) => void): this;
on(event: 'dialog', callback: (params: BrowserContextDialogEvent) => void): this;
on(event: 'page', callback: (params: BrowserContextPageEvent) => void): this;
on(event: 'pageError', callback: (params: BrowserContextPageErrorEvent) => void): this;
on(event: 'route', callback: (params: BrowserContextRouteEvent) => void): this;
on(event: 'video', callback: (params: BrowserContextVideoEvent) => void): this;
on(event: 'backgroundPage', callback: (params: BrowserContextBackgroundPageEvent) => void): this;
Expand Down Expand Up @@ -1453,6 +1454,10 @@ export type BrowserContextDialogEvent = {
export type BrowserContextPageEvent = {
page: PageChannel,
};
export type BrowserContextPageErrorEvent = {
error: SerializedError,
page?: PageChannel,
};
export type BrowserContextRouteEvent = {
route: RouteChannel,
};
Expand Down Expand Up @@ -1697,6 +1702,7 @@ export interface BrowserContextEvents {
'close': BrowserContextCloseEvent;
'dialog': BrowserContextDialogEvent;
'page': BrowserContextPageEvent;
'pageError': BrowserContextPageErrorEvent;
'route': BrowserContextRouteEvent;
'video': BrowserContextVideoEvent;
'backgroundPage': BrowserContextBackgroundPageEvent;
Expand Down Expand Up @@ -1725,7 +1731,6 @@ export interface PageEventTarget {
on(event: 'fileChooser', callback: (params: PageFileChooserEvent) => void): this;
on(event: 'frameAttached', callback: (params: PageFrameAttachedEvent) => void): this;
on(event: 'frameDetached', callback: (params: PageFrameDetachedEvent) => void): this;
on(event: 'pageError', callback: (params: PagePageErrorEvent) => void): this;
on(event: 'route', callback: (params: PageRouteEvent) => void): this;
on(event: 'video', callback: (params: PageVideoEvent) => void): this;
on(event: 'webSocket', callback: (params: PageWebSocketEvent) => void): this;
Expand Down Expand Up @@ -1787,9 +1792,6 @@ export type PageFrameAttachedEvent = {
export type PageFrameDetachedEvent = {
frame: FrameChannel,
};
export type PagePageErrorEvent = {
error: SerializedError,
};
export type PageRouteEvent = {
route: RouteChannel,
};
Expand Down Expand Up @@ -2220,7 +2222,6 @@ export interface PageEvents {
'fileChooser': PageFileChooserEvent;
'frameAttached': PageFrameAttachedEvent;
'frameDetached': PageFrameDetachedEvent;
'pageError': PagePageErrorEvent;
'route': PageRouteEvent;
'video': PageVideoEvent;
'webSocket': PageWebSocketEvent;
Expand Down
9 changes: 5 additions & 4 deletions packages/protocol/src/protocol.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1183,6 +1183,11 @@ BrowserContext:
parameters:
page: Page

pageError:
parameters:
error: SerializedError
page: Page?
vigneshshanmugam marked this conversation as resolved.
Show resolved Hide resolved

route:
parameters:
route: Route
Expand Down Expand Up @@ -1630,10 +1635,6 @@ Page:
parameters:
frame: Frame

pageError:
parameters:
error: SerializedError

route:
parameters:
route: Route
Expand Down
Loading