From d7b3836752fbb996a4eb7dd6e36c3970912ac2e4 Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Tue, 4 Apr 2023 13:13:52 -0700 Subject: [PATCH] chore: revert console and dialog events on BrowserContext (#22195) This reverts #22033 and #21943, since there will be no immediate benefits in v1.33. --- docs/src/api/class-browsercontext.md | 96 ------- docs/src/api/class-consolemessage.md | 6 - docs/src/api/class-dialog.md | 6 - docs/src/api/class-page.md | 19 +- .../src/client/browserContext.ts | 22 -- .../src/client/consoleMessage.ts | 11 - packages/playwright-core/src/client/dialog.ts | 10 - packages/playwright-core/src/client/events.ts | 2 - packages/playwright-core/src/client/page.ts | 12 + .../playwright-core/src/protocol/validator.ts | 14 +- .../src/server/browserContext.ts | 2 - .../src/server/chromium/crPage.ts | 2 +- .../playwright-core/src/server/console.ts | 11 +- packages/playwright-core/src/server/dialog.ts | 4 - .../dispatchers/browserContextDispatcher.ts | 4 - .../dispatchers/consoleMessageDispatcher.ts | 7 +- .../server/dispatchers/dialogDispatcher.ts | 12 +- .../src/server/dispatchers/pageDispatcher.ts | 4 + .../src/server/firefox/ffPage.ts | 3 +- packages/playwright-core/src/server/frames.ts | 4 +- packages/playwright-core/src/server/page.ts | 33 +-- .../playwright-core/src/server/recorder.ts | 5 +- .../src/server/webkit/wkPage.ts | 3 +- packages/playwright-core/types/types.d.ts | 260 ++---------------- packages/protocol/src/channels.ts | 22 +- packages/protocol/src/protocol.yml | 18 +- tests/library/browsercontext-events.spec.ts | 162 ----------- tests/library/trace-viewer.spec.ts | 2 +- 28 files changed, 93 insertions(+), 663 deletions(-) delete mode 100644 tests/library/browsercontext-events.spec.ts diff --git a/docs/src/api/class-browsercontext.md b/docs/src/api/class-browsercontext.md index 3ae900ff37..f3bef29d01 100644 --- a/docs/src/api/class-browsercontext.md +++ b/docs/src/api/class-browsercontext.md @@ -94,102 +94,6 @@ Emitted when Browser context gets closed. This might happen because of one of th * Browser application is closed or crashed. * The [`method: Browser.close`] method was called. -## event: BrowserContext.console -* since: v1.33 -* langs: - - alias-java: consoleMessage -- argument: <[ConsoleMessage]> - -Emitted when JavaScript within the page calls one of console API methods, e.g. `console.log` or `console.dir`. Also emitted if the page throws an error or a warning. - -The arguments passed into `console.log` and the page are available on the [ConsoleMessage] event handler argument. - -**Usage** - -```js -context.on('console', async msg => { - const values = []; - for (const arg of msg.args()) - values.push(await arg.jsonValue()); - console.log(...values); -}); -await page.evaluate(() => console.log('hello', 5, { foo: 'bar' })); -``` - -```java -context.onConsoleMessage(msg -> { - for (int i = 0; i < msg.args().size(); ++i) - System.out.println(i + ": " + msg.args().get(i).jsonValue()); -}); -page.evaluate("() => console.log('hello', 5, { foo: 'bar' })"); -``` - -```python async -async def print_args(msg): - values = [] - for arg in msg.args: - values.append(await arg.json_value()) - print(values) - -context.on("console", print_args) -await page.evaluate("console.log('hello', 5, { foo: 'bar' })") -``` - -```python sync -def print_args(msg): - for arg in msg.args: - print(arg.json_value()) - -context.on("console", print_args) -page.evaluate("console.log('hello', 5, { foo: 'bar' })") -``` - -```csharp -context.Console += async (_, msg) => -{ - foreach (var arg in msg.Args) - Console.WriteLine(await arg.JsonValueAsync()); -}; - -await page.EvaluateAsync("console.log('hello', 5, { foo: 'bar' })"); -``` - - -## event: BrowserContext.dialog -* since: v1.33 -- argument: <[Dialog]> - -Emitted when a JavaScript dialog appears, such as `alert`, `prompt`, `confirm` or `beforeunload`. Listener **must** either [`method: Dialog.accept`] or [`method: Dialog.dismiss`] the dialog - otherwise the page will [freeze](https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop#never_blocking) waiting for the dialog, and actions like click will never finish. - -**Usage** - -```js -context.on('dialog', dialog => { - dialog.accept(); -}); -``` - -```java -context.onDialog(dialog -> { - dialog.accept(); -}); -``` - -```python -context.on("dialog", lambda dialog: dialog.accept()) -``` - -```csharp -context.RequestFailed += (_, request) => -{ - Console.WriteLine(request.Url + " " + request.Failure); -}; -``` - -:::note -When no [`event: Page.dialog`] or [`event: BrowserContext.dialog`] listeners are present, all dialogs are automatically dismissed. -::: - ## event: BrowserContext.page * since: v1.8 - argument: <[Page]> diff --git a/docs/src/api/class-consolemessage.md b/docs/src/api/class-consolemessage.md index 5d1a3d0ff2..9fb1a9424c 100644 --- a/docs/src/api/class-consolemessage.md +++ b/docs/src/api/class-consolemessage.md @@ -125,12 +125,6 @@ List of arguments passed to a `console` function call. See also [`event: Page.co URL of the resource followed by 0-based line and column numbers in the resource formatted as `URL:line:column`. -## method: ConsoleMessage.page -* since: v1.33 -- returns: <[Page]|[null]> - -The page that produced this console message, if any. - ## method: ConsoleMessage.text * since: v1.8 - returns: <[string]> diff --git a/docs/src/api/class-dialog.md b/docs/src/api/class-dialog.md index 37935d0d9d..b013f4b69c 100644 --- a/docs/src/api/class-dialog.md +++ b/docs/src/api/class-dialog.md @@ -137,12 +137,6 @@ Returns when the dialog has been dismissed. A message displayed in the dialog. -## method: Dialog.page -* since: v1.33 -- returns: <[Page]|[null]> - -The page that initiated this dialog, if available. - ## method: Dialog.type * since: v1.8 - returns: <[string]> diff --git a/docs/src/api/class-page.md b/docs/src/api/class-page.md index b952ac6e36..2068614f75 100644 --- a/docs/src/api/class-page.md +++ b/docs/src/api/class-page.md @@ -163,11 +163,12 @@ Emitted when the page closes. - alias-java: consoleMessage - argument: <[ConsoleMessage]> -Emitted when JavaScript within the page calls one of console API methods, e.g. `console.log` or `console.dir`. Also emitted if the page throws an error or a warning. +Emitted when JavaScript within the page calls one of console API methods, e.g. `console.log` or `console.dir`. Also +emitted if the page throws an error or a warning. -The arguments passed into `console.log` are available on the [ConsoleMessage] event handler argument. +The arguments passed into `console.log` appear as arguments on the event handler. -**Usage** +An example of handling `console` event: ```js page.on('console', async msg => { @@ -176,7 +177,7 @@ page.on('console', async msg => { values.push(await arg.jsonValue()); console.log(...values); }); -await page.evaluate(() => console.log('hello', 5, { foo: 'bar' })); +await page.evaluate(() => console.log('hello', 5, {foo: 'bar'})); ``` ```java @@ -184,7 +185,7 @@ page.onConsoleMessage(msg -> { for (int i = 0; i < msg.args().size(); ++i) System.out.println(i + ": " + msg.args().get(i).jsonValue()); }); -page.evaluate("() => console.log('hello', 5, { foo: 'bar' })"); +page.evaluate("() => console.log('hello', 5, {foo: 'bar'})"); ``` ```python async @@ -195,7 +196,7 @@ async def print_args(msg): print(values) page.on("console", print_args) -await page.evaluate("console.log('hello', 5, { foo: 'bar' })") +await page.evaluate("console.log('hello', 5, {foo: 'bar'})") ``` ```python sync @@ -204,7 +205,7 @@ def print_args(msg): print(arg.json_value()) page.on("console", print_args) -page.evaluate("console.log('hello', 5, { foo: 'bar' })") +page.evaluate("console.log('hello', 5, {foo: 'bar'})") ``` ```csharp @@ -285,8 +286,6 @@ try { Emitted when a JavaScript dialog appears, such as `alert`, `prompt`, `confirm` or `beforeunload`. Listener **must** either [`method: Dialog.accept`] or [`method: Dialog.dismiss`] the dialog - otherwise the page will [freeze](https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop#never_blocking) waiting for the dialog, and actions like click will never finish. -**Usage** - ```js page.on('dialog', dialog => { dialog.accept(); @@ -311,7 +310,7 @@ page.RequestFailed += (_, request) => ``` :::note -When no [`event: Page.dialog`] or [`event: BrowserContext.dialog`] listeners are present, all dialogs are automatically dismissed. +When no [`event: Page.dialog`] listeners are present, all dialogs are automatically dismissed. ::: ## event: Page.DOMContentLoaded diff --git a/packages/playwright-core/src/client/browserContext.ts b/packages/playwright-core/src/client/browserContext.ts index 3bb1d6bdc2..9851382570 100644 --- a/packages/playwright-core/src/client/browserContext.ts +++ b/packages/playwright-core/src/client/browserContext.ts @@ -40,8 +40,6 @@ import { APIRequestContext } from './fetch'; import { createInstrumentation } from './clientInstrumentation'; import { rewriteErrorMessage } from '../utils/stackTrace'; import { HarRouter } from './harRouter'; -import { ConsoleMessage } from './consoleMessage'; -import { Dialog } from './dialog'; export class BrowserContext extends ChannelOwner implements api.BrowserContext { _pages = new Set(); @@ -94,26 +92,6 @@ export class BrowserContext extends ChannelOwner this._serviceWorkers.add(serviceWorker); this.emit(Events.BrowserContext.ServiceWorker, serviceWorker); }); - this._channel.on('console', ({ message }) => { - const consoleMessage = ConsoleMessage.from(message); - this.emit(Events.BrowserContext.Console, consoleMessage); - const page = consoleMessage.page(); - if (page) - page.emit(Events.Page.Console, consoleMessage); - }); - this._channel.on('dialog', ({ dialog }) => { - const dialogObject = Dialog.from(dialog); - let hasListeners = this.emit(Events.BrowserContext.Dialog, dialogObject); - const page = dialogObject.page(); - if (page) - hasListeners = page.emit(Events.Page.Dialog, dialogObject) || hasListeners; - if (!hasListeners) { - if (dialogObject.type() === 'beforeunload') - dialog.accept({}).catch(() => {}); - else - dialog.dismiss().catch(() => {}); - } - }); this._channel.on('request', ({ request, page }) => this._onRequest(network.Request.from(request), Page.fromNullable(page))); this._channel.on('requestFailed', ({ request, failureText, responseEndTiming, page }) => this._onRequestFailed(network.Request.from(request), responseEndTiming, failureText, Page.fromNullable(page))); this._channel.on('requestFinished', params => this._onRequestFinished(params)); diff --git a/packages/playwright-core/src/client/consoleMessage.ts b/packages/playwright-core/src/client/consoleMessage.ts index 11f12babd4..df11c23672 100644 --- a/packages/playwright-core/src/client/consoleMessage.ts +++ b/packages/playwright-core/src/client/consoleMessage.ts @@ -19,7 +19,6 @@ import { JSHandle } from './jsHandle'; import type * as channels from '@protocol/channels'; import { ChannelOwner } from './channelOwner'; import type * as api from '../../types/types'; -import { Page } from './page'; type ConsoleMessageLocation = channels.ConsoleMessageInitializer['location']; @@ -28,18 +27,8 @@ export class ConsoleMessage extends ChannelOwner return (message as any)._object; } - private _page: Page | null; - constructor(parent: ChannelOwner, type: string, guid: string, initializer: channels.ConsoleMessageInitializer) { super(parent, type, guid, initializer); - // Note: currently, we only report console messages for pages and they always have a page. - // However, in the future we might report console messages for service workers or something else, - // where page() would be null. - this._page = Page.fromNullable(initializer.page); - } - - page() { - return this._page; } type(): string { diff --git a/packages/playwright-core/src/client/dialog.ts b/packages/playwright-core/src/client/dialog.ts index c012838b7c..e302e8d8d1 100644 --- a/packages/playwright-core/src/client/dialog.ts +++ b/packages/playwright-core/src/client/dialog.ts @@ -17,24 +17,14 @@ import type * as channels from '@protocol/channels'; import { ChannelOwner } from './channelOwner'; import type * as api from '../../types/types'; -import { Page } from './page'; export class Dialog extends ChannelOwner implements api.Dialog { static from(dialog: channels.DialogChannel): Dialog { return (dialog as any)._object; } - private _page: Page | null; - constructor(parent: ChannelOwner, type: string, guid: string, initializer: channels.DialogInitializer) { super(parent, type, guid, initializer); - // Note: dialogs that open early during page initialization block it. - // Therefore, we must report the dialog without a page to be able to handle it. - this._page = Page.fromNullable(initializer.page); - } - - page() { - return this._page; } type(): string { diff --git a/packages/playwright-core/src/client/events.ts b/packages/playwright-core/src/client/events.ts index ceca0829d6..1d2cf9f52b 100644 --- a/packages/playwright-core/src/client/events.ts +++ b/packages/playwright-core/src/client/events.ts @@ -35,9 +35,7 @@ export const Events = { }, BrowserContext: { - Console: 'console', Close: 'close', - Dialog: 'dialog', Page: 'page', BackgroundPage: 'backgroundpage', ServiceWorker: 'serviceworker', diff --git a/packages/playwright-core/src/client/page.ts b/packages/playwright-core/src/client/page.ts index ff6d15f6cd..28c9b27ecb 100644 --- a/packages/playwright-core/src/client/page.ts +++ b/packages/playwright-core/src/client/page.ts @@ -31,7 +31,9 @@ import { Artifact } from './artifact'; import type { BrowserContext } from './browserContext'; import { ChannelOwner } from './channelOwner'; import { evaluationScript } from './clientHelper'; +import { ConsoleMessage } from './consoleMessage'; import { Coverage } from './coverage'; +import { Dialog } from './dialog'; import { Download } from './download'; import { determineScreenshotType, ElementHandle } from './elementHandle'; import { Events } from './events'; @@ -122,7 +124,17 @@ export class Page extends ChannelOwner implements api.Page this._channel.on('bindingCall', ({ binding }) => this._onBinding(BindingCall.from(binding))); this._channel.on('close', () => this._onClose()); + this._channel.on('console', ({ message }) => this.emit(Events.Page.Console, ConsoleMessage.from(message))); this._channel.on('crash', () => this._onCrash()); + this._channel.on('dialog', ({ dialog }) => { + const dialogObj = Dialog.from(dialog); + if (!this.emit(Events.Page.Dialog, dialogObj)) { + if (dialogObj.type() === 'beforeunload') + dialog.accept({}).catch(() => {}); + else + dialog.dismiss().catch(() => {}); + } + }); this._channel.on('download', ({ url, suggestedFilename, artifact }) => { const artifactObject = Artifact.from(artifact); this.emit(Events.Page.Download, new Download(this, url, suggestedFilename, artifactObject)); diff --git a/packages/playwright-core/src/protocol/validator.ts b/packages/playwright-core/src/protocol/validator.ts index 1c87acb9ee..a7aad29c90 100644 --- a/packages/playwright-core/src/protocol/validator.ts +++ b/packages/playwright-core/src/protocol/validator.ts @@ -756,13 +756,7 @@ scheme.BrowserContextInitializer = tObject({ scheme.BrowserContextBindingCallEvent = tObject({ binding: tChannel(['BindingCall']), }); -scheme.BrowserContextConsoleEvent = tObject({ - message: tChannel(['ConsoleMessage']), -}); scheme.BrowserContextCloseEvent = tOptional(tObject({})); -scheme.BrowserContextDialogEvent = tObject({ - dialog: tChannel(['Dialog']), -}); scheme.BrowserContextPageEvent = tObject({ page: tChannel(['Page']), }); @@ -936,7 +930,13 @@ scheme.PageBindingCallEvent = tObject({ binding: tChannel(['BindingCall']), }); scheme.PageCloseEvent = tOptional(tObject({})); +scheme.PageConsoleEvent = tObject({ + message: tChannel(['ConsoleMessage']), +}); scheme.PageCrashEvent = tOptional(tObject({})); +scheme.PageDialogEvent = tObject({ + dialog: tChannel(['Dialog']), +}); scheme.PageDownloadEvent = tObject({ url: tString, suggestedFilename: tString, @@ -2079,7 +2079,6 @@ scheme.WebSocketSocketErrorEvent = tObject({ }); scheme.WebSocketCloseEvent = tOptional(tObject({})); scheme.ConsoleMessageInitializer = tObject({ - page: tChannel(['Page']), type: tString, text: tString, args: tArray(tChannel(['ElementHandle', 'JSHandle'])), @@ -2104,7 +2103,6 @@ scheme.BindingCallResolveParams = tObject({ }); scheme.BindingCallResolveResult = tOptional(tObject({})); scheme.DialogInitializer = tObject({ - page: tOptional(tChannel(['Page'])), type: tString, message: tString, defaultValue: tString, diff --git a/packages/playwright-core/src/server/browserContext.ts b/packages/playwright-core/src/server/browserContext.ts index c92b671304..e339c308f8 100644 --- a/packages/playwright-core/src/server/browserContext.ts +++ b/packages/playwright-core/src/server/browserContext.ts @@ -44,9 +44,7 @@ import type { Artifact } from './artifact'; export abstract class BrowserContext extends SdkObject { static Events = { - Console: 'console', Close: 'close', - Dialog: 'dialog', Page: 'page', Request: 'request', Response: 'response', diff --git a/packages/playwright-core/src/server/chromium/crPage.ts b/packages/playwright-core/src/server/chromium/crPage.ts index 080a835ca6..e66868b2cd 100644 --- a/packages/playwright-core/src/server/chromium/crPage.ts +++ b/packages/playwright-core/src/server/chromium/crPage.ts @@ -845,7 +845,7 @@ class FrameSession { _onDialog(event: Protocol.Page.javascriptDialogOpeningPayload) { if (!this._page._frameManager.frame(this._targetId)) return; // Our frame/subtree may be gone already. - this._page.emitOnContext(BrowserContext.Events.Dialog, new dialog.Dialog( + this._page.emit(Page.Events.Dialog, new dialog.Dialog( this._page, event.type, event.message, diff --git a/packages/playwright-core/src/server/console.ts b/packages/playwright-core/src/server/console.ts index 5b0de45a0f..ccb23e7b39 100644 --- a/packages/playwright-core/src/server/console.ts +++ b/packages/playwright-core/src/server/console.ts @@ -17,28 +17,21 @@ import { SdkObject } from './instrumentation'; import type * as js from './javascript'; import type { ConsoleMessageLocation } from './types'; -import type { Page } from './page'; export class ConsoleMessage extends SdkObject { private _type: string; private _text?: string; private _args: js.JSHandle[]; private _location: ConsoleMessageLocation; - private _page: Page; - constructor(page: Page, type: string, text: string | undefined, args: js.JSHandle[], location?: ConsoleMessageLocation) { - super(page, 'console-message'); - this._page = page; + constructor(parent: SdkObject, type: string, text: string | undefined, args: js.JSHandle[], location?: ConsoleMessageLocation) { + super(parent, 'console-message'); this._type = type; this._text = text; this._args = args; this._location = location || { url: '', lineNumber: 0, columnNumber: 0 }; } - page() { - return this._page; - } - type(): string { return this._type; } diff --git a/packages/playwright-core/src/server/dialog.ts b/packages/playwright-core/src/server/dialog.ts index 51dcfc2fc9..dd1683e9aa 100644 --- a/packages/playwright-core/src/server/dialog.ts +++ b/packages/playwright-core/src/server/dialog.ts @@ -41,10 +41,6 @@ export class Dialog extends SdkObject { this._page._frameManager.dialogDidOpen(this); } - page() { - return this._page; - } - type(): string { return this._type; } diff --git a/packages/playwright-core/src/server/dispatchers/browserContextDispatcher.ts b/packages/playwright-core/src/server/dispatchers/browserContextDispatcher.ts index 5ac9649588..09e04074db 100644 --- a/packages/playwright-core/src/server/dispatchers/browserContextDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/browserContextDispatcher.ts @@ -33,8 +33,6 @@ import * as fs from 'fs'; import * as path from 'path'; import { createGuid, urlMatches } from '../../utils'; import { WritableStreamDispatcher } from './writableStreamDispatcher'; -import { ConsoleMessageDispatcher } from './consoleMessageDispatcher'; -import { DialogDispatcher } from './dialogDispatcher'; export class BrowserContextDispatcher extends Dispatcher implements channels.BrowserContextChannel { _type_EventTarget = true; @@ -81,8 +79,6 @@ export class BrowserContextDispatcher extends Dispatcher this._dispatchEvent('console', { message: new ConsoleMessageDispatcher(PageDispatcher.from(this, message.page()), message) })); - this.addObjectListener(BrowserContext.Events.Dialog, dialog => this._dispatchEvent('dialog', { dialog: new DialogDispatcher(this, dialog) })); if (context._browser.options.name === 'chromium') { for (const page of (context as CRBrowserContext).backgroundPages()) diff --git a/packages/playwright-core/src/server/dispatchers/consoleMessageDispatcher.ts b/packages/playwright-core/src/server/dispatchers/consoleMessageDispatcher.ts index 35354a5ed5..ae1d2124b9 100644 --- a/packages/playwright-core/src/server/dispatchers/consoleMessageDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/consoleMessageDispatcher.ts @@ -23,13 +23,12 @@ import { ElementHandleDispatcher } from './elementHandlerDispatcher'; export class ConsoleMessageDispatcher extends Dispatcher implements channels.ConsoleMessageChannel { _type_ConsoleMessage = true; - constructor(page: PageDispatcher, message: ConsoleMessage) { - super(page, message, 'ConsoleMessage', { + constructor(scope: PageDispatcher, message: ConsoleMessage) { + super(scope, message, 'ConsoleMessage', { type: message.type(), text: message.text(), - args: message.args().map(a => ElementHandleDispatcher.fromJSHandle(page, a)), + args: message.args().map(a => ElementHandleDispatcher.fromJSHandle(scope, a)), location: message.location(), - page, }); } } diff --git a/packages/playwright-core/src/server/dispatchers/dialogDispatcher.ts b/packages/playwright-core/src/server/dispatchers/dialogDispatcher.ts index 50ff01f108..8ae74504f8 100644 --- a/packages/playwright-core/src/server/dispatchers/dialogDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/dialogDispatcher.ts @@ -17,17 +17,13 @@ import type { Dialog } from '../dialog'; import type * as channels from '@protocol/channels'; import { Dispatcher } from './dispatcher'; -import { PageDispatcher } from './pageDispatcher'; -import type { BrowserContextDispatcher } from './browserContextDispatcher'; +import type { PageDispatcher } from './pageDispatcher'; -export class DialogDispatcher extends Dispatcher implements channels.DialogChannel { +export class DialogDispatcher extends Dispatcher implements channels.DialogChannel { _type_Dialog = true; - constructor(scope: BrowserContextDispatcher, dialog: Dialog) { - const page = PageDispatcher.fromNullable(scope, dialog.page().initializedOrUndefined()); - // Prefer scoping to the page, unless we don't have one. - super(page || scope, dialog, 'Dialog', { - page, + constructor(scope: PageDispatcher, dialog: Dialog) { + super(scope, dialog, 'Dialog', { type: dialog.type(), message: dialog.message(), defaultValue: dialog.defaultValue(), diff --git a/packages/playwright-core/src/server/dispatchers/pageDispatcher.ts b/packages/playwright-core/src/server/dispatchers/pageDispatcher.ts index 3e249692e2..b14b897c47 100644 --- a/packages/playwright-core/src/server/dispatchers/pageDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/pageDispatcher.ts @@ -20,6 +20,8 @@ import { Page, Worker } from '../page'; import type * as channels from '@protocol/channels'; import { Dispatcher, existingDispatcher } from './dispatcher'; import { parseError, serializeError } from '../../protocol/serializers'; +import { ConsoleMessageDispatcher } from './consoleMessageDispatcher'; +import { DialogDispatcher } from './dialogDispatcher'; import { FrameDispatcher } from './frameDispatcher'; import { RequestDispatcher } from './networkDispatchers'; import { ResponseDispatcher } from './networkDispatchers'; @@ -74,7 +76,9 @@ export class PageDispatcher extends Dispatcher this._dispatchEvent('console', { message: new ConsoleMessageDispatcher(this, message) })); this.addObjectListener(Page.Events.Crash, () => this._dispatchEvent('crash')); + this.addObjectListener(Page.Events.Dialog, dialog => this._dispatchEvent('dialog', { dialog: new DialogDispatcher(this, dialog) })); this.addObjectListener(Page.Events.Download, (download: Download) => { // Artifact can outlive the page, so bind to the context scope. this._dispatchEvent('download', { url: download.url, suggestedFilename: download.suggestedFilename(), artifact: ArtifactDispatcher.from(parentScope, download.artifact) }); diff --git a/packages/playwright-core/src/server/firefox/ffPage.ts b/packages/playwright-core/src/server/firefox/ffPage.ts index d3db91f9e1..47be1684d6 100644 --- a/packages/playwright-core/src/server/firefox/ffPage.ts +++ b/packages/playwright-core/src/server/firefox/ffPage.ts @@ -34,7 +34,6 @@ import type { Progress } from '../progress'; import { splitErrorMessage } from '../../utils/stackTrace'; import { debugLogger } from '../../common/debugLogger'; import { ManualPromise } from '../../utils/manualPromise'; -import { BrowserContext } from '../browserContext'; export const UTILITY_WORLD_NAME = '__playwright_utility_world__'; @@ -258,7 +257,7 @@ export class FFPage implements PageDelegate { } _onDialogOpened(params: Protocol.Page.dialogOpenedPayload) { - this._page.emitOnContext(BrowserContext.Events.Dialog, new dialog.Dialog( + this._page.emit(Page.Events.Dialog, new dialog.Dialog( this._page, params.type, params.message, diff --git a/packages/playwright-core/src/server/frames.ts b/packages/playwright-core/src/server/frames.ts index c9c0afc904..b53b1e6305 100644 --- a/packages/playwright-core/src/server/frames.ts +++ b/packages/playwright-core/src/server/frames.ts @@ -1032,8 +1032,8 @@ export class Frame extends SdkObject { let cspMessage: ConsoleMessage | undefined; const actionPromise = func().then(r => result = r).catch(e => error = e); const errorPromise = new Promise(resolve => { - listeners.push(eventsHelper.addEventListener(this._page._browserContext, BrowserContext.Events.Console, (message: ConsoleMessage) => { - if (message.page() === this._page && message.type() === 'error' && message.text().includes('Content Security Policy')) { + listeners.push(eventsHelper.addEventListener(this._page, Page.Events.Console, (message: ConsoleMessage) => { + if (message.type() === 'error' && message.text().includes('Content Security Policy')) { cspMessage = message; resolve(); } diff --git a/packages/playwright-core/src/server/page.ts b/packages/playwright-core/src/server/page.ts index 11c577dfa3..2d69d956c1 100644 --- a/packages/playwright-core/src/server/page.ts +++ b/packages/playwright-core/src/server/page.ts @@ -121,6 +121,8 @@ export class Page extends SdkObject { static Events = { Close: 'close', Crash: 'crash', + Console: 'console', + Dialog: 'dialog', Download: 'download', FileChooser: 'filechooser', // Can't use just 'error' due to node.js special treatment of error events. @@ -139,7 +141,6 @@ export class Page extends SdkObject { private _closedPromise = new ManualPromise(); private _disconnected = false; private _initialized = false; - private _eventsToEmitAfterInitialized: { event: string | symbol, args: any[] }[] = []; readonly _disconnectedPromise = new ManualPromise(); readonly _crashedPromise = new ManualPromise(); readonly _browserContext: BrowserContext; @@ -207,18 +208,12 @@ export class Page extends SdkObject { } this._initialized = true; this.emitOnContext(contextEvent, this); - - for (const { event, args } of this._eventsToEmitAfterInitialized) - this._browserContext.emit(event, ...args); - this._eventsToEmitAfterInitialized = []; - - // It may happen that page initialization finishes after Close event has already been sent, + // I may happen that page initialization finishes after Close event has already been sent, // in that case we fire another Close event to ensure that each reported Page will have // corresponding Close event after it is reported on the context. if (this.isClosed()) this.emit(Page.Events.Close); - else - this.instrumentation.onPageOpen(this); + this.instrumentation.onPageOpen(this); } initializedOrUndefined() { @@ -231,19 +226,6 @@ export class Page extends SdkObject { this._browserContext.emit(event, ...args); } - emitOnContextOnceInitialized(event: string | symbol, ...args: any[]) { - if (this._isServerSideOnly) - return; - // Some events, like console messages, may come before page is ready. - // In this case, postpone the event until page is initialized, - // and dispatch it to the client later, either on the live Page, - // or on the "errored" Page. - if (this._initialized) - this._browserContext.emit(event, ...args); - else - this._eventsToEmitAfterInitialized.push({ event, args }); - } - async resetForReuse(metadata: CallMetadata) { this.setDefaultNavigationTimeout(undefined); this.setDefaultTimeout(undefined); @@ -369,11 +351,10 @@ export class Page extends SdkObject { _addConsoleMessage(type: string, args: js.JSHandle[], location: types.ConsoleMessageLocation, text?: string) { const message = new ConsoleMessage(this, type, text, args, location); const intercepted = this._frameManager.interceptConsoleMessage(message); - if (intercepted) { + if (intercepted || !this.listenerCount(Page.Events.Console)) args.forEach(arg => arg.dispose()); - return; - } - this.emitOnContextOnceInitialized(BrowserContext.Events.Console, message); + else + this.emit(Page.Events.Console, message); } async reload(metadata: CallMetadata, options: types.NavigateOptions): Promise { diff --git a/packages/playwright-core/src/server/recorder.ts b/packages/playwright-core/src/server/recorder.ts index ddf5d44ec0..0dfbfab0b0 100644 --- a/packages/playwright-core/src/server/recorder.ts +++ b/packages/playwright-core/src/server/recorder.ts @@ -43,7 +43,6 @@ import { raceAgainstTimeout } from '../utils/timeoutRunner'; import type { Language, LanguageGenerator } from './recorder/language'; import { locatorOrSelectorAsSelector } from '../utils/isomorphic/locatorParser'; import { eventsHelper, type RegisteredListener } from './../utils/eventsHelper'; -import type { Dialog } from './dialog'; type BindingSource = { frame: Frame, page: Page }; @@ -426,10 +425,9 @@ class ContextRecorder extends EventEmitter { } async install() { - this._context.on(BrowserContext.Events.Page, (page: Page) => this._onPage(page)); + this._context.on(BrowserContext.Events.Page, page => this._onPage(page)); for (const page of this._context.pages()) this._onPage(page); - this._context.on(BrowserContext.Events.Dialog, (dialog: Dialog) => this._onDialog(dialog.page())); // Input actions that potentially lead to navigation are intercepted on the page and are // performed by the Playwright. @@ -473,6 +471,7 @@ class ContextRecorder extends EventEmitter { this._onFrameNavigated(frame, page); }); page.on(Page.Events.Download, () => this._onDownload(page)); + page.on(Page.Events.Dialog, () => this._onDialog(page)); const suffix = this._pageAliases.size ? String(++this._lastPopupOrdinal) : ''; const pageAlias = 'page' + suffix; this._pageAliases.set(page, pageAlias); diff --git a/packages/playwright-core/src/server/webkit/wkPage.ts b/packages/playwright-core/src/server/webkit/wkPage.ts index 99dc45e6af..1a336841ae 100644 --- a/packages/playwright-core/src/server/webkit/wkPage.ts +++ b/packages/playwright-core/src/server/webkit/wkPage.ts @@ -44,7 +44,6 @@ import { WKProvisionalPage } from './wkProvisionalPage'; import { WKWorkers } from './wkWorkers'; import { debugLogger } from '../../common/debugLogger'; import { ManualPromise } from '../../utils/manualPromise'; -import { BrowserContext } from '../browserContext'; const UTILITY_WORLD_NAME = '__playwright_utility_world__'; @@ -608,7 +607,7 @@ export class WKPage implements PageDelegate { } _onDialog(event: Protocol.Dialog.javascriptDialogOpeningPayload) { - this._page.emitOnContext(BrowserContext.Events.Dialog, new dialog.Dialog( + this._page.emit(Page.Events.Dialog, new dialog.Dialog( this._page, event.type as dialog.DialogType, event.message, diff --git a/packages/playwright-core/types/types.d.ts b/packages/playwright-core/types/types.d.ts index 62a72a4ff2..cc8b66d0ab 100644 --- a/packages/playwright-core/types/types.d.ts +++ b/packages/playwright-core/types/types.d.ts @@ -894,9 +894,9 @@ export interface Page { * Emitted when JavaScript within the page calls one of console API methods, e.g. `console.log` or `console.dir`. Also * emitted if the page throws an error or a warning. * - * The arguments passed into `console.log` are available on the [ConsoleMessage] event handler argument. + * The arguments passed into `console.log` appear as arguments on the event handler. * - * **Usage** + * An example of handling `console` event: * * ```js * page.on('console', async msg => { @@ -905,7 +905,7 @@ export interface Page { * values.push(await arg.jsonValue()); * console.log(...values); * }); - * await page.evaluate(() => console.log('hello', 5, { foo: 'bar' })); + * await page.evaluate(() => console.log('hello', 5, {foo: 'bar'})); * ``` * */ @@ -938,17 +938,14 @@ export interface Page { * will [freeze](https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop#never_blocking) waiting for the * dialog, and actions like click will never finish. * - * **Usage** - * * ```js * page.on('dialog', dialog => { * dialog.accept(); * }); * ``` * - * **NOTE** When no [page.on('dialog')](https://playwright.dev/docs/api/class-page#page-event-dialog) or - * [browserContext.on('dialog')](https://playwright.dev/docs/api/class-browsercontext#browser-context-event-dialog) - * listeners are present, all dialogs are automatically dismissed. + * **NOTE** When no [page.on('dialog')](https://playwright.dev/docs/api/class-page#page-event-dialog) listeners are + * present, all dialogs are automatically dismissed. */ on(event: 'dialog', listener: (dialog: Dialog) => void): this; @@ -1190,9 +1187,9 @@ export interface Page { * Emitted when JavaScript within the page calls one of console API methods, e.g. `console.log` or `console.dir`. Also * emitted if the page throws an error or a warning. * - * The arguments passed into `console.log` are available on the [ConsoleMessage] event handler argument. + * The arguments passed into `console.log` appear as arguments on the event handler. * - * **Usage** + * An example of handling `console` event: * * ```js * page.on('console', async msg => { @@ -1201,7 +1198,7 @@ export interface Page { * values.push(await arg.jsonValue()); * console.log(...values); * }); - * await page.evaluate(() => console.log('hello', 5, { foo: 'bar' })); + * await page.evaluate(() => console.log('hello', 5, {foo: 'bar'})); * ``` * */ @@ -1234,17 +1231,14 @@ export interface Page { * will [freeze](https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop#never_blocking) waiting for the * dialog, and actions like click will never finish. * - * **Usage** - * * ```js * page.on('dialog', dialog => { * dialog.accept(); * }); * ``` * - * **NOTE** When no [page.on('dialog')](https://playwright.dev/docs/api/class-page#page-event-dialog) or - * [browserContext.on('dialog')](https://playwright.dev/docs/api/class-browsercontext#browser-context-event-dialog) - * listeners are present, all dialogs are automatically dismissed. + * **NOTE** When no [page.on('dialog')](https://playwright.dev/docs/api/class-page#page-event-dialog) listeners are + * present, all dialogs are automatically dismissed. */ addListener(event: 'dialog', listener: (dialog: Dialog) => void): this; @@ -1581,9 +1575,9 @@ export interface Page { * Emitted when JavaScript within the page calls one of console API methods, e.g. `console.log` or `console.dir`. Also * emitted if the page throws an error or a warning. * - * The arguments passed into `console.log` are available on the [ConsoleMessage] event handler argument. + * The arguments passed into `console.log` appear as arguments on the event handler. * - * **Usage** + * An example of handling `console` event: * * ```js * page.on('console', async msg => { @@ -1592,7 +1586,7 @@ export interface Page { * values.push(await arg.jsonValue()); * console.log(...values); * }); - * await page.evaluate(() => console.log('hello', 5, { foo: 'bar' })); + * await page.evaluate(() => console.log('hello', 5, {foo: 'bar'})); * ``` * */ @@ -1625,17 +1619,14 @@ export interface Page { * will [freeze](https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop#never_blocking) waiting for the * dialog, and actions like click will never finish. * - * **Usage** - * * ```js * page.on('dialog', dialog => { * dialog.accept(); * }); * ``` * - * **NOTE** When no [page.on('dialog')](https://playwright.dev/docs/api/class-page#page-event-dialog) or - * [browserContext.on('dialog')](https://playwright.dev/docs/api/class-browsercontext#browser-context-event-dialog) - * listeners are present, all dialogs are automatically dismissed. + * **NOTE** When no [page.on('dialog')](https://playwright.dev/docs/api/class-page#page-event-dialog) listeners are + * present, all dialogs are automatically dismissed. */ prependListener(event: 'dialog', listener: (dialog: Dialog) => void): this; @@ -4222,9 +4213,9 @@ export interface Page { * Emitted when JavaScript within the page calls one of console API methods, e.g. `console.log` or `console.dir`. Also * emitted if the page throws an error or a warning. * - * The arguments passed into `console.log` are available on the [ConsoleMessage] event handler argument. + * The arguments passed into `console.log` appear as arguments on the event handler. * - * **Usage** + * An example of handling `console` event: * * ```js * page.on('console', async msg => { @@ -4233,7 +4224,7 @@ export interface Page { * values.push(await arg.jsonValue()); * console.log(...values); * }); - * await page.evaluate(() => console.log('hello', 5, { foo: 'bar' })); + * await page.evaluate(() => console.log('hello', 5, {foo: 'bar'})); * ``` * */ @@ -4266,17 +4257,14 @@ export interface Page { * will [freeze](https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop#never_blocking) waiting for the * dialog, and actions like click will never finish. * - * **Usage** - * * ```js * page.on('dialog', dialog => { * dialog.accept(); * }); * ``` * - * **NOTE** When no [page.on('dialog')](https://playwright.dev/docs/api/class-page#page-event-dialog) or - * [browserContext.on('dialog')](https://playwright.dev/docs/api/class-browsercontext#browser-context-event-dialog) - * listeners are present, all dialogs are automatically dismissed. + * **NOTE** When no [page.on('dialog')](https://playwright.dev/docs/api/class-page#page-event-dialog) listeners are + * present, all dialogs are automatically dismissed. */ waitForEvent(event: 'dialog', optionsOrPredicate?: { predicate?: (dialog: Dialog) => boolean | Promise, timeout?: number } | ((dialog: Dialog) => boolean | Promise)): Promise; @@ -7496,48 +7484,6 @@ export interface BrowserContext { */ on(event: 'close', listener: (browserContext: BrowserContext) => void): this; - /** - * Emitted when JavaScript within the page calls one of console API methods, e.g. `console.log` or `console.dir`. Also - * emitted if the page throws an error or a warning. - * - * The arguments passed into `console.log` and the page are available on the [ConsoleMessage] event handler argument. - * - * **Usage** - * - * ```js - * context.on('console', async msg => { - * const values = []; - * for (const arg of msg.args()) - * values.push(await arg.jsonValue()); - * console.log(...values); - * }); - * await page.evaluate(() => console.log('hello', 5, { foo: 'bar' })); - * ``` - * - */ - on(event: 'console', listener: (consoleMessage: ConsoleMessage) => void): this; - - /** - * Emitted when a JavaScript dialog appears, such as `alert`, `prompt`, `confirm` or `beforeunload`. Listener **must** - * either [dialog.accept([promptText])](https://playwright.dev/docs/api/class-dialog#dialog-accept) or - * [dialog.dismiss()](https://playwright.dev/docs/api/class-dialog#dialog-dismiss) the dialog - otherwise the page - * will [freeze](https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop#never_blocking) waiting for the - * dialog, and actions like click will never finish. - * - * **Usage** - * - * ```js - * context.on('dialog', dialog => { - * dialog.accept(); - * }); - * ``` - * - * **NOTE** When no [page.on('dialog')](https://playwright.dev/docs/api/class-page#page-event-dialog) or - * [browserContext.on('dialog')](https://playwright.dev/docs/api/class-browsercontext#browser-context-event-dialog) - * listeners are present, all dialogs are automatically dismissed. - */ - on(event: 'dialog', listener: (dialog: Dialog) => 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 @@ -7616,16 +7562,6 @@ export interface BrowserContext { */ once(event: 'close', listener: (browserContext: BrowserContext) => 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: 'console', listener: (consoleMessage: ConsoleMessage) => 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: 'dialog', listener: (dialog: Dialog) => void): this; - /** * Adds an event listener that will be automatically removed after it is triggered once. See `addListener` for more information about this event. */ @@ -7676,48 +7612,6 @@ export interface BrowserContext { */ addListener(event: 'close', listener: (browserContext: BrowserContext) => void): this; - /** - * Emitted when JavaScript within the page calls one of console API methods, e.g. `console.log` or `console.dir`. Also - * emitted if the page throws an error or a warning. - * - * The arguments passed into `console.log` and the page are available on the [ConsoleMessage] event handler argument. - * - * **Usage** - * - * ```js - * context.on('console', async msg => { - * const values = []; - * for (const arg of msg.args()) - * values.push(await arg.jsonValue()); - * console.log(...values); - * }); - * await page.evaluate(() => console.log('hello', 5, { foo: 'bar' })); - * ``` - * - */ - addListener(event: 'console', listener: (consoleMessage: ConsoleMessage) => void): this; - - /** - * Emitted when a JavaScript dialog appears, such as `alert`, `prompt`, `confirm` or `beforeunload`. Listener **must** - * either [dialog.accept([promptText])](https://playwright.dev/docs/api/class-dialog#dialog-accept) or - * [dialog.dismiss()](https://playwright.dev/docs/api/class-dialog#dialog-dismiss) the dialog - otherwise the page - * will [freeze](https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop#never_blocking) waiting for the - * dialog, and actions like click will never finish. - * - * **Usage** - * - * ```js - * context.on('dialog', dialog => { - * dialog.accept(); - * }); - * ``` - * - * **NOTE** When no [page.on('dialog')](https://playwright.dev/docs/api/class-page#page-event-dialog) or - * [browserContext.on('dialog')](https://playwright.dev/docs/api/class-browsercontext#browser-context-event-dialog) - * listeners are present, all dialogs are automatically dismissed. - */ - addListener(event: 'dialog', listener: (dialog: Dialog) => 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 @@ -7796,16 +7690,6 @@ export interface BrowserContext { */ removeListener(event: 'close', listener: (browserContext: BrowserContext) => void): this; - /** - * Removes an event listener added by `on` or `addListener`. - */ - removeListener(event: 'console', listener: (consoleMessage: ConsoleMessage) => void): this; - - /** - * Removes an event listener added by `on` or `addListener`. - */ - removeListener(event: 'dialog', listener: (dialog: Dialog) => void): this; - /** * Removes an event listener added by `on` or `addListener`. */ @@ -7846,16 +7730,6 @@ export interface BrowserContext { */ off(event: 'close', listener: (browserContext: BrowserContext) => void): this; - /** - * Removes an event listener added by `on` or `addListener`. - */ - off(event: 'console', listener: (consoleMessage: ConsoleMessage) => void): this; - - /** - * Removes an event listener added by `on` or `addListener`. - */ - off(event: 'dialog', listener: (dialog: Dialog) => void): this; - /** * Removes an event listener added by `on` or `addListener`. */ @@ -7906,48 +7780,6 @@ export interface BrowserContext { */ prependListener(event: 'close', listener: (browserContext: BrowserContext) => void): this; - /** - * Emitted when JavaScript within the page calls one of console API methods, e.g. `console.log` or `console.dir`. Also - * emitted if the page throws an error or a warning. - * - * The arguments passed into `console.log` and the page are available on the [ConsoleMessage] event handler argument. - * - * **Usage** - * - * ```js - * context.on('console', async msg => { - * const values = []; - * for (const arg of msg.args()) - * values.push(await arg.jsonValue()); - * console.log(...values); - * }); - * await page.evaluate(() => console.log('hello', 5, { foo: 'bar' })); - * ``` - * - */ - prependListener(event: 'console', listener: (consoleMessage: ConsoleMessage) => void): this; - - /** - * Emitted when a JavaScript dialog appears, such as `alert`, `prompt`, `confirm` or `beforeunload`. Listener **must** - * either [dialog.accept([promptText])](https://playwright.dev/docs/api/class-dialog#dialog-accept) or - * [dialog.dismiss()](https://playwright.dev/docs/api/class-dialog#dialog-dismiss) the dialog - otherwise the page - * will [freeze](https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop#never_blocking) waiting for the - * dialog, and actions like click will never finish. - * - * **Usage** - * - * ```js - * context.on('dialog', dialog => { - * dialog.accept(); - * }); - * ``` - * - * **NOTE** When no [page.on('dialog')](https://playwright.dev/docs/api/class-page#page-event-dialog) or - * [browserContext.on('dialog')](https://playwright.dev/docs/api/class-browsercontext#browser-context-event-dialog) - * listeners are present, all dialogs are automatically dismissed. - */ - prependListener(event: 'dialog', listener: (dialog: Dialog) => 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 @@ -8487,48 +8319,6 @@ export interface BrowserContext { */ waitForEvent(event: 'close', optionsOrPredicate?: { predicate?: (browserContext: BrowserContext) => boolean | Promise, timeout?: number } | ((browserContext: BrowserContext) => boolean | Promise)): Promise; - /** - * Emitted when JavaScript within the page calls one of console API methods, e.g. `console.log` or `console.dir`. Also - * emitted if the page throws an error or a warning. - * - * The arguments passed into `console.log` and the page are available on the [ConsoleMessage] event handler argument. - * - * **Usage** - * - * ```js - * context.on('console', async msg => { - * const values = []; - * for (const arg of msg.args()) - * values.push(await arg.jsonValue()); - * console.log(...values); - * }); - * await page.evaluate(() => console.log('hello', 5, { foo: 'bar' })); - * ``` - * - */ - waitForEvent(event: 'console', optionsOrPredicate?: { predicate?: (consoleMessage: ConsoleMessage) => boolean | Promise, timeout?: number } | ((consoleMessage: ConsoleMessage) => boolean | Promise)): Promise; - - /** - * Emitted when a JavaScript dialog appears, such as `alert`, `prompt`, `confirm` or `beforeunload`. Listener **must** - * either [dialog.accept([promptText])](https://playwright.dev/docs/api/class-dialog#dialog-accept) or - * [dialog.dismiss()](https://playwright.dev/docs/api/class-dialog#dialog-dismiss) the dialog - otherwise the page - * will [freeze](https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop#never_blocking) waiting for the - * dialog, and actions like click will never finish. - * - * **Usage** - * - * ```js - * context.on('dialog', dialog => { - * dialog.accept(); - * }); - * ``` - * - * **NOTE** When no [page.on('dialog')](https://playwright.dev/docs/api/class-page#page-event-dialog) or - * [browserContext.on('dialog')](https://playwright.dev/docs/api/class-browsercontext#browser-context-event-dialog) - * listeners are present, all dialogs are automatically dismissed. - */ - waitForEvent(event: 'dialog', optionsOrPredicate?: { predicate?: (dialog: Dialog) => boolean | Promise, timeout?: number } | ((dialog: Dialog) => boolean | Promise)): Promise; - /** * 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 @@ -16353,11 +16143,6 @@ export interface ConsoleMessage { columnNumber: number; }; - /** - * The page that produced this console message, if any. - */ - page(): Page|null; - /** * The text of the console message. */ @@ -16555,11 +16340,6 @@ export interface Dialog { */ message(): string; - /** - * The page that initiated this dialog, if available. - */ - page(): Page|null; - /** * Returns dialog's type, can be one of `alert`, `beforeunload`, `confirm` or `prompt`. */ diff --git a/packages/protocol/src/channels.ts b/packages/protocol/src/channels.ts index b353c9a6ec..32c287d289 100644 --- a/packages/protocol/src/channels.ts +++ b/packages/protocol/src/channels.ts @@ -1395,9 +1395,7 @@ export type BrowserContextInitializer = { }; export interface BrowserContextEventTarget { on(event: 'bindingCall', callback: (params: BrowserContextBindingCallEvent) => void): this; - on(event: 'console', callback: (params: BrowserContextConsoleEvent) => void): this; 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: 'route', callback: (params: BrowserContextRouteEvent) => void): this; on(event: 'video', callback: (params: BrowserContextVideoEvent) => void): this; @@ -1438,13 +1436,7 @@ export interface BrowserContextChannel extends BrowserContextEventTarget, EventT export type BrowserContextBindingCallEvent = { binding: BindingCallChannel, }; -export type BrowserContextConsoleEvent = { - message: ConsoleMessageChannel, -}; export type BrowserContextCloseEvent = {}; -export type BrowserContextDialogEvent = { - dialog: DialogChannel, -}; export type BrowserContextPageEvent = { page: PageChannel, }; @@ -1686,9 +1678,7 @@ export type BrowserContextUpdateSubscriptionResult = void; export interface BrowserContextEvents { 'bindingCall': BrowserContextBindingCallEvent; - 'console': BrowserContextConsoleEvent; 'close': BrowserContextCloseEvent; - 'dialog': BrowserContextDialogEvent; 'page': BrowserContextPageEvent; 'route': BrowserContextRouteEvent; 'video': BrowserContextVideoEvent; @@ -1713,7 +1703,9 @@ export type PageInitializer = { export interface PageEventTarget { on(event: 'bindingCall', callback: (params: PageBindingCallEvent) => void): this; on(event: 'close', callback: (params: PageCloseEvent) => void): this; + on(event: 'console', callback: (params: PageConsoleEvent) => void): this; on(event: 'crash', callback: (params: PageCrashEvent) => void): this; + on(event: 'dialog', callback: (params: PageDialogEvent) => void): this; on(event: 'download', callback: (params: PageDownloadEvent) => void): this; on(event: 'fileChooser', callback: (params: PageFileChooserEvent) => void): this; on(event: 'frameAttached', callback: (params: PageFrameAttachedEvent) => void): this; @@ -1764,7 +1756,13 @@ export type PageBindingCallEvent = { binding: BindingCallChannel, }; export type PageCloseEvent = {}; +export type PageConsoleEvent = { + message: ConsoleMessageChannel, +}; export type PageCrashEvent = {}; +export type PageDialogEvent = { + dialog: DialogChannel, +}; export type PageDownloadEvent = { url: string, suggestedFilename: string, @@ -2204,7 +2202,9 @@ export type PageUpdateSubscriptionResult = void; export interface PageEvents { 'bindingCall': PageBindingCallEvent; 'close': PageCloseEvent; + 'console': PageConsoleEvent; 'crash': PageCrashEvent; + 'dialog': PageDialogEvent; 'download': PageDownloadEvent; 'fileChooser': PageFileChooserEvent; 'frameAttached': PageFrameAttachedEvent; @@ -3694,7 +3694,6 @@ export interface WebSocketEvents { // ----------- ConsoleMessage ----------- export type ConsoleMessageInitializer = { - page: PageChannel, type: string, text: string, args: JSHandleChannel[], @@ -3747,7 +3746,6 @@ export interface BindingCallEvents { // ----------- Dialog ----------- export type DialogInitializer = { - page?: PageChannel, type: string, message: string, defaultValue: string, diff --git a/packages/protocol/src/protocol.yml b/packages/protocol/src/protocol.yml index 514b6ca783..005b67458b 100644 --- a/packages/protocol/src/protocol.yml +++ b/packages/protocol/src/protocol.yml @@ -1160,16 +1160,8 @@ BrowserContext: parameters: binding: BindingCall - console: - parameters: - message: ConsoleMessage - close: - dialog: - parameters: - dialog: Dialog - page: parameters: page: Page @@ -1584,8 +1576,16 @@ Page: close: + console: + parameters: + message: ConsoleMessage + crash: + dialog: + parameters: + dialog: Dialog + download: parameters: url: string @@ -2883,7 +2883,6 @@ ConsoleMessage: type: interface initializer: - page: Page type: string text: string args: @@ -2925,7 +2924,6 @@ Dialog: type: interface initializer: - page: Page? type: string message: string defaultValue: string diff --git a/tests/library/browsercontext-events.spec.ts b/tests/library/browsercontext-events.spec.ts deleted file mode 100644 index b6f84aafc7..0000000000 --- a/tests/library/browsercontext-events.spec.ts +++ /dev/null @@ -1,162 +0,0 @@ -/** - * Copyright (c) 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 { browserTest as test, expect } from '../config/browserTest'; - -test('console event should work @smoke', async ({ page }) => { - const [, message] = await Promise.all([ - page.evaluate(() => console.log('hello')), - page.context().waitForEvent('console'), - ]); - - expect(message.text()).toBe('hello'); - expect(message.page()).toBe(page); -}); - -test('console event should work in popup', async ({ page }) => { - const [, message, popup] = await Promise.all([ - page.evaluate(() => { - const win = window.open(''); - (win as any).console.log('hello'); - }), - page.context().waitForEvent('console'), - page.waitForEvent('popup'), - ]); - - expect(message.text()).toBe('hello'); - expect(message.page()).toBe(popup); -}); - -test('console event should work in popup 2', async ({ page, browserName }) => { - test.fixme(browserName === 'firefox', 'console message from javascript: url is not reported at all'); - - const [, message, popup] = await Promise.all([ - page.evaluate(async () => { - const win = window.open('javascript:console.log("hello")'); - await new Promise(f => setTimeout(f, 0)); - win.close(); - }), - page.context().waitForEvent('console', msg => msg.type() === 'log'), - page.context().waitForEvent('page'), - ]); - - expect(message.text()).toBe('hello'); - expect(message.page()).toBe(popup); -}); - -test('console event should work in immediately closed popup', async ({ page, browserName }) => { - test.fixme(browserName === 'firefox', 'console message is not reported at all'); - - const [, message, popup] = await Promise.all([ - page.evaluate(async () => { - const win = window.open(); - (win as any).console.log('hello'); - win.close(); - }), - page.context().waitForEvent('console'), - page.waitForEvent('popup'), - ]); - - expect(message.text()).toBe('hello'); - expect(message.page()).toBe(popup); -}); - -test('dialog event should work @smoke', async ({ page }) => { - const promise = page.evaluate(() => prompt('hey?')); - const [dialog1, dialog2] = await Promise.all([ - page.context().waitForEvent('dialog'), - page.waitForEvent('dialog'), - ]); - - expect(dialog1).toBe(dialog2); - expect(dialog1.message()).toBe('hey?'); - expect(dialog1.page()).toBe(page); - await dialog1.accept('hello'); - expect(await promise).toBe('hello'); -}); - -test('dialog event should work in popup', async ({ page }) => { - const promise = page.evaluate(() => { - const win = window.open(''); - return (win as any).prompt('hey?'); - }); - - const [dialog, popup] = await Promise.all([ - page.context().waitForEvent('dialog'), - page.waitForEvent('popup'), - ]); - - expect(dialog.message()).toBe('hey?'); - expect(dialog.page()).toBe(popup); - await dialog.accept('hello'); - expect(await promise).toBe('hello'); -}); - -test('dialog event should work in popup 2', async ({ page, browserName }) => { - test.fixme(browserName === 'firefox', 'dialog from javascript: url is not reported at all'); - - const promise = page.evaluate(async () => { - window.open('javascript:prompt("hey?")'); - }); - - const dialog = await page.context().waitForEvent('dialog'); - - expect(dialog.message()).toBe('hey?'); - expect(dialog.page()).toBe(null); - await dialog.accept('hello'); - await promise; -}); - -test('dialog event should work in immdiately closed popup', async ({ page }) => { - const promise = page.evaluate(async () => { - const win = window.open(); - const result = (win as any).prompt('hey?'); - win.close(); - return result; - }); - - const [dialog, popup] = await Promise.all([ - page.context().waitForEvent('dialog'), - page.waitForEvent('popup'), - ]); - - expect(dialog.message()).toBe('hey?'); - expect(dialog.page()).toBe(popup); - await dialog.accept('hello'); - expect(await promise).toBe('hello'); -}); - -test('dialog event should work with inline script tag', async ({ page, server }) => { - server.setRoute('/popup.html', (req, res) => { - res.setHeader('content-type', 'text/html'); - res.end(``); - }); - - await page.goto(server.EMPTY_PAGE); - await page.setContent(`Click me`); - - const promise = page.click('a'); - const [dialog, popup] = await Promise.all([ - page.context().waitForEvent('dialog'), - page.context().waitForEvent('page'), - ]); - - expect(dialog.message()).toBe('hey?'); - expect(dialog.page()).toBe(popup); - await dialog.accept('hello'); - await promise; - await expect.poll(() => popup.evaluate('window.result')).toBe('hello'); -}); diff --git a/tests/library/trace-viewer.spec.ts b/tests/library/trace-viewer.spec.ts index 6950fda119..faa3cb5f4b 100644 --- a/tests/library/trace-viewer.spec.ts +++ b/tests/library/trace-viewer.spec.ts @@ -122,7 +122,7 @@ test('should contain action info', async ({ showTraceViewer }) => { test('should render events', async ({ showTraceViewer }) => { const traceViewer = await showTraceViewer([traceFile]); const events = await traceViewer.eventBars(); - expect(events).toContain('browsercontext_console'); + expect(events).toContain('page_console'); }); test('should render console', async ({ showTraceViewer, browserName }) => {