diff --git a/docs/src/api/class-browsercontext.md b/docs/src/api/class-browsercontext.md index 98a1c7d392..e414e0e89d 100644 --- a/docs/src/api/class-browsercontext.md +++ b/docs/src/api/class-browsercontext.md @@ -106,7 +106,7 @@ The reason to be reported to the operations interrupted by the context closure. - 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`. The arguments passed into `console.log` and the page are available on the [ConsoleMessage] event handler argument. diff --git a/docs/src/api/class-electronapplication.md b/docs/src/api/class-electronapplication.md index 06de31dce4..8a4b2d6850 100644 --- a/docs/src/api/class-electronapplication.md +++ b/docs/src/api/class-electronapplication.md @@ -41,6 +41,26 @@ const { _electron: electron } = require('playwright'); This event is issued when the application closes. +## event: ElectronApplication.console +* since: v1.42 +- argument: <[ConsoleMessage]> + +Emitted when JavaScript within the Electron main process calls one of console API methods, e.g. `console.log` or `console.dir`. + +The arguments passed into `console.log` are available on the [ConsoleMessage] event handler argument. + +**Usage** + +```js +electronApp.on('console', async msg => { + const values = []; + for (const arg of msg.args()) + values.push(await arg.jsonValue()); + console.log(...values); +}); +await electronApp.evaluate(() => console.log('hello', 5, { foo: 'bar' })); +``` + ## event: ElectronApplication.window * since: v1.9 - argument: <[Page]> diff --git a/docs/src/api/class-page.md b/docs/src/api/class-page.md index 4f4b444a0b..796a3cecc7 100644 --- a/docs/src/api/class-page.md +++ b/docs/src/api/class-page.md @@ -163,7 +163,7 @@ 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`. The arguments passed into `console.log` are available on the [ConsoleMessage] event handler argument. diff --git a/packages/playwright-core/src/client/consoleMessage.ts b/packages/playwright-core/src/client/consoleMessage.ts index 1c55639ab5..fcdc3fd149 100644 --- a/packages/playwright-core/src/client/consoleMessage.ts +++ b/packages/playwright-core/src/client/consoleMessage.ts @@ -25,10 +25,10 @@ type ConsoleMessageLocation = channels.BrowserContextConsoleEvent['location']; export class ConsoleMessage implements api.ConsoleMessage { private _page: Page | null; - private _event: channels.BrowserContextConsoleEvent; + private _event: channels.BrowserContextConsoleEvent | channels.ElectronApplicationConsoleEvent; - constructor(event: channels.BrowserContextConsoleEvent) { - this._page = event.page ? Page.from(event.page) : null; + constructor(event: channels.BrowserContextConsoleEvent | channels.ElectronApplicationConsoleEvent) { + this._page = ('page' in event && event.page) ? Page.from(event.page) : null; this._event = event; } diff --git a/packages/playwright-core/src/client/electron.ts b/packages/playwright-core/src/client/electron.ts index 247a584367..f20bbfba19 100644 --- a/packages/playwright-core/src/client/electron.ts +++ b/packages/playwright-core/src/client/electron.ts @@ -26,6 +26,7 @@ import { envObjectToArray } from './clientHelper'; import { Events } from './events'; import { JSHandle, parseResult, serializeArgument } from './jsHandle'; import type { Page } from './page'; +import { ConsoleMessage } from './consoleMessage'; import type { Env, WaitForEventOptions, Headers, BrowserContextOptions } from './types'; import { Waiter } from './waiter'; import { TargetClosedError } from './errors'; @@ -81,6 +82,10 @@ export class ElectronApplication extends ChannelOwner this.emit(Events.ElectronApplication.Console, new ConsoleMessage(event))); + this._setEventToSubscriptionMapping(new Map([ + [Events.ElectronApplication.Console, 'console'], + ])); } process(): childProcess.ChildProcess { diff --git a/packages/playwright-core/src/client/events.ts b/packages/playwright-core/src/client/events.ts index 287e5915e5..a074b26f3d 100644 --- a/packages/playwright-core/src/client/events.ts +++ b/packages/playwright-core/src/client/events.ts @@ -91,6 +91,7 @@ export const Events = { ElectronApplication: { Close: 'close', + Console: 'console', Window: 'window', }, }; diff --git a/packages/playwright-core/src/protocol/validator.ts b/packages/playwright-core/src/protocol/validator.ts index 22a9cceeec..8aa42c4df7 100644 --- a/packages/playwright-core/src/protocol/validator.ts +++ b/packages/playwright-core/src/protocol/validator.ts @@ -765,7 +765,6 @@ scheme.BrowserContextBindingCallEvent = tObject({ binding: tChannel(['BindingCall']), }); scheme.BrowserContextConsoleEvent = tObject({ - page: tChannel(['Page']), type: tString, text: tString, args: tArray(tChannel(['ElementHandle', 'JSHandle'])), @@ -774,6 +773,7 @@ scheme.BrowserContextConsoleEvent = tObject({ lineNumber: tNumber, columnNumber: tNumber, }), + page: tChannel(['Page']), }); scheme.BrowserContextCloseEvent = tOptional(tObject({})); scheme.BrowserContextDialogEvent = tObject({ @@ -2258,6 +2258,16 @@ scheme.ElectronApplicationInitializer = tObject({ context: tChannel(['BrowserContext']), }); scheme.ElectronApplicationCloseEvent = tOptional(tObject({})); +scheme.ElectronApplicationConsoleEvent = tObject({ + type: tString, + text: tString, + args: tArray(tChannel(['ElementHandle', 'JSHandle'])), + location: tObject({ + url: tString, + lineNumber: tNumber, + columnNumber: tNumber, + }), +}); scheme.ElectronApplicationBrowserWindowParams = tObject({ page: tChannel(['Page']), }); @@ -2280,6 +2290,11 @@ scheme.ElectronApplicationEvaluateExpressionHandleParams = tObject({ scheme.ElectronApplicationEvaluateExpressionHandleResult = tObject({ handle: tChannel(['ElementHandle', 'JSHandle']), }); +scheme.ElectronApplicationUpdateSubscriptionParams = tObject({ + event: tEnum(['console']), + enabled: tBoolean, +}); +scheme.ElectronApplicationUpdateSubscriptionResult = tOptional(tObject({})); scheme.ElectronApplicationCloseParams = tOptional(tObject({})); scheme.ElectronApplicationCloseResult = tOptional(tObject({})); scheme.AndroidInitializer = tOptional(tObject({})); diff --git a/packages/playwright-core/src/server/console.ts b/packages/playwright-core/src/server/console.ts index 5b0de45a0f..e1faa7fb8f 100644 --- a/packages/playwright-core/src/server/console.ts +++ b/packages/playwright-core/src/server/console.ts @@ -14,20 +14,18 @@ * limitations under the License. */ -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 { +export class ConsoleMessage { private _type: string; private _text?: string; private _args: js.JSHandle[]; private _location: ConsoleMessageLocation; - private _page: Page; + private _page: Page | null; - constructor(page: Page, type: string, text: string | undefined, args: js.JSHandle[], location?: ConsoleMessageLocation) { - super(page, 'console-message'); + constructor(page: Page | null, type: string, text: string | undefined, args: js.JSHandle[], location?: ConsoleMessageLocation) { this._page = page; this._type = type; this._text = text; diff --git a/packages/playwright-core/src/server/dispatchers/browserContextDispatcher.ts b/packages/playwright-core/src/server/dispatchers/browserContextDispatcher.ts index e9505de2c0..b4a06a67b5 100644 --- a/packages/playwright-core/src/server/dispatchers/browserContextDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/browserContextDispatcher.ts @@ -90,8 +90,9 @@ export class BrowserContextDispatcher extends Dispatcher { - if (this._shouldDispatchEvent(message.page(), 'console')) { - const pageDispatcher = PageDispatcher.from(this, message.page()); + const page = message.page()!; + if (this._shouldDispatchEvent(page, 'console')) { + const pageDispatcher = PageDispatcher.from(this, page); this._dispatchEvent('console', { page: pageDispatcher, type: message.type(), diff --git a/packages/playwright-core/src/server/dispatchers/electronDispatcher.ts b/packages/playwright-core/src/server/dispatchers/electronDispatcher.ts index a636ac8c53..a4e28da26f 100644 --- a/packages/playwright-core/src/server/dispatchers/electronDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/electronDispatcher.ts @@ -21,6 +21,7 @@ import { ElectronApplication } from '../electron/electron'; import type * as channels from '@protocol/channels'; import { BrowserContextDispatcher } from './browserContextDispatcher'; import type { PageDispatcher } from './pageDispatcher'; +import type { ConsoleMessage } from '../console'; import { parseArgument, serializeResult } from './jsHandleDispatcher'; import { ElementHandleDispatcher } from './elementHandlerDispatcher'; @@ -40,6 +41,7 @@ export class ElectronDispatcher extends Dispatcher implements channels.ElectronApplicationChannel { _type_EventTarget = true; _type_ElectronApplication = true; + private readonly _subscriptions = new Set(); constructor(scope: ElectronDispatcher, electronApplication: ElectronApplication) { super(scope, electronApplication, 'ElectronApplication', { @@ -49,6 +51,16 @@ export class ElectronApplicationDispatcher extends Dispatcher { + if (!this._subscriptions.has('console')) + return; + this._dispatchEvent('console', { + type: message.type(), + text: message.text(), + args: message.args().map(a => ElementHandleDispatcher.fromJSHandle(this, a)), + location: message.location() + }); + }); } async browserWindow(params: channels.ElectronApplicationBrowserWindowParams): Promise { @@ -67,6 +79,13 @@ export class ElectronApplicationDispatcher extends Dispatcher { + if (params.enabled) + this._subscriptions.add(params.event); + else + this._subscriptions.delete(params.event); + } + async close(): Promise { await this._object.close(); } diff --git a/packages/playwright-core/src/server/electron/electron.ts b/packages/playwright-core/src/server/electron/electron.ts index ee0e8608f0..2c166c56df 100644 --- a/packages/playwright-core/src/server/electron/electron.ts +++ b/packages/playwright-core/src/server/electron/electron.ts @@ -43,12 +43,15 @@ import * as readline from 'readline'; import { RecentLogsCollector } from '../../utils/debugLogger'; import { serverSideCallMetadata, SdkObject } from '../instrumentation'; import type * as channels from '@protocol/channels'; +import { toConsoleMessageLocation } from '../chromium/crProtocolHelper'; +import { ConsoleMessage } from '../console'; const ARTIFACTS_FOLDER = path.join(os.tmpdir(), 'playwright-artifacts-'); export class ElectronApplication extends SdkObject { static Events = { Close: 'close', + Console: 'console', }; private _browserContext: CRBrowserContext; @@ -82,6 +85,7 @@ export class ElectronApplication extends SdkObject { }); this._nodeElectronHandlePromise.resolve(new js.JSHandle(this._nodeExecutionContext!, 'object', 'ElectronModule', remoteObject.objectId!)); }); + this._nodeSession.on('Runtime.consoleAPICalled', event => this._onConsoleAPI(event)); this._browserContext.setCustomCloseHandler(async () => { await this._browserContext.stopVideoRecording(); const electronHandle = await this._nodeElectronHandlePromise; @@ -89,6 +93,30 @@ export class ElectronApplication extends SdkObject { }); } + async _onConsoleAPI(event: Protocol.Runtime.consoleAPICalledPayload) { + if (event.executionContextId === 0) { + // DevTools protocol stores the last 1000 console messages. These + // messages are always reported even for removed execution contexts. In + // this case, they are marked with executionContextId = 0 and are + // reported upon enabling Runtime agent. + // + // Ignore these messages since: + // - there's no execution context we can use to operate with message + // arguments + // - these messages are reported before Playwright clients can subscribe + // to the 'console' + // page event. + // + // @see https://github.com/GoogleChrome/puppeteer/issues/3865 + return; + } + if (!this._nodeExecutionContext) + return; + const args = event.args.map(arg => this._nodeExecutionContext!.createHandle(arg)); + const message = new ConsoleMessage(null, event.type, undefined, args, toConsoleMessageLocation(event.stackTrace)); + this.emit(ElectronApplication.Events.Console, message); + } + async initialize() { await this._nodeSession.send('Runtime.enable', {}); // Delay loading the app until browser is started and the browser targets are configured to auto-attach. diff --git a/packages/playwright-core/src/server/trace/recorder/tracing.ts b/packages/playwright-core/src/server/trace/recorder/tracing.ts index df41894c05..c2f58e654b 100644 --- a/packages/playwright-core/src/server/trace/recorder/tracing.ts +++ b/packages/playwright-core/src/server/trace/recorder/tracing.ts @@ -450,7 +450,7 @@ export class Tracing extends SdkObject implements InstrumentationListener, Snaps args: message.args().map(a => ({ preview: a.toString(), value: a.rawValue() })), location: message.location(), time: monotonicTime(), - pageId: message.page().guid, + pageId: message.page()?.guid, }; this._appendTraceEvent(event); } diff --git a/packages/playwright-core/types/types.d.ts b/packages/playwright-core/types/types.d.ts index d2061f7857..0202372eab 100644 --- a/packages/playwright-core/types/types.d.ts +++ b/packages/playwright-core/types/types.d.ts @@ -901,8 +901,7 @@ export interface Page { on(event: 'close', listener: (page: Page) => 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. + * Emitted when JavaScript within the page calls one of console API methods, e.g. `console.log` or `console.dir`. * * The arguments passed into `console.log` are available on the {@link ConsoleMessage} event handler argument. * @@ -1197,8 +1196,7 @@ export interface Page { addListener(event: 'close', listener: (page: Page) => 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. + * Emitted when JavaScript within the page calls one of console API methods, e.g. `console.log` or `console.dir`. * * The arguments passed into `console.log` are available on the {@link ConsoleMessage} event handler argument. * @@ -1588,8 +1586,7 @@ export interface Page { prependListener(event: 'close', listener: (page: Page) => 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. + * Emitted when JavaScript within the page calls one of console API methods, e.g. `console.log` or `console.dir`. * * The arguments passed into `console.log` are available on the {@link ConsoleMessage} event handler argument. * @@ -4340,8 +4337,7 @@ export interface Page { waitForEvent(event: 'close', optionsOrPredicate?: { predicate?: (page: Page) => boolean | Promise, timeout?: number } | ((page: Page) => 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. + * Emitted when JavaScript within the page calls one of console API methods, e.g. `console.log` or `console.dir`. * * The arguments passed into `console.log` are available on the {@link ConsoleMessage} event handler argument. * @@ -7663,8 +7659,7 @@ 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. + * Emitted when JavaScript within the page calls one of console API methods, e.g. `console.log` or `console.dir`. * * The arguments passed into `console.log` and the page are available on the {@link ConsoleMessage} event handler * argument. @@ -7855,8 +7850,7 @@ 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. + * Emitted when JavaScript within the page calls one of console API methods, e.g. `console.log` or `console.dir`. * * The arguments passed into `console.log` and the page are available on the {@link ConsoleMessage} event handler * argument. @@ -8102,8 +8096,7 @@ 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. + * Emitted when JavaScript within the page calls one of console API methods, e.g. `console.log` or `console.dir`. * * The arguments passed into `console.log` and the page are available on the {@link ConsoleMessage} event handler * argument. @@ -8720,8 +8713,7 @@ 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. + * Emitted when JavaScript within the page calls one of console API methods, e.g. `console.log` or `console.dir`. * * The arguments passed into `console.log` and the page are available on the {@link ConsoleMessage} event handler * argument. @@ -13931,6 +13923,27 @@ export interface ElectronApplication { */ on(event: 'close', listener: () => void): this; + /** + * Emitted when JavaScript within the Electron main process calls one of console API methods, e.g. `console.log` or + * `console.dir`. + * + * The arguments passed into `console.log` are available on the {@link ConsoleMessage} event handler argument. + * + * **Usage** + * + * ```js + * electronApp.on('console', async msg => { + * const values = []; + * for (const arg of msg.args()) + * values.push(await arg.jsonValue()); + * console.log(...values); + * }); + * await electronApp.evaluate(() => console.log('hello', 5, { foo: 'bar' })); + * ``` + * + */ + on(event: 'console', listener: (consoleMessage: ConsoleMessage) => void): this; + /** * This event is issued for every window that is created **and loaded** in Electron. It contains a {@link Page} that * can be used for Playwright automation. @@ -13942,6 +13955,11 @@ export interface ElectronApplication { */ once(event: 'close', listener: () => 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. */ @@ -13952,6 +13970,27 @@ export interface ElectronApplication { */ addListener(event: 'close', listener: () => void): this; + /** + * Emitted when JavaScript within the Electron main process calls one of console API methods, e.g. `console.log` or + * `console.dir`. + * + * The arguments passed into `console.log` are available on the {@link ConsoleMessage} event handler argument. + * + * **Usage** + * + * ```js + * electronApp.on('console', async msg => { + * const values = []; + * for (const arg of msg.args()) + * values.push(await arg.jsonValue()); + * console.log(...values); + * }); + * await electronApp.evaluate(() => console.log('hello', 5, { foo: 'bar' })); + * ``` + * + */ + addListener(event: 'console', listener: (consoleMessage: ConsoleMessage) => void): this; + /** * This event is issued for every window that is created **and loaded** in Electron. It contains a {@link Page} that * can be used for Playwright automation. @@ -13963,6 +14002,11 @@ export interface ElectronApplication { */ removeListener(event: 'close', listener: () => 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`. */ @@ -13973,6 +14017,11 @@ export interface ElectronApplication { */ off(event: 'close', listener: () => 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`. */ @@ -13983,6 +14032,27 @@ export interface ElectronApplication { */ prependListener(event: 'close', listener: () => void): this; + /** + * Emitted when JavaScript within the Electron main process calls one of console API methods, e.g. `console.log` or + * `console.dir`. + * + * The arguments passed into `console.log` are available on the {@link ConsoleMessage} event handler argument. + * + * **Usage** + * + * ```js + * electronApp.on('console', async msg => { + * const values = []; + * for (const arg of msg.args()) + * values.push(await arg.jsonValue()); + * console.log(...values); + * }); + * await electronApp.evaluate(() => console.log('hello', 5, { foo: 'bar' })); + * ``` + * + */ + prependListener(event: 'console', listener: (consoleMessage: ConsoleMessage) => void): this; + /** * This event is issued for every window that is created **and loaded** in Electron. It contains a {@link Page} that * can be used for Playwright automation. @@ -14039,6 +14109,27 @@ export interface ElectronApplication { */ waitForEvent(event: 'close', optionsOrPredicate?: { predicate?: () => boolean | Promise, timeout?: number } | (() => boolean | Promise)): Promise; + /** + * Emitted when JavaScript within the Electron main process calls one of console API methods, e.g. `console.log` or + * `console.dir`. + * + * The arguments passed into `console.log` are available on the {@link ConsoleMessage} event handler argument. + * + * **Usage** + * + * ```js + * electronApp.on('console', async msg => { + * const values = []; + * for (const arg of msg.args()) + * values.push(await arg.jsonValue()); + * console.log(...values); + * }); + * await electronApp.evaluate(() => console.log('hello', 5, { foo: 'bar' })); + * ``` + * + */ + waitForEvent(event: 'console', optionsOrPredicate?: { predicate?: (consoleMessage: ConsoleMessage) => boolean | Promise, timeout?: number } | ((consoleMessage: ConsoleMessage) => boolean | Promise)): Promise; + /** * This event is issued for every window that is created **and loaded** in Electron. It contains a {@link Page} that * can be used for Playwright automation. diff --git a/packages/protocol/src/channels.ts b/packages/protocol/src/channels.ts index d4eff9134f..21a4b0e5cd 100644 --- a/packages/protocol/src/channels.ts +++ b/packages/protocol/src/channels.ts @@ -1453,7 +1453,6 @@ export type BrowserContextBindingCallEvent = { binding: BindingCallChannel, }; export type BrowserContextConsoleEvent = { - page: PageChannel, type: string, text: string, args: JSHandleChannel[], @@ -1462,6 +1461,7 @@ export type BrowserContextConsoleEvent = { lineNumber: number, columnNumber: number, }, + page: PageChannel, }; export type BrowserContextCloseEvent = {}; export type BrowserContextDialogEvent = { @@ -4082,15 +4082,27 @@ export type ElectronApplicationInitializer = { }; export interface ElectronApplicationEventTarget { on(event: 'close', callback: (params: ElectronApplicationCloseEvent) => void): this; + on(event: 'console', callback: (params: ElectronApplicationConsoleEvent) => void): this; } export interface ElectronApplicationChannel extends ElectronApplicationEventTarget, EventTargetChannel { _type_ElectronApplication: boolean; browserWindow(params: ElectronApplicationBrowserWindowParams, metadata?: CallMetadata): Promise; evaluateExpression(params: ElectronApplicationEvaluateExpressionParams, metadata?: CallMetadata): Promise; evaluateExpressionHandle(params: ElectronApplicationEvaluateExpressionHandleParams, metadata?: CallMetadata): Promise; + updateSubscription(params: ElectronApplicationUpdateSubscriptionParams, metadata?: CallMetadata): Promise; close(params?: ElectronApplicationCloseParams, metadata?: CallMetadata): Promise; } export type ElectronApplicationCloseEvent = {}; +export type ElectronApplicationConsoleEvent = { + type: string, + text: string, + args: JSHandleChannel[], + location: { + url: string, + lineNumber: number, + columnNumber: number, + }, +}; export type ElectronApplicationBrowserWindowParams = { page: PageChannel, }; @@ -4122,12 +4134,21 @@ export type ElectronApplicationEvaluateExpressionHandleOptions = { export type ElectronApplicationEvaluateExpressionHandleResult = { handle: JSHandleChannel, }; +export type ElectronApplicationUpdateSubscriptionParams = { + event: 'console', + enabled: boolean, +}; +export type ElectronApplicationUpdateSubscriptionOptions = { + +}; +export type ElectronApplicationUpdateSubscriptionResult = void; export type ElectronApplicationCloseParams = {}; export type ElectronApplicationCloseOptions = {}; export type ElectronApplicationCloseResult = void; export interface ElectronApplicationEvents { 'close': ElectronApplicationCloseEvent; + 'console': ElectronApplicationConsoleEvent; } // ----------- Android ----------- diff --git a/packages/protocol/src/protocol.yml b/packages/protocol/src/protocol.yml index a56922fd2f..dde01b6c9a 100644 --- a/packages/protocol/src/protocol.yml +++ b/packages/protocol/src/protocol.yml @@ -970,6 +970,21 @@ Browser: close: +ConsoleMessage: + type: mixin + properties: + type: string + text: string + args: + type: array + items: JSHandle + location: + type: object + properties: + url: string + lineNumber: number + columnNumber: number + EventTarget: type: interface @@ -1175,18 +1190,8 @@ BrowserContext: console: parameters: + $mixin: ConsoleMessage page: Page - type: string - text: string - args: - type: array - items: JSHandle - location: - type: object - properties: - url: string - lineNumber: number - columnNumber: number close: @@ -3208,11 +3213,21 @@ ElectronApplication: returns: handle: JSHandle + updateSubscription: + parameters: + event: + type: enum + literals: + - console + enabled: boolean + close: events: close: - + console: + parameters: + $mixin: ConsoleMessage Android: type: interface diff --git a/tests/electron/electron-app.spec.ts b/tests/electron/electron-app.spec.ts index 453283973c..f672a84a89 100644 --- a/tests/electron/electron-app.spec.ts +++ b/tests/electron/electron-app.spec.ts @@ -18,6 +18,7 @@ import type { BrowserWindow } from 'electron'; import path from 'path'; import fs from 'fs'; import { electronTest as test, expect } from './electronTest'; +import type { ConsoleMessage } from 'playwright'; test('should fire close event', async ({ launchElectronApp }) => { const electronApp = await launchElectronApp('electron-app.js'); @@ -31,6 +32,48 @@ test('should fire close event', async ({ launchElectronApp }) => { expect(events.join('|')).toBe('context|application'); }); +test('should fire console events', async ({ launchElectronApp }) => { + const electronApp = await launchElectronApp('electron-app.js'); + const messages = []; + electronApp.on('console', message => messages.push({ type: message.type(), text: message.text() })); + await electronApp.evaluate(() => { + console.log('its type log'); + console.debug('its type debug'); + console.info('its type info'); + console.error('its type error'); + console.warn('its type warn'); + }); + await electronApp.close(); + expect(messages).toEqual([ + { type: 'log', text: 'its type log' }, + { type: 'debug', text: 'its type debug' }, + { type: 'info', text: 'its type info' }, + { type: 'error', text: 'its type error' }, + { type: 'warning', text: 'its type warn' }, + ]); +}); + +test('should fire console events with handles and complex objects', async ({ launchElectronApp }) => { + const electronApp = await launchElectronApp('electron-app.js'); + const messages: ConsoleMessage[] = []; + electronApp.on('console', message => messages.push(message)); + await electronApp.evaluate(() => { + globalThis.complexObject = [{ a: 1, b: 2, c: 3 }, { a: 4, b: 5, c: 6 }, { a: 7, b: 8, c: 9 }]; + console.log(globalThis.complexObject[0], globalThis.complexObject[1], globalThis.complexObject[2]); + }); + expect(messages.length).toBe(1); + const message = messages[0]; + expect(message.text()).toBe('{a: 1, b: 2, c: 3} {a: 4, b: 5, c: 6} {a: 7, b: 8, c: 9}'); + expect(message.args().length).toBe(3); + expect(await message.args()[0].jsonValue()).toEqual({ a: 1, b: 2, c: 3 }); + expect(await message.args()[0].evaluate(part => part === globalThis.complexObject[0])).toBeTruthy(); + expect(await message.args()[1].jsonValue()).toEqual({ a: 4, b: 5, c: 6 }); + expect(await message.args()[1].evaluate(part => part === globalThis.complexObject[1])).toBeTruthy(); + expect(await message.args()[2].jsonValue()).toEqual({ a: 7, b: 8, c: 9 }); + expect(await message.args()[2].evaluate(part => part === globalThis.complexObject[2])).toBeTruthy(); + await electronApp.close(); +}); + test('should dispatch ready event', async ({ launchElectronApp }) => { const electronApp = await launchElectronApp('electron-app-ready-event.js'); const events = await electronApp.evaluate(() => globalThis.__playwrightLog);