Skip to content

Commit 6c53978

Browse files
committed
feat(api): introduce BrowserContext.waitForEvent
1 parent e5f82af commit 6c53978

15 files changed

+139
-93
lines changed

docs/api.md

+13-4
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,7 @@ await context.close();
291291
- [browserContext.setGeolocation(geolocation)](#browsercontextsetgeolocationgeolocation)
292292
- [browserContext.setOffline(offline)](#browsercontextsetofflineoffline)
293293
- [browserContext.setPermissions(origin, permissions[])](#browsercontextsetpermissionsorigin-permissions)
294+
- [browserContext.waitForEvent(event[, optionsOrPredicate])](#browsercontextwaitforeventevent-optionsorpredicate)
294295
<!-- GEN:stop -->
295296

296297
#### event: 'close'
@@ -545,6 +546,15 @@ await browserContext.setGeolocation({latitude: 59.95, longitude: 30.31667});
545546
- `'payment-handler'`
546547
- returns: <[Promise]>
547548

549+
#### browserContext.waitForEvent(event[, optionsOrPredicate])
550+
- `event` <[string]> Event name, same one would pass into `browserContext.on(event)`.
551+
- `optionsOrPredicate` <[Function]|[Object]> Either a predicate that receives an event or an options object.
552+
- `predicate` <[Function]> receives the event data and resolves to truthy value when the waiting should resolve.
553+
- `timeout` <[number]> maximum time to wait for in milliseconds. Defaults to `30000` (30 seconds). Pass `0` to disable timeout. The default value can be changed by using the [browserContext.setDefaultTimeout(timeout)](#browsercontextsetdefaulttimeouttimeout).
554+
- returns: <[Promise]<[any]>> Promise which resolves to the event data value.
555+
556+
Waits for event to fire and passes its value into the predicate function. Resolves when the predicate returns truthy value. Will throw an error if the context closes before the event
557+
is fired.
548558

549559
```js
550560
const context = await browser.newContext();
@@ -1611,13 +1621,11 @@ Shortcut for [page.mainFrame().waitFor(selectorOrFunctionOrTimeout[, options[, .
16111621
- `event` <[string]> Event name, same one would pass into `page.on(event)`.
16121622
- `optionsOrPredicate` <[Function]|[Object]> Either a predicate that receives an event or an options object.
16131623
- `predicate` <[Function]> receives the event data and resolves to truthy value when the waiting should resolve.
1614-
- `polling` <[number]|"raf"|"mutation"> An interval at which the `pageFunction` is executed, defaults to `raf`. If `polling` is a number, then it is treated as an interval in milliseconds at which the function would be executed. If `polling` is a string, then it can be one of the following values:
1615-
- `'raf'` - to constantly execute `pageFunction` in `requestAnimationFrame` callback. This is the tightest polling mode which is suitable to observe styling changes.
1616-
- `'mutation'` - to execute `pageFunction` on every DOM mutation.
16171624
- `timeout` <[number]> maximum time to wait for in milliseconds. Defaults to `30000` (30 seconds). Pass `0` to disable timeout. The default value can be changed by using the [browserContext.setDefaultTimeout(timeout)](#browsercontextsetdefaulttimeouttimeout) or [page.setDefaultTimeout(timeout)](#pagesetdefaulttimeouttimeout) methods.
16181625
- returns: <[Promise]<[any]>> Promise which resolves to the event data value.
16191626

1620-
Waits for event to fire and passes its value into the predicate function. Resolves when the predicate returns truthy value.
1627+
Waits for event to fire and passes its value into the predicate function. Resolves when the predicate returns truthy value. Will throw an error if the page is closed before the event
1628+
is fired.
16211629

16221630
#### page.waitForFunction(pageFunction[, options[, ...args]])
16231631
- `pageFunction` <[function]|[string]> Function to be evaluated in browser context
@@ -3729,6 +3737,7 @@ const backgroundPage = await backroundPageTarget.page();
37293737
- [browserContext.setGeolocation(geolocation)](#browsercontextsetgeolocationgeolocation)
37303738
- [browserContext.setOffline(offline)](#browsercontextsetofflineoffline)
37313739
- [browserContext.setPermissions(origin, permissions[])](#browsercontextsetpermissionsorigin-permissions)
3740+
- [browserContext.waitForEvent(event[, optionsOrPredicate])](#browsercontextwaitforeventevent-optionsorpredicate)
37323741
<!-- GEN:stop -->
37333742

37343743
#### event: 'backgroundpage'

src/browserContext.ts

+63-7
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,13 @@
1515
* limitations under the License.
1616
*/
1717

18-
import { Page, PageBinding } from './page';
19-
import * as network from './network';
20-
import * as types from './types';
2118
import { helper } from './helper';
19+
import * as network from './network';
20+
import { Page, PageBinding } from './page';
21+
import * as platform from './platform';
2222
import { TimeoutSettings } from './timeoutSettings';
23+
import * as types from './types';
24+
import { Events } from './events';
2325

2426
export type BrowserContextOptions = {
2527
viewport?: types.Viewport | null,
@@ -50,15 +52,69 @@ export interface BrowserContext {
5052
setOffline(offline: boolean): Promise<void>;
5153
addInitScript(script: Function | string | { path?: string, content?: string }, ...args: any[]): Promise<void>;
5254
exposeFunction(name: string, playwrightFunction: Function): Promise<void>;
55+
waitForEvent(event: string, optionsOrPredicate?: Function | (types.TimeoutOptions & { predicate?: Function })): Promise<any>;
5356
close(): Promise<void>;
57+
}
5458

55-
_existingPages(): Page[];
56-
readonly _timeoutSettings: TimeoutSettings;
59+
export abstract class BrowserContextBase extends platform.EventEmitter implements BrowserContext {
60+
readonly _timeoutSettings = new TimeoutSettings();
61+
readonly _pageBindings = new Map<string, PageBinding>();
5762
readonly _options: BrowserContextOptions;
58-
readonly _pageBindings: Map<string, PageBinding>;
63+
private _closePromise: Promise<Error> | undefined;
64+
65+
constructor(options: BrowserContextOptions) {
66+
super();
67+
this._options = options;
68+
}
69+
70+
abstract _existingPages(): Page[];
71+
72+
// BrowserContext methods.
73+
abstract pages(): Promise<Page[]>;
74+
abstract newPage(): Promise<Page>;
75+
abstract cookies(...urls: string[]): Promise<network.NetworkCookie[]>;
76+
abstract setCookies(cookies: network.SetNetworkCookieParam[]): Promise<void>;
77+
abstract clearCookies(): Promise<void>;
78+
abstract setPermissions(origin: string, permissions: string[]): Promise<void>;
79+
abstract clearPermissions(): Promise<void>;
80+
abstract setGeolocation(geolocation: types.Geolocation | null): Promise<void>;
81+
abstract setExtraHTTPHeaders(headers: network.Headers): Promise<void>;
82+
abstract setOffline(offline: boolean): Promise<void>;
83+
abstract addInitScript(script: string | Function | { path?: string | undefined; content?: string | undefined; }, ...args: any[]): Promise<void>;
84+
abstract exposeFunction(name: string, playwrightFunction: Function): Promise<void>;
85+
abstract close(): Promise<void>;
86+
87+
setDefaultNavigationTimeout(timeout: number) {
88+
this._timeoutSettings.setDefaultNavigationTimeout(timeout);
89+
}
90+
91+
setDefaultTimeout(timeout: number) {
92+
this._timeoutSettings.setDefaultTimeout(timeout);
93+
}
94+
95+
async waitForEvent(event: string, optionsOrPredicate?: Function | (types.TimeoutOptions & { predicate?: Function })): Promise<any> {
96+
if (!optionsOrPredicate)
97+
optionsOrPredicate = {};
98+
if (typeof optionsOrPredicate === 'function')
99+
optionsOrPredicate = { predicate: optionsOrPredicate };
100+
const { timeout = this._timeoutSettings.timeout(), predicate = () => true } = optionsOrPredicate;
101+
102+
let abortPromise: Promise<Error>;
103+
if (event === Events.BrowserContext.Close) {
104+
abortPromise = new Promise<Error>(() => { });
105+
} else {
106+
if (!this._closePromise) {
107+
this._closePromise = new Promise(fulfill => {
108+
this.once(Events.BrowserContext.Close, () => fulfill(new Error('Context closed')));
109+
});
110+
}
111+
abortPromise = this._closePromise;
112+
}
113+
return helper.waitForEvent(this, event, (...args: any[]) => !!predicate(...args), timeout, abortPromise);
114+
}
59115
}
60116

61-
export function assertBrowserContextIsNotOwned(context: BrowserContext) {
117+
export function assertBrowserContextIsNotOwned(context: BrowserContextBase) {
62118
const pages = context._existingPages();
63119
for (const page of pages) {
64120
if (page._ownedContext)

src/chromium/crBrowser.ts

+13-27
Original file line numberDiff line numberDiff line change
@@ -15,22 +15,21 @@
1515
* limitations under the License.
1616
*/
1717

18-
import { Events } from './events';
19-
import { Events as CommonEvents } from '../events';
20-
import { assert, helper, debugError } from '../helper';
21-
import { BrowserContext, BrowserContextOptions, validateBrowserContextOptions, assertBrowserContextIsNotOwned, verifyGeolocation } from '../browserContext';
22-
import { CRConnection, ConnectionEvents, CRSession } from './crConnection';
23-
import { Page, PageEvent, PageBinding } from '../page';
24-
import { CRTarget } from './crTarget';
25-
import { Protocol } from './protocol';
26-
import { CRPage } from './crPage';
2718
import { Browser, createPageInNewContext } from '../browser';
19+
import { assertBrowserContextIsNotOwned, BrowserContext, BrowserContextBase, BrowserContextOptions, validateBrowserContextOptions, verifyGeolocation } from '../browserContext';
20+
import { Events as CommonEvents } from '../events';
21+
import { assert, debugError, helper } from '../helper';
2822
import * as network from '../network';
29-
import * as types from '../types';
23+
import { Page, PageBinding, PageEvent } from '../page';
3024
import * as platform from '../platform';
31-
import { readProtocolStream } from './crProtocolHelper';
3225
import { ConnectionTransport, SlowMoTransport } from '../transport';
33-
import { TimeoutSettings } from '../timeoutSettings';
26+
import * as types from '../types';
27+
import { ConnectionEvents, CRConnection, CRSession } from './crConnection';
28+
import { CRPage } from './crPage';
29+
import { readProtocolStream } from './crProtocolHelper';
30+
import { CRTarget } from './crTarget';
31+
import { Events } from './events';
32+
import { Protocol } from './protocol';
3433

3534
export class CRBrowser extends platform.EventEmitter implements Browser {
3635
_connection: CRConnection;
@@ -226,21 +225,16 @@ export class CRBrowser extends platform.EventEmitter implements Browser {
226225
}
227226
}
228227

229-
export class CRBrowserContext extends platform.EventEmitter implements BrowserContext {
228+
export class CRBrowserContext extends BrowserContextBase {
230229
readonly _browser: CRBrowser;
231230
readonly _browserContextId: string | null;
232-
readonly _options: BrowserContextOptions;
233-
readonly _timeoutSettings: TimeoutSettings;
234231
readonly _evaluateOnNewDocumentSources: string[];
235-
readonly _pageBindings = new Map<string, PageBinding>();
236232
private _closed = false;
237233

238234
constructor(browser: CRBrowser, browserContextId: string | null, options: BrowserContextOptions) {
239-
super();
235+
super(options);
240236
this._browser = browser;
241237
this._browserContextId = browserContextId;
242-
this._timeoutSettings = new TimeoutSettings();
243-
this._options = options;
244238
this._evaluateOnNewDocumentSources = [];
245239
}
246240

@@ -262,14 +256,6 @@ export class CRBrowserContext extends platform.EventEmitter implements BrowserCo
262256
return pages;
263257
}
264258

265-
setDefaultNavigationTimeout(timeout: number) {
266-
this._timeoutSettings.setDefaultNavigationTimeout(timeout);
267-
}
268-
269-
setDefaultTimeout(timeout: number) {
270-
this._timeoutSettings.setDefaultTimeout(timeout);
271-
}
272-
273259
async pages(): Promise<Page[]> {
274260
const targets = this._browser._allTargets().filter(target => target.context() === this && target.type() === 'page');
275261
const pages = await Promise.all(targets.map(target => target.pageOrError()));

src/chromium/crPage.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ export class CRPage implements PageDelegate {
101101
this._client.send('Target.setAutoAttach', { autoAttach: true, waitForDebuggerOnStart: true, flatten: true }),
102102
this._client.send('Emulation.setFocusEmulationEnabled', { enabled: true }),
103103
];
104-
const options = this._page.context()._options;
104+
const options = this._browserContext._options;
105105
if (options.bypassCSP)
106106
promises.push(this._client.send('Page.setBypassCSP', { enabled: true }));
107107
if (options.ignoreHTTPSErrors)
@@ -336,7 +336,7 @@ export class CRPage implements PageDelegate {
336336

337337
async updateExtraHTTPHeaders(): Promise<void> {
338338
const headers = network.mergeHeaders([
339-
this._page.context()._options.extraHTTPHeaders,
339+
this._browserContext._options.extraHTTPHeaders,
340340
this._page._state.extraHTTPHeaders
341341
]);
342342
await this._client.send('Network.setExtraHTTPHeaders', { headers });
@@ -348,7 +348,7 @@ export class CRPage implements PageDelegate {
348348
}
349349

350350
async _updateViewport(updateTouch: boolean): Promise<void> {
351-
let viewport = this._page.context()._options.viewport || { width: 0, height: 0 };
351+
let viewport = this._browserContext._options.viewport || { width: 0, height: 0 };
352352
const viewportSize = this._page._state.viewportSize;
353353
if (viewportSize)
354354
viewport = { ...viewport, ...viewportSize };

src/dom.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
160160
const frameId = await this._page._delegate.getOwnerFrame(this);
161161
if (!frameId)
162162
return null;
163-
const pages = this._page.context()._existingPages();
163+
const pages = this._page._browserContext._existingPages();
164164
for (const page of pages) {
165165
const frame = page._frameManager.frame(frameId);
166166
if (frame)

src/firefox/ffBrowser.ts

+8-14
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,18 @@
1616
*/
1717

1818
import { Browser, createPageInNewContext } from '../browser';
19-
import { BrowserContext, BrowserContextOptions, validateBrowserContextOptions, assertBrowserContextIsNotOwned } from '../browserContext';
19+
import { assertBrowserContextIsNotOwned, BrowserContext, BrowserContextBase, BrowserContextOptions, validateBrowserContextOptions } from '../browserContext';
2020
import { Events } from '../events';
2121
import { assert, helper, RegisteredListener } from '../helper';
2222
import * as network from '../network';
23-
import * as types from '../types';
24-
import { Page, PageEvent, PageBinding } from '../page';
25-
import { ConnectionEvents, FFConnection, FFSessionEvents, FFSession } from './ffConnection';
26-
import { FFPage } from './ffPage';
23+
import { Page, PageBinding, PageEvent } from '../page';
2724
import * as platform from '../platform';
28-
import { Protocol } from './protocol';
2925
import { ConnectionTransport, SlowMoTransport } from '../transport';
30-
import { TimeoutSettings } from '../timeoutSettings';
26+
import * as types from '../types';
27+
import { ConnectionEvents, FFConnection, FFSession, FFSessionEvents } from './ffConnection';
3128
import { headersArray } from './ffNetworkManager';
29+
import { FFPage } from './ffPage';
30+
import { Protocol } from './protocol';
3231

3332
export class FFBrowser extends platform.EventEmitter implements Browser {
3433
_connection: FFConnection;
@@ -269,21 +268,16 @@ class Target {
269268
}
270269
}
271270

272-
export class FFBrowserContext extends platform.EventEmitter implements BrowserContext {
271+
export class FFBrowserContext extends BrowserContextBase {
273272
readonly _browser: FFBrowser;
274273
readonly _browserContextId: string | null;
275-
readonly _options: BrowserContextOptions;
276-
readonly _timeoutSettings: TimeoutSettings;
277274
private _closed = false;
278275
private readonly _evaluateOnNewDocumentSources: string[];
279-
readonly _pageBindings = new Map<string, PageBinding>();
280276

281277
constructor(browser: FFBrowser, browserContextId: string | null, options: BrowserContextOptions) {
282-
super();
278+
super(options);
283279
this._browser = browser;
284280
this._browserContextId = browserContextId;
285-
this._timeoutSettings = new TimeoutSettings();
286-
this._options = options;
287281
this._evaluateOnNewDocumentSources = [];
288282
}
289283

src/firefox/ffPage.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import { Events } from '../events';
2626
import * as dialog from '../dialog';
2727
import { Protocol } from './protocol';
2828
import { RawMouseImpl, RawKeyboardImpl } from './ffInput';
29-
import { BrowserContext } from '../browserContext';
29+
import { BrowserContextBase } from '../browserContext';
3030
import { getAccessibilityTree } from './ffAccessibility';
3131
import * as types from '../types';
3232
import * as platform from '../platform';
@@ -45,7 +45,7 @@ export class FFPage implements PageDelegate {
4545
private _eventListeners: RegisteredListener[];
4646
private _workers = new Map<string, { frameId: string, session: FFSession }>();
4747

48-
constructor(session: FFSession, browserContext: BrowserContext, openerResolver: () => Promise<Page | null>) {
48+
constructor(session: FFSession, browserContext: BrowserContextBase, openerResolver: () => Promise<Page | null>) {
4949
this._session = session;
5050
this._openerResolver = openerResolver;
5151
this.rawKeyboard = new RawKeyboardImpl(session);

src/page.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import { Screenshotter } from './screenshotter';
2525
import { TimeoutSettings } from './timeoutSettings';
2626
import * as types from './types';
2727
import { Events } from './events';
28-
import { BrowserContext } from './browserContext';
28+
import { BrowserContext, BrowserContextBase } from './browserContext';
2929
import { ConsoleMessage, ConsoleMessageLocation } from './console';
3030
import * as accessibility from './accessibility';
3131
import * as platform from './platform';
@@ -113,7 +113,7 @@ export class Page extends platform.EventEmitter {
113113
private _disconnected = false;
114114
private _disconnectedCallback: (e: Error) => void;
115115
readonly _disconnectedPromise: Promise<Error>;
116-
private _browserContext: BrowserContext;
116+
readonly _browserContext: BrowserContextBase;
117117
readonly keyboard: input.Keyboard;
118118
readonly mouse: input.Mouse;
119119
readonly _timeoutSettings: TimeoutSettings;
@@ -129,7 +129,7 @@ export class Page extends platform.EventEmitter {
129129
readonly _requestHandlers: { url: types.URLMatch, handler: (request: network.Request) => void }[] = [];
130130
_ownedContext: BrowserContext | undefined;
131131

132-
constructor(delegate: PageDelegate, browserContext: BrowserContext) {
132+
constructor(delegate: PageDelegate, browserContext: BrowserContextBase) {
133133
super();
134134
this._delegate = delegate;
135135
this._closedCallback = () => {};
@@ -580,7 +580,7 @@ export class PageBinding {
580580
try {
581581
let binding = page._pageBindings.get(name);
582582
if (!binding)
583-
binding = page.context()._pageBindings.get(name);
583+
binding = page._browserContext._pageBindings.get(name);
584584
const result = await binding!.playwrightFunction(...args);
585585
expression = helper.evaluationString(deliverResult, name, seq, result);
586586
} catch (error) {

0 commit comments

Comments
 (0)