Skip to content

Commit 3089df7

Browse files
authored
feat: worker.on('console') event for web workers (#38201)
1 parent b3c4932 commit 3089df7

File tree

21 files changed

+266
-21
lines changed

21 files changed

+266
-21
lines changed

docs/src/api/class-worker.md

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,12 @@ foreach(var pageWorker in page.Workers)
5858

5959
Emitted when this dedicated [WebWorker](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API) is terminated.
6060

61+
## event: Worker.console
62+
* since: v1.57
63+
- argument: <[ConsoleMessage]>
64+
65+
Emitted when JavaScript within the Web Worker calls one of console API methods, e.g. `console.log` or `console.dir`. Console is not supported for Service Workers.
66+
6167
## async method: Worker.evaluate
6268
* since: v1.8
6369
- returns: <[Serializable]>
@@ -123,3 +129,58 @@ Performs action and waits for the Worker to close.
123129

124130
### param: Worker.waitForClose.callback = %%-java-wait-for-event-callback-%%
125131
* since: v1.9
132+
133+
## async method: Worker.waitForEvent
134+
* since: v1.57
135+
* langs: js, python
136+
- alias-python: expect_event
137+
- returns: <[any]>
138+
139+
Waits for event to fire and passes its value into the predicate function.
140+
Returns when the predicate returns truthy value.
141+
Will throw an error if the page is closed before the event is fired.
142+
Returns the event data value.
143+
144+
**Usage**
145+
146+
```js
147+
// Start waiting for download before clicking. Note no await.
148+
const consolePromise = worker.waitForEvent('console');
149+
await worker.evaluate('console.log(42)');
150+
const consoleMessage = await consolePromise;
151+
```
152+
153+
```python async
154+
async with worker.expect_event("console") as event_info:
155+
await worker.evaluate("console.log(42)")
156+
message = await event_info.value
157+
```
158+
159+
```python sync
160+
with worker.expect_event("console") as event_info:
161+
worker.evaluate("console.log(42)")
162+
message = event_info.value
163+
```
164+
165+
## async method: Worker.waitForEvent
166+
* since: v1.57
167+
* langs: python
168+
- returns: <[EventContextManager]>
169+
170+
### param: Worker.waitForEvent.event = %%-wait-for-event-event-%%
171+
* since: v1.57
172+
173+
### param: Worker.waitForEvent.optionsOrPredicate
174+
* since: v1.57
175+
* langs: js
176+
- `optionsOrPredicate` ?<[function]|[Object]>
177+
- `predicate` <[function]> Receives the event data and resolves to truthy value when the waiting should resolve.
178+
- `timeout` ?<[float]> Maximum time to wait for in milliseconds. Defaults to `0` - no timeout. The default value can be changed via `actionTimeout` option in the config, or by using the [`method: BrowserContext.setDefaultTimeout`] or [`method: Page.setDefaultTimeout`] methods.
179+
180+
Either a predicate that receives an event or an options object. Optional.
181+
182+
### option: Worker.waitForEvent.predicate = %%-wait-for-event-predicate-%%
183+
* since: v1.57
184+
185+
### option: Worker.waitForEvent.timeout = %%-wait-for-event-timeout-%%
186+
* since: v1.57

packages/playwright-client/types/types.d.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10330,34 +10330,80 @@ export interface Worker {
1033010330
*/
1033110331
on(event: 'close', listener: (worker: Worker) => any): this;
1033210332

10333+
/**
10334+
* Emitted when JavaScript within the Web Worker calls one of console API methods, e.g. `console.log` or
10335+
* `console.dir`. Console is not supported for Service Workers.
10336+
*/
10337+
on(event: 'console', listener: (consoleMessage: ConsoleMessage) => any): this;
10338+
1033310339
/**
1033410340
* Adds an event listener that will be automatically removed after it is triggered once. See `addListener` for more information about this event.
1033510341
*/
1033610342
once(event: 'close', listener: (worker: Worker) => any): this;
1033710343

10344+
/**
10345+
* Adds an event listener that will be automatically removed after it is triggered once. See `addListener` for more information about this event.
10346+
*/
10347+
once(event: 'console', listener: (consoleMessage: ConsoleMessage) => any): this;
10348+
1033810349
/**
1033910350
* Emitted when this dedicated [WebWorker](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API) is
1034010351
* terminated.
1034110352
*/
1034210353
addListener(event: 'close', listener: (worker: Worker) => any): this;
1034310354

10355+
/**
10356+
* Emitted when JavaScript within the Web Worker calls one of console API methods, e.g. `console.log` or
10357+
* `console.dir`. Console is not supported for Service Workers.
10358+
*/
10359+
addListener(event: 'console', listener: (consoleMessage: ConsoleMessage) => any): this;
10360+
1034410361
/**
1034510362
* Removes an event listener added by `on` or `addListener`.
1034610363
*/
1034710364
removeListener(event: 'close', listener: (worker: Worker) => any): this;
1034810365

10366+
/**
10367+
* Removes an event listener added by `on` or `addListener`.
10368+
*/
10369+
removeListener(event: 'console', listener: (consoleMessage: ConsoleMessage) => any): this;
10370+
1034910371
/**
1035010372
* Removes an event listener added by `on` or `addListener`.
1035110373
*/
1035210374
off(event: 'close', listener: (worker: Worker) => any): this;
1035310375

10376+
/**
10377+
* Removes an event listener added by `on` or `addListener`.
10378+
*/
10379+
off(event: 'console', listener: (consoleMessage: ConsoleMessage) => any): this;
10380+
1035410381
/**
1035510382
* Emitted when this dedicated [WebWorker](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API) is
1035610383
* terminated.
1035710384
*/
1035810385
prependListener(event: 'close', listener: (worker: Worker) => any): this;
1035910386

10387+
/**
10388+
* Emitted when JavaScript within the Web Worker calls one of console API methods, e.g. `console.log` or
10389+
* `console.dir`. Console is not supported for Service Workers.
10390+
*/
10391+
prependListener(event: 'console', listener: (consoleMessage: ConsoleMessage) => any): this;
10392+
1036010393
url(): string;
10394+
10395+
/**
10396+
* Emitted when this dedicated [WebWorker](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API) is
10397+
* terminated.
10398+
*/
10399+
waitForEvent(event: 'close', optionsOrPredicate?: { predicate?: (worker: Worker) => boolean | Promise<boolean>, timeout?: number } | ((worker: Worker) => boolean | Promise<boolean>)): Promise<Worker>;
10400+
10401+
/**
10402+
* Emitted when JavaScript within the Web Worker calls one of console API methods, e.g. `console.log` or
10403+
* `console.dir`. Console is not supported for Service Workers.
10404+
*/
10405+
waitForEvent(event: 'console', optionsOrPredicate?: { predicate?: (consoleMessage: ConsoleMessage) => boolean | Promise<boolean>, timeout?: number } | ((consoleMessage: ConsoleMessage) => boolean | Promise<boolean>)): Promise<ConsoleMessage>;
10406+
1036110407
}
1036210408

1036310409
/**

packages/playwright-core/src/client/browserContext.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -109,10 +109,9 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel>
109109
});
110110
this._channel.on('console', event => {
111111
const consoleMessage = new ConsoleMessage(this._platform, event, Page.fromNullable(event.page));
112+
Worker.fromNullable(event.worker)?.emit(Events.Worker.Console, consoleMessage);
113+
consoleMessage.page()?.emit(Events.Page.Console, consoleMessage);
112114
this.emit(Events.BrowserContext.Console, consoleMessage);
113-
const page = consoleMessage.page();
114-
if (page)
115-
page.emit(Events.Page.Console, consoleMessage);
116115
});
117116
this._channel.on('pageError', ({ error, page }) => {
118117
const pageObject = Page.from(page);

packages/playwright-core/src/client/events.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ export const Events = {
8787

8888
Worker: {
8989
Close: 'close',
90+
Console: 'console',
9091
},
9192

9293
ElectronApplication: {

packages/playwright-core/src/client/worker.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,19 +19,26 @@ import { TargetClosedError } from './errors';
1919
import { Events } from './events';
2020
import { JSHandle, assertMaxArguments, parseResult, serializeArgument } from './jsHandle';
2121
import { LongStandingScope } from '../utils/isomorphic/manualPromise';
22+
import { TimeoutSettings } from './timeoutSettings';
23+
import { Waiter } from './waiter';
2224

2325
import type { BrowserContext } from './browserContext';
2426
import type { Page } from './page';
2527
import type * as structs from '../../types/structs';
2628
import type * as api from '../../types/types';
2729
import type * as channels from '@protocol/channels';
30+
import type { WaitForEventOptions } from './types';
2831

2932

3033
export class Worker extends ChannelOwner<channels.WorkerChannel> implements api.Worker {
3134
_page: Page | undefined; // Set for web workers.
3235
_context: BrowserContext | undefined; // Set for service workers.
3336
readonly _closedScope = new LongStandingScope();
3437

38+
static fromNullable(worker: channels.WorkerChannel | undefined): Worker | null {
39+
return worker ? Worker.from(worker) : null;
40+
}
41+
3542
static from(worker: channels.WorkerChannel): Worker {
3643
return (worker as any)._object;
3744
}
@@ -63,4 +70,19 @@ export class Worker extends ChannelOwner<channels.WorkerChannel> implements api.
6370
const result = await this._channel.evaluateExpressionHandle({ expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: serializeArgument(arg) });
6471
return JSHandle.from(result.handle) as any as structs.SmartHandle<R>;
6572
}
73+
74+
async waitForEvent(event: string, optionsOrPredicate: WaitForEventOptions = {}): Promise<any> {
75+
return await this._wrapApiCall(async () => {
76+
const timeoutSettings = this._page?._timeoutSettings ?? this._context?._timeoutSettings ?? new TimeoutSettings(this._platform);
77+
const timeout = timeoutSettings.timeout(typeof optionsOrPredicate === 'function' ? {} : optionsOrPredicate);
78+
const predicate = typeof optionsOrPredicate === 'function' ? optionsOrPredicate : optionsOrPredicate.predicate;
79+
const waiter = Waiter.createForEvent(this, event);
80+
waiter.rejectOnTimeout(timeout, `Timeout ${timeout}ms exceeded while waiting for event "${event}"`);
81+
if (event !== Events.Worker.Close)
82+
waiter.rejectOnEvent(this, Events.Worker.Close, () => new TargetClosedError());
83+
const result = await waiter.waitForEvent(this, event, predicate as any);
84+
waiter.dispose();
85+
return result;
86+
});
87+
}
6688
}

packages/playwright-core/src/protocol/validator.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -810,12 +810,14 @@ scheme.EventTargetWaitForEventInfoParams = tObject({
810810
});
811811
scheme.BrowserContextWaitForEventInfoParams = tType('EventTargetWaitForEventInfoParams');
812812
scheme.PageWaitForEventInfoParams = tType('EventTargetWaitForEventInfoParams');
813+
scheme.WorkerWaitForEventInfoParams = tType('EventTargetWaitForEventInfoParams');
813814
scheme.WebSocketWaitForEventInfoParams = tType('EventTargetWaitForEventInfoParams');
814815
scheme.ElectronApplicationWaitForEventInfoParams = tType('EventTargetWaitForEventInfoParams');
815816
scheme.AndroidDeviceWaitForEventInfoParams = tType('EventTargetWaitForEventInfoParams');
816817
scheme.EventTargetWaitForEventInfoResult = tOptional(tObject({}));
817818
scheme.BrowserContextWaitForEventInfoResult = tType('EventTargetWaitForEventInfoResult');
818819
scheme.PageWaitForEventInfoResult = tType('EventTargetWaitForEventInfoResult');
820+
scheme.WorkerWaitForEventInfoResult = tType('EventTargetWaitForEventInfoResult');
819821
scheme.WebSocketWaitForEventInfoResult = tType('EventTargetWaitForEventInfoResult');
820822
scheme.ElectronApplicationWaitForEventInfoResult = tType('EventTargetWaitForEventInfoResult');
821823
scheme.AndroidDeviceWaitForEventInfoResult = tType('EventTargetWaitForEventInfoResult');
@@ -894,7 +896,8 @@ scheme.BrowserContextConsoleEvent = tObject({
894896
lineNumber: tInt,
895897
columnNumber: tInt,
896898
}),
897-
page: tChannel(['Page']),
899+
page: tOptional(tChannel(['Page'])),
900+
worker: tOptional(tChannel(['Worker'])),
898901
});
899902
scheme.BrowserContextCloseEvent = tOptional(tObject({}));
900903
scheme.BrowserContextDialogEvent = tObject({
@@ -1914,6 +1917,11 @@ scheme.WorkerEvaluateExpressionHandleParams = tObject({
19141917
scheme.WorkerEvaluateExpressionHandleResult = tObject({
19151918
handle: tChannel(['ElementHandle', 'JSHandle']),
19161919
});
1920+
scheme.WorkerUpdateSubscriptionParams = tObject({
1921+
event: tEnum(['console']),
1922+
enabled: tBoolean,
1923+
});
1924+
scheme.WorkerUpdateSubscriptionResult = tOptional(tObject({}));
19171925
scheme.JSHandleInitializer = tObject({
19181926
preview: tString,
19191927
});

packages/playwright-core/src/server/bidi/bidiPage.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -286,7 +286,7 @@ export class BidiPage implements PageDelegate {
286286

287287
const callFrame = params.stackTrace?.callFrames[0];
288288
const location = callFrame ?? { url: '', lineNumber: 1, columnNumber: 1 };
289-
this._page.addConsoleMessage(entry.method, entry.args.map(arg => createHandle(context, arg)), location, params.text || undefined);
289+
this._page.addConsoleMessage(null, entry.method, entry.args.map(arg => createHandle(context, arg)), location, params.text || undefined);
290290
}
291291

292292
async navigateFrame(frame: frames.Frame, url: string, referrer: string | undefined): Promise<frames.GotoResult> {

packages/playwright-core/src/server/chromium/crPage.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -759,7 +759,7 @@ class FrameSession {
759759
session.on('Target.detachedFromTarget', event => this._onDetachedFromTarget(event));
760760
session.on('Runtime.consoleAPICalled', event => {
761761
const args = event.args.map(o => createHandle(worker.existingExecutionContext!, o));
762-
this._page.addConsoleMessage(event.type, args, toConsoleMessageLocation(event.stackTrace));
762+
this._page.addConsoleMessage(worker, event.type, args, toConsoleMessageLocation(event.stackTrace));
763763
});
764764
session.on('Runtime.exceptionThrown', exception => this._page.addPageError(exceptionToError(exception.exceptionDetails)));
765765
}
@@ -822,7 +822,7 @@ class FrameSession {
822822
if (!context)
823823
return;
824824
const values = event.args.map(arg => createHandle(context, arg));
825-
this._page.addConsoleMessage(event.type, values, toConsoleMessageLocation(event.stackTrace));
825+
this._page.addConsoleMessage(null, event.type, values, toConsoleMessageLocation(event.stackTrace));
826826
}
827827

828828
async _onBindingCalled(event: Protocol.Runtime.bindingCalledPayload) {
@@ -869,7 +869,7 @@ class FrameSession {
869869
lineNumber: lineNumber || 0,
870870
columnNumber: 0,
871871
};
872-
this._page.addConsoleMessage(level, [], location, text);
872+
this._page.addConsoleMessage(null, level, [], location, text);
873873
}
874874
}
875875

packages/playwright-core/src/server/console.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
*/
1616

1717
import type * as js from './javascript';
18-
import type { Page } from './page';
18+
import type { Page, Worker } from './page';
1919
import type { ConsoleMessageLocation } from './types';
2020

2121
export class ConsoleMessage {
@@ -24,9 +24,11 @@ export class ConsoleMessage {
2424
private _args: js.JSHandle[];
2525
private _location: ConsoleMessageLocation;
2626
private _page: Page | null;
27+
private _worker: Worker | null;
2728

28-
constructor(page: Page | null, type: string, text: string | undefined, args: js.JSHandle[], location?: ConsoleMessageLocation) {
29+
constructor(page: Page | null, worker: Worker | null, type: string, text: string | undefined, args: js.JSHandle[], location?: ConsoleMessageLocation) {
2930
this._page = page;
31+
this._worker = worker;
3032
this._type = type;
3133
this._text = text;
3234
this._args = args;
@@ -37,6 +39,10 @@ export class ConsoleMessage {
3739
return this._page;
3840
}
3941

42+
worker() {
43+
return this._worker;
44+
}
45+
4046
type(): string {
4147
return this._type;
4248
}

packages/playwright-core/src/server/dispatchers/browserContextDispatcher.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,10 +120,12 @@ export class BrowserContextDispatcher extends Dispatcher<BrowserContext, channel
120120
});
121121
this.addObjectListener(BrowserContext.Events.Console, (message: ConsoleMessage) => {
122122
const page = message.page()!;
123-
if (this._shouldDispatchEvent(page, 'console')) {
123+
const workerDispatcher = WorkerDispatcher.fromNullable(this, message.worker());
124+
if (this._shouldDispatchEvent(page, 'console') || workerDispatcher?._subscriptions.has('console')) {
124125
const pageDispatcher = PageDispatcher.from(this, page);
125126
this._dispatchEvent('console', {
126127
page: pageDispatcher,
128+
worker: workerDispatcher,
127129
...pageDispatcher.serializeConsoleMessage(message),
128130
});
129131
}

0 commit comments

Comments
 (0)