Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(api): introduce BrowserContext.waitForEvent #1252

Merged
merged 4 commits into from
Mar 6, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 13 additions & 4 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,7 @@ await context.close();
- [browserContext.setGeolocation(geolocation)](#browsercontextsetgeolocationgeolocation)
- [browserContext.setOffline(offline)](#browsercontextsetofflineoffline)
- [browserContext.setPermissions(origin, permissions[])](#browsercontextsetpermissionsorigin-permissions)
- [browserContext.waitForEvent(event[, optionsOrPredicate])](#browsercontextwaitforeventevent-optionsorpredicate)
<!-- GEN:stop -->

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

#### browserContext.waitForEvent(event[, optionsOrPredicate])
- `event` <[string]> Event name, same one would pass into `browserContext.on(event)`.
- `optionsOrPredicate` <[Function]|[Object]> Either a predicate that receives an event or an options object.
- `predicate` <[Function]> receives the event data and resolves to truthy value when the waiting should resolve.
- `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).
- returns: <[Promise]<[any]>> Promise which resolves to the event data value.

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
is fired.

```js
const context = await browser.newContext();
Expand Down Expand Up @@ -1611,13 +1621,11 @@ Shortcut for [page.mainFrame().waitFor(selectorOrFunctionOrTimeout[, options[, .
- `event` <[string]> Event name, same one would pass into `page.on(event)`.
- `optionsOrPredicate` <[Function]|[Object]> Either a predicate that receives an event or an options object.
- `predicate` <[Function]> receives the event data and resolves to truthy value when the waiting should resolve.
- `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:
- `'raf'` - to constantly execute `pageFunction` in `requestAnimationFrame` callback. This is the tightest polling mode which is suitable to observe styling changes.
- `'mutation'` - to execute `pageFunction` on every DOM mutation.
- `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.
- returns: <[Promise]<[any]>> Promise which resolves to the event data value.

Waits for event to fire and passes its value into the predicate function. Resolves when the predicate returns truthy value.
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
is fired.

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

#### event: 'backgroundpage'
Expand Down
70 changes: 63 additions & 7 deletions src/browserContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@
* limitations under the License.
*/

import { Page, PageBinding } from './page';
import * as network from './network';
import * as types from './types';
import { helper } from './helper';
import * as network from './network';
import { Page, PageBinding } from './page';
import * as platform from './platform';
import { TimeoutSettings } from './timeoutSettings';
import * as types from './types';
import { Events } from './events';

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

_existingPages(): Page[];
readonly _timeoutSettings: TimeoutSettings;
export abstract class BrowserContextBase extends platform.EventEmitter implements BrowserContext {
readonly _timeoutSettings = new TimeoutSettings();
readonly _pageBindings = new Map<string, PageBinding>();
readonly _options: BrowserContextOptions;
readonly _pageBindings: Map<string, PageBinding>;
private _closePromise: Promise<Error> | undefined;

constructor(options: BrowserContextOptions) {
super();
this._options = options;
}

abstract _existingPages(): Page[];

// BrowserContext methods.
abstract pages(): Promise<Page[]>;
abstract newPage(): Promise<Page>;
abstract cookies(...urls: string[]): Promise<network.NetworkCookie[]>;
abstract setCookies(cookies: network.SetNetworkCookieParam[]): Promise<void>;
abstract clearCookies(): Promise<void>;
abstract setPermissions(origin: string, permissions: string[]): Promise<void>;
abstract clearPermissions(): Promise<void>;
abstract setGeolocation(geolocation: types.Geolocation | null): Promise<void>;
abstract setExtraHTTPHeaders(headers: network.Headers): Promise<void>;
abstract setOffline(offline: boolean): Promise<void>;
abstract addInitScript(script: string | Function | { path?: string | undefined; content?: string | undefined; }, ...args: any[]): Promise<void>;
abstract exposeFunction(name: string, playwrightFunction: Function): Promise<void>;
abstract close(): Promise<void>;

setDefaultNavigationTimeout(timeout: number) {
this._timeoutSettings.setDefaultNavigationTimeout(timeout);
}

setDefaultTimeout(timeout: number) {
this._timeoutSettings.setDefaultTimeout(timeout);
}

async waitForEvent(event: string, optionsOrPredicate?: Function | (types.TimeoutOptions & { predicate?: Function })): Promise<any> {
yury-s marked this conversation as resolved.
Show resolved Hide resolved
if (!optionsOrPredicate)
optionsOrPredicate = {};
if (typeof optionsOrPredicate === 'function')
optionsOrPredicate = { predicate: optionsOrPredicate };
const { timeout = this._timeoutSettings.timeout(), predicate = () => true } = optionsOrPredicate;

let abortPromise: Promise<Error>;
if (event === Events.BrowserContext.Close) {
abortPromise = new Promise<Error>(() => { });
} else {
if (!this._closePromise) {
this._closePromise = new Promise(fulfill => {
this.once(Events.BrowserContext.Close, () => fulfill(new Error('Context closed')));
});
}
abortPromise = this._closePromise;
}
return helper.waitForEvent(this, event, (...args: any[]) => !!predicate(...args), timeout, abortPromise);
}
}

export function assertBrowserContextIsNotOwned(context: BrowserContext) {
export function assertBrowserContextIsNotOwned(context: BrowserContextBase) {
const pages = context._existingPages();
for (const page of pages) {
if (page._ownedContext)
Expand Down
40 changes: 13 additions & 27 deletions src/chromium/crBrowser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,21 @@
* limitations under the License.
*/

import { Events } from './events';
import { Events as CommonEvents } from '../events';
import { assert, helper, debugError } from '../helper';
import { BrowserContext, BrowserContextOptions, validateBrowserContextOptions, assertBrowserContextIsNotOwned, verifyGeolocation } from '../browserContext';
import { CRConnection, ConnectionEvents, CRSession } from './crConnection';
import { Page, PageEvent, PageBinding } from '../page';
import { CRTarget } from './crTarget';
import { Protocol } from './protocol';
import { CRPage } from './crPage';
import { Browser, createPageInNewContext } from '../browser';
import { assertBrowserContextIsNotOwned, BrowserContext, BrowserContextBase, BrowserContextOptions, validateBrowserContextOptions, verifyGeolocation } from '../browserContext';
import { Events as CommonEvents } from '../events';
import { assert, debugError, helper } from '../helper';
import * as network from '../network';
import * as types from '../types';
import { Page, PageBinding, PageEvent } from '../page';
import * as platform from '../platform';
import { readProtocolStream } from './crProtocolHelper';
import { ConnectionTransport, SlowMoTransport } from '../transport';
import { TimeoutSettings } from '../timeoutSettings';
import * as types from '../types';
import { ConnectionEvents, CRConnection, CRSession } from './crConnection';
import { CRPage } from './crPage';
import { readProtocolStream } from './crProtocolHelper';
import { CRTarget } from './crTarget';
import { Events } from './events';
import { Protocol } from './protocol';

export class CRBrowser extends platform.EventEmitter implements Browser {
_connection: CRConnection;
Expand Down Expand Up @@ -226,21 +225,16 @@ export class CRBrowser extends platform.EventEmitter implements Browser {
}
}

export class CRBrowserContext extends platform.EventEmitter implements BrowserContext {
export class CRBrowserContext extends BrowserContextBase {
readonly _browser: CRBrowser;
readonly _browserContextId: string | null;
readonly _options: BrowserContextOptions;
readonly _timeoutSettings: TimeoutSettings;
readonly _evaluateOnNewDocumentSources: string[];
readonly _pageBindings = new Map<string, PageBinding>();
private _closed = false;

constructor(browser: CRBrowser, browserContextId: string | null, options: BrowserContextOptions) {
super();
super(options);
this._browser = browser;
this._browserContextId = browserContextId;
this._timeoutSettings = new TimeoutSettings();
this._options = options;
this._evaluateOnNewDocumentSources = [];
}

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

setDefaultNavigationTimeout(timeout: number) {
this._timeoutSettings.setDefaultNavigationTimeout(timeout);
}

setDefaultTimeout(timeout: number) {
this._timeoutSettings.setDefaultTimeout(timeout);
}

async pages(): Promise<Page[]> {
const targets = this._browser._allTargets().filter(target => target.context() === this && target.type() === 'page');
const pages = await Promise.all(targets.map(target => target.pageOrError()));
Expand Down
6 changes: 3 additions & 3 deletions src/chromium/crPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ export class CRPage implements PageDelegate {
this._client.send('Target.setAutoAttach', { autoAttach: true, waitForDebuggerOnStart: true, flatten: true }),
this._client.send('Emulation.setFocusEmulationEnabled', { enabled: true }),
];
const options = this._page.context()._options;
const options = this._browserContext._options;
if (options.bypassCSP)
promises.push(this._client.send('Page.setBypassCSP', { enabled: true }));
if (options.ignoreHTTPSErrors)
Expand Down Expand Up @@ -336,7 +336,7 @@ export class CRPage implements PageDelegate {

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

async _updateViewport(updateTouch: boolean): Promise<void> {
let viewport = this._page.context()._options.viewport || { width: 0, height: 0 };
let viewport = this._browserContext._options.viewport || { width: 0, height: 0 };
const viewportSize = this._page._state.viewportSize;
if (viewportSize)
viewport = { ...viewport, ...viewportSize };
Expand Down
2 changes: 1 addition & 1 deletion src/dom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
const frameId = await this._page._delegate.getOwnerFrame(this);
if (!frameId)
return null;
const pages = this._page.context()._existingPages();
const pages = this._page._browserContext._existingPages();
for (const page of pages) {
const frame = page._frameManager.frame(frameId);
if (frame)
Expand Down
22 changes: 8 additions & 14 deletions src/firefox/ffBrowser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,18 @@
*/

import { Browser, createPageInNewContext } from '../browser';
import { BrowserContext, BrowserContextOptions, validateBrowserContextOptions, assertBrowserContextIsNotOwned } from '../browserContext';
import { assertBrowserContextIsNotOwned, BrowserContext, BrowserContextBase, BrowserContextOptions, validateBrowserContextOptions } from '../browserContext';
import { Events } from '../events';
import { assert, helper, RegisteredListener } from '../helper';
import * as network from '../network';
import * as types from '../types';
import { Page, PageEvent, PageBinding } from '../page';
import { ConnectionEvents, FFConnection, FFSessionEvents, FFSession } from './ffConnection';
import { FFPage } from './ffPage';
import { Page, PageBinding, PageEvent } from '../page';
import * as platform from '../platform';
import { Protocol } from './protocol';
import { ConnectionTransport, SlowMoTransport } from '../transport';
import { TimeoutSettings } from '../timeoutSettings';
import * as types from '../types';
import { ConnectionEvents, FFConnection, FFSession, FFSessionEvents } from './ffConnection';
import { headersArray } from './ffNetworkManager';
import { FFPage } from './ffPage';
import { Protocol } from './protocol';

export class FFBrowser extends platform.EventEmitter implements Browser {
_connection: FFConnection;
Expand Down Expand Up @@ -269,21 +268,16 @@ class Target {
}
}

export class FFBrowserContext extends platform.EventEmitter implements BrowserContext {
export class FFBrowserContext extends BrowserContextBase {
readonly _browser: FFBrowser;
readonly _browserContextId: string | null;
readonly _options: BrowserContextOptions;
readonly _timeoutSettings: TimeoutSettings;
private _closed = false;
private readonly _evaluateOnNewDocumentSources: string[];
readonly _pageBindings = new Map<string, PageBinding>();

constructor(browser: FFBrowser, browserContextId: string | null, options: BrowserContextOptions) {
super();
super(options);
this._browser = browser;
this._browserContextId = browserContextId;
this._timeoutSettings = new TimeoutSettings();
this._options = options;
this._evaluateOnNewDocumentSources = [];
}

Expand Down
4 changes: 2 additions & 2 deletions src/firefox/ffPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import { Events } from '../events';
import * as dialog from '../dialog';
import { Protocol } from './protocol';
import { RawMouseImpl, RawKeyboardImpl } from './ffInput';
import { BrowserContext } from '../browserContext';
import { BrowserContextBase } from '../browserContext';
import { getAccessibilityTree } from './ffAccessibility';
import * as types from '../types';
import * as platform from '../platform';
Expand All @@ -45,7 +45,7 @@ export class FFPage implements PageDelegate {
private _eventListeners: RegisteredListener[];
private _workers = new Map<string, { frameId: string, session: FFSession }>();

constructor(session: FFSession, browserContext: BrowserContext, openerResolver: () => Promise<Page | null>) {
constructor(session: FFSession, browserContext: BrowserContextBase, openerResolver: () => Promise<Page | null>) {
yury-s marked this conversation as resolved.
Show resolved Hide resolved
this._session = session;
this._openerResolver = openerResolver;
this.rawKeyboard = new RawKeyboardImpl(session);
Expand Down
8 changes: 4 additions & 4 deletions src/page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import { Screenshotter } from './screenshotter';
import { TimeoutSettings } from './timeoutSettings';
import * as types from './types';
import { Events } from './events';
import { BrowserContext } from './browserContext';
import { BrowserContext, BrowserContextBase } from './browserContext';
import { ConsoleMessage, ConsoleMessageLocation } from './console';
import * as accessibility from './accessibility';
import * as platform from './platform';
Expand Down Expand Up @@ -113,7 +113,7 @@ export class Page extends platform.EventEmitter {
private _disconnected = false;
private _disconnectedCallback: (e: Error) => void;
readonly _disconnectedPromise: Promise<Error>;
private _browserContext: BrowserContext;
readonly _browserContext: BrowserContextBase;
readonly keyboard: input.Keyboard;
readonly mouse: input.Mouse;
readonly _timeoutSettings: TimeoutSettings;
Expand All @@ -129,7 +129,7 @@ export class Page extends platform.EventEmitter {
readonly _requestHandlers: { url: types.URLMatch, handler: (request: network.Request) => void }[] = [];
_ownedContext: BrowserContext | undefined;

constructor(delegate: PageDelegate, browserContext: BrowserContext) {
constructor(delegate: PageDelegate, browserContext: BrowserContextBase) {
super();
this._delegate = delegate;
this._closedCallback = () => {};
Expand Down Expand Up @@ -580,7 +580,7 @@ export class PageBinding {
try {
let binding = page._pageBindings.get(name);
if (!binding)
binding = page.context()._pageBindings.get(name);
binding = page._browserContext._pageBindings.get(name);
const result = await binding!.playwrightFunction(...args);
expression = helper.evaluationString(deliverResult, name, seq, result);
} catch (error) {
Expand Down
Loading