mirror of
https://github.com/microsoft/playwright.git
synced 2024-12-11 12:33:45 +03:00
feat: browserContext.on('dialog'/'console') (#22805)
Relanding #22033 and #21943.
This commit is contained in:
parent
ae3398d404
commit
236c329ea9
@ -94,6 +94,102 @@ 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<object>());
|
||||
};
|
||||
|
||||
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]>
|
||||
|
@ -125,6 +125,12 @@ 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]>
|
||||
|
@ -137,6 +137,12 @@ 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]>
|
||||
|
@ -163,12 +163,11 @@ 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` appear as arguments on the event handler.
|
||||
The arguments passed into `console.log` are available on the [ConsoleMessage] event handler argument.
|
||||
|
||||
An example of handling `console` event:
|
||||
**Usage**
|
||||
|
||||
```js
|
||||
page.on('console', async msg => {
|
||||
@ -177,7 +176,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
|
||||
@ -185,7 +184,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
|
||||
@ -196,7 +195,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
|
||||
@ -205,7 +204,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
|
||||
@ -286,6 +285,8 @@ 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();
|
||||
@ -310,7 +311,7 @@ page.RequestFailed += (_, request) =>
|
||||
```
|
||||
|
||||
:::note
|
||||
When no [`event: Page.dialog`] listeners are present, all dialogs are automatically dismissed.
|
||||
When no [`event: Page.dialog`] or [`event: BrowserContext.dialog`] listeners are present, all dialogs are automatically dismissed.
|
||||
:::
|
||||
|
||||
## event: Page.DOMContentLoaded
|
||||
|
@ -39,6 +39,8 @@ import { Artifact } from './artifact';
|
||||
import { APIRequestContext } from './fetch';
|
||||
import { rewriteErrorMessage } from '../utils/stackTrace';
|
||||
import { HarRouter } from './harRouter';
|
||||
import { ConsoleMessage } from './consoleMessage';
|
||||
import { Dialog } from './dialog';
|
||||
|
||||
export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel> implements api.BrowserContext {
|
||||
_pages = new Set<Page>();
|
||||
@ -91,6 +93,26 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel>
|
||||
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));
|
||||
|
@ -19,6 +19,7 @@ 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'];
|
||||
|
||||
@ -27,8 +28,18 @@ export class ConsoleMessage extends ChannelOwner<channels.ConsoleMessageChannel>
|
||||
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 {
|
||||
|
@ -17,14 +17,24 @@
|
||||
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<channels.DialogChannel> 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 {
|
||||
|
@ -35,7 +35,9 @@ export const Events = {
|
||||
},
|
||||
|
||||
BrowserContext: {
|
||||
Console: 'console',
|
||||
Close: 'close',
|
||||
Dialog: 'dialog',
|
||||
Page: 'page',
|
||||
BackgroundPage: 'backgroundpage',
|
||||
ServiceWorker: 'serviceworker',
|
||||
|
@ -31,9 +31,7 @@ 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';
|
||||
@ -124,17 +122,7 @@ export class Page extends ChannelOwner<channels.PageChannel> 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));
|
||||
|
@ -760,7 +760,13 @@ 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']),
|
||||
});
|
||||
@ -935,13 +941,7 @@ 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,
|
||||
@ -2084,6 +2084,7 @@ scheme.WebSocketSocketErrorEvent = tObject({
|
||||
});
|
||||
scheme.WebSocketCloseEvent = tOptional(tObject({}));
|
||||
scheme.ConsoleMessageInitializer = tObject({
|
||||
page: tChannel(['Page']),
|
||||
type: tString,
|
||||
text: tString,
|
||||
args: tArray(tChannel(['ElementHandle', 'JSHandle'])),
|
||||
@ -2108,6 +2109,7 @@ scheme.BindingCallResolveParams = tObject({
|
||||
});
|
||||
scheme.BindingCallResolveResult = tOptional(tObject({}));
|
||||
scheme.DialogInitializer = tObject({
|
||||
page: tOptional(tChannel(['Page'])),
|
||||
type: tString,
|
||||
message: tString,
|
||||
defaultValue: tString,
|
||||
|
@ -44,7 +44,9 @@ 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',
|
||||
|
@ -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.emit(Page.Events.Dialog, new dialog.Dialog(
|
||||
this._page.emitOnContext(BrowserContext.Events.Dialog, new dialog.Dialog(
|
||||
this._page,
|
||||
event.type,
|
||||
event.message,
|
||||
|
@ -17,21 +17,28 @@
|
||||
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(parent: SdkObject, type: string, text: string | undefined, args: js.JSHandle[], location?: ConsoleMessageLocation) {
|
||||
super(parent, 'console-message');
|
||||
constructor(page: Page, type: string, text: string | undefined, args: js.JSHandle[], location?: ConsoleMessageLocation) {
|
||||
super(page, 'console-message');
|
||||
this._page = page;
|
||||
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;
|
||||
}
|
||||
|
@ -41,6 +41,10 @@ export class Dialog extends SdkObject {
|
||||
this._page._frameManager.dialogDidOpen(this);
|
||||
}
|
||||
|
||||
page() {
|
||||
return this._page;
|
||||
}
|
||||
|
||||
type(): string {
|
||||
return this._type;
|
||||
}
|
||||
|
@ -33,6 +33,8 @@ 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<BrowserContext, channels.BrowserContextChannel, DispatcherScope> implements channels.BrowserContextChannel {
|
||||
_type_EventTarget = true;
|
||||
@ -79,6 +81,8 @@ export class BrowserContextDispatcher extends Dispatcher<BrowserContext, channel
|
||||
this._dispatchEvent('close');
|
||||
this._dispose();
|
||||
});
|
||||
this.addObjectListener(BrowserContext.Events.Console, message => 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())
|
||||
|
@ -23,12 +23,13 @@ import { ElementHandleDispatcher } from './elementHandlerDispatcher';
|
||||
export class ConsoleMessageDispatcher extends Dispatcher<ConsoleMessage, channels.ConsoleMessageChannel, PageDispatcher> implements channels.ConsoleMessageChannel {
|
||||
_type_ConsoleMessage = true;
|
||||
|
||||
constructor(scope: PageDispatcher, message: ConsoleMessage) {
|
||||
super(scope, message, 'ConsoleMessage', {
|
||||
constructor(page: PageDispatcher, message: ConsoleMessage) {
|
||||
super(page, message, 'ConsoleMessage', {
|
||||
type: message.type(),
|
||||
text: message.text(),
|
||||
args: message.args().map(a => ElementHandleDispatcher.fromJSHandle(scope, a)),
|
||||
args: message.args().map(a => ElementHandleDispatcher.fromJSHandle(page, a)),
|
||||
location: message.location(),
|
||||
page,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -17,13 +17,17 @@
|
||||
import type { Dialog } from '../dialog';
|
||||
import type * as channels from '@protocol/channels';
|
||||
import { Dispatcher } from './dispatcher';
|
||||
import type { PageDispatcher } from './pageDispatcher';
|
||||
import { PageDispatcher } from './pageDispatcher';
|
||||
import type { BrowserContextDispatcher } from './browserContextDispatcher';
|
||||
|
||||
export class DialogDispatcher extends Dispatcher<Dialog, channels.DialogChannel, PageDispatcher> implements channels.DialogChannel {
|
||||
export class DialogDispatcher extends Dispatcher<Dialog, channels.DialogChannel, BrowserContextDispatcher | PageDispatcher> implements channels.DialogChannel {
|
||||
_type_Dialog = true;
|
||||
|
||||
constructor(scope: PageDispatcher, dialog: Dialog) {
|
||||
super(scope, dialog, 'Dialog', {
|
||||
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,
|
||||
type: dialog.type(),
|
||||
message: dialog.message(),
|
||||
defaultValue: dialog.defaultValue(),
|
||||
|
@ -20,8 +20,6 @@ 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';
|
||||
@ -76,9 +74,7 @@ export class PageDispatcher extends Dispatcher<Page, channels.PageChannel, Brows
|
||||
this._dispatchEvent('close');
|
||||
this._dispose();
|
||||
});
|
||||
this.addObjectListener(Page.Events.Console, message => 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) });
|
||||
|
@ -34,6 +34,7 @@ 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__';
|
||||
|
||||
@ -257,7 +258,7 @@ export class FFPage implements PageDelegate {
|
||||
}
|
||||
|
||||
_onDialogOpened(params: Protocol.Page.dialogOpenedPayload) {
|
||||
this._page.emit(Page.Events.Dialog, new dialog.Dialog(
|
||||
this._page.emitOnContext(BrowserContext.Events.Dialog, new dialog.Dialog(
|
||||
this._page,
|
||||
params.type,
|
||||
params.message,
|
||||
|
@ -1036,8 +1036,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<void>(resolve => {
|
||||
listeners.push(eventsHelper.addEventListener(this._page, Page.Events.Console, (message: ConsoleMessage) => {
|
||||
if (message.type() === 'error' && message.text().includes('Content Security Policy')) {
|
||||
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')) {
|
||||
cspMessage = message;
|
||||
resolve();
|
||||
}
|
||||
|
@ -121,8 +121,6 @@ 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.
|
||||
@ -141,6 +139,7 @@ export class Page extends SdkObject {
|
||||
private _closedPromise = new ManualPromise<void>();
|
||||
private _disconnected = false;
|
||||
private _initialized = false;
|
||||
private _eventsToEmitAfterInitialized: { event: string | symbol, args: any[] }[] = [];
|
||||
readonly _disconnectedPromise = new ManualPromise<Error>();
|
||||
readonly _crashedPromise = new ManualPromise<Error>();
|
||||
readonly _browserContext: BrowserContext;
|
||||
@ -208,12 +207,18 @@ export class Page extends SdkObject {
|
||||
}
|
||||
this._initialized = true;
|
||||
this.emitOnContext(contextEvent, this);
|
||||
// I may happen that page initialization finishes after Close event has already been sent,
|
||||
|
||||
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,
|
||||
// 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);
|
||||
this.instrumentation.onPageOpen(this);
|
||||
else
|
||||
this.instrumentation.onPageOpen(this);
|
||||
}
|
||||
|
||||
initializedOrUndefined() {
|
||||
@ -226,6 +231,19 @@ 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);
|
||||
@ -351,10 +369,11 @@ 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 || !this.listenerCount(Page.Events.Console))
|
||||
if (intercepted) {
|
||||
args.forEach(arg => arg.dispose());
|
||||
else
|
||||
this.emit(Page.Events.Console, message);
|
||||
return;
|
||||
}
|
||||
this.emitOnContextOnceInitialized(BrowserContext.Events.Console, message);
|
||||
}
|
||||
|
||||
async reload(metadata: CallMetadata, options: types.NavigateOptions): Promise<network.Response | null> {
|
||||
|
@ -43,6 +43,7 @@ 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 };
|
||||
|
||||
@ -424,9 +425,10 @@ class ContextRecorder extends EventEmitter {
|
||||
}
|
||||
|
||||
async install() {
|
||||
this._context.on(BrowserContext.Events.Page, page => this._onPage(page));
|
||||
this._context.on(BrowserContext.Events.Page, (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.
|
||||
@ -470,7 +472,6 @@ 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);
|
||||
|
@ -44,6 +44,7 @@ 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__';
|
||||
|
||||
@ -607,7 +608,7 @@ export class WKPage implements PageDelegate {
|
||||
}
|
||||
|
||||
_onDialog(event: Protocol.Dialog.javascriptDialogOpeningPayload) {
|
||||
this._page.emit(Page.Events.Dialog, new dialog.Dialog(
|
||||
this._page.emitOnContext(BrowserContext.Events.Dialog, new dialog.Dialog(
|
||||
this._page,
|
||||
event.type as dialog.DialogType,
|
||||
event.message,
|
||||
|
260
packages/playwright-core/types/types.d.ts
vendored
260
packages/playwright-core/types/types.d.ts
vendored
@ -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` appear as arguments on the event handler.
|
||||
* The arguments passed into `console.log` are available on the [ConsoleMessage] event handler argument.
|
||||
*
|
||||
* An example of handling `console` event:
|
||||
* **Usage**
|
||||
*
|
||||
* ```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,14 +938,17 @@ 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) listeners are
|
||||
* present, all dialogs are automatically dismissed.
|
||||
* **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;
|
||||
|
||||
@ -1187,9 +1190,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` appear as arguments on the event handler.
|
||||
* The arguments passed into `console.log` are available on the [ConsoleMessage] event handler argument.
|
||||
*
|
||||
* An example of handling `console` event:
|
||||
* **Usage**
|
||||
*
|
||||
* ```js
|
||||
* page.on('console', async msg => {
|
||||
@ -1198,7 +1201,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' }));
|
||||
* ```
|
||||
*
|
||||
*/
|
||||
@ -1231,14 +1234,17 @@ 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) listeners are
|
||||
* present, all dialogs are automatically dismissed.
|
||||
* **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;
|
||||
|
||||
@ -1575,9 +1581,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` appear as arguments on the event handler.
|
||||
* The arguments passed into `console.log` are available on the [ConsoleMessage] event handler argument.
|
||||
*
|
||||
* An example of handling `console` event:
|
||||
* **Usage**
|
||||
*
|
||||
* ```js
|
||||
* page.on('console', async msg => {
|
||||
@ -1586,7 +1592,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' }));
|
||||
* ```
|
||||
*
|
||||
*/
|
||||
@ -1619,14 +1625,17 @@ 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) listeners are
|
||||
* present, all dialogs are automatically dismissed.
|
||||
* **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;
|
||||
|
||||
@ -4227,9 +4236,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` appear as arguments on the event handler.
|
||||
* The arguments passed into `console.log` are available on the [ConsoleMessage] event handler argument.
|
||||
*
|
||||
* An example of handling `console` event:
|
||||
* **Usage**
|
||||
*
|
||||
* ```js
|
||||
* page.on('console', async msg => {
|
||||
@ -4238,7 +4247,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' }));
|
||||
* ```
|
||||
*
|
||||
*/
|
||||
@ -4271,14 +4280,17 @@ 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) listeners are
|
||||
* present, all dialogs are automatically dismissed.
|
||||
* **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<boolean>, timeout?: number } | ((dialog: Dialog) => boolean | Promise<boolean>)): Promise<Dialog>;
|
||||
|
||||
@ -7514,6 +7526,48 @@ 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
|
||||
@ -7592,6 +7646,16 @@ 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.
|
||||
*/
|
||||
@ -7642,6 +7706,48 @@ 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
|
||||
@ -7720,6 +7826,16 @@ 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`.
|
||||
*/
|
||||
@ -7760,6 +7876,16 @@ 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`.
|
||||
*/
|
||||
@ -7810,6 +7936,48 @@ 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
|
||||
@ -8349,6 +8517,48 @@ export interface BrowserContext {
|
||||
*/
|
||||
waitForEvent(event: 'close', optionsOrPredicate?: { predicate?: (browserContext: BrowserContext) => boolean | Promise<boolean>, timeout?: number } | ((browserContext: BrowserContext) => boolean | Promise<boolean>)): Promise<BrowserContext>;
|
||||
|
||||
/**
|
||||
* 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<boolean>, timeout?: number } | ((consoleMessage: ConsoleMessage) => boolean | Promise<boolean>)): Promise<ConsoleMessage>;
|
||||
|
||||
/**
|
||||
* 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<boolean>, timeout?: number } | ((dialog: Dialog) => boolean | Promise<boolean>)): Promise<Dialog>;
|
||||
|
||||
/**
|
||||
* 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
|
||||
@ -16196,6 +16406,11 @@ export interface ConsoleMessage {
|
||||
columnNumber: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* The page that produced this console message, if any.
|
||||
*/
|
||||
page(): Page|null;
|
||||
|
||||
/**
|
||||
* The text of the console message.
|
||||
*/
|
||||
@ -16393,6 +16608,11 @@ 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`.
|
||||
*/
|
||||
|
@ -1401,7 +1401,9 @@ 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;
|
||||
@ -1442,7 +1444,13 @@ 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,7 +1694,9 @@ export type BrowserContextUpdateSubscriptionResult = void;
|
||||
|
||||
export interface BrowserContextEvents {
|
||||
'bindingCall': BrowserContextBindingCallEvent;
|
||||
'console': BrowserContextConsoleEvent;
|
||||
'close': BrowserContextCloseEvent;
|
||||
'dialog': BrowserContextDialogEvent;
|
||||
'page': BrowserContextPageEvent;
|
||||
'route': BrowserContextRouteEvent;
|
||||
'video': BrowserContextVideoEvent;
|
||||
@ -1711,9 +1721,7 @@ 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,13 +1772,7 @@ 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,
|
||||
@ -2210,9 +2212,7 @@ export type PageUpdateSubscriptionResult = void;
|
||||
export interface PageEvents {
|
||||
'bindingCall': PageBindingCallEvent;
|
||||
'close': PageCloseEvent;
|
||||
'console': PageConsoleEvent;
|
||||
'crash': PageCrashEvent;
|
||||
'dialog': PageDialogEvent;
|
||||
'download': PageDownloadEvent;
|
||||
'fileChooser': PageFileChooserEvent;
|
||||
'frameAttached': PageFrameAttachedEvent;
|
||||
@ -3702,6 +3702,7 @@ export interface WebSocketEvents {
|
||||
|
||||
// ----------- ConsoleMessage -----------
|
||||
export type ConsoleMessageInitializer = {
|
||||
page: PageChannel,
|
||||
type: string,
|
||||
text: string,
|
||||
args: JSHandleChannel[],
|
||||
@ -3754,6 +3755,7 @@ export interface BindingCallEvents {
|
||||
|
||||
// ----------- Dialog -----------
|
||||
export type DialogInitializer = {
|
||||
page?: PageChannel,
|
||||
type: string,
|
||||
message: string,
|
||||
defaultValue: string,
|
||||
|
@ -1165,8 +1165,16 @@ BrowserContext:
|
||||
parameters:
|
||||
binding: BindingCall
|
||||
|
||||
console:
|
||||
parameters:
|
||||
message: ConsoleMessage
|
||||
|
||||
close:
|
||||
|
||||
dialog:
|
||||
parameters:
|
||||
dialog: Dialog
|
||||
|
||||
page:
|
||||
parameters:
|
||||
page: Page
|
||||
@ -1595,16 +1603,8 @@ Page:
|
||||
|
||||
close:
|
||||
|
||||
console:
|
||||
parameters:
|
||||
message: ConsoleMessage
|
||||
|
||||
crash:
|
||||
|
||||
dialog:
|
||||
parameters:
|
||||
dialog: Dialog
|
||||
|
||||
download:
|
||||
parameters:
|
||||
url: string
|
||||
@ -2935,6 +2935,7 @@ ConsoleMessage:
|
||||
type: interface
|
||||
|
||||
initializer:
|
||||
page: Page
|
||||
type: string
|
||||
text: string
|
||||
args:
|
||||
@ -2976,6 +2977,7 @@ Dialog:
|
||||
type: interface
|
||||
|
||||
initializer:
|
||||
page: Page?
|
||||
type: string
|
||||
message: string
|
||||
defaultValue: string
|
||||
|
162
tests/library/browsercontext-events.spec.ts
Normal file
162
tests/library/browsercontext-events.spec.ts
Normal file
@ -0,0 +1,162 @@
|
||||
/**
|
||||
* 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(`<script>window.result = prompt('hey?')</script>`);
|
||||
});
|
||||
|
||||
await page.goto(server.EMPTY_PAGE);
|
||||
await page.setContent(`<a href='popup.html' target=_blank>Click me</a>`);
|
||||
|
||||
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');
|
||||
});
|
@ -125,7 +125,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('page_console');
|
||||
expect(events).toContain('browsercontext_console');
|
||||
});
|
||||
|
||||
test('should render console', async ({ showTraceViewer, browserName }) => {
|
||||
|
Loading…
Reference in New Issue
Block a user