diff --git a/docs/api.md b/docs/api.md index f103a95457d32..a86c0f32fc4d7 100644 --- a/docs/api.md +++ b/docs/api.md @@ -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) #### event: 'close' @@ -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(); @@ -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 @@ -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) #### event: 'backgroundpage' diff --git a/src/browserContext.ts b/src/browserContext.ts index 40362e5abd688..0a34f0c07ca32 100644 --- a/src/browserContext.ts +++ b/src/browserContext.ts @@ -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, @@ -50,15 +52,74 @@ export interface BrowserContext { setOffline(offline: boolean): Promise; addInitScript(script: Function | string | { path?: string, content?: string }, ...args: any[]): Promise; exposeFunction(name: string, playwrightFunction: Function): Promise; + waitForEvent(event: string, optionsOrPredicate?: Function | (types.TimeoutOptions & { predicate?: Function })): Promise; close(): Promise; +} - _existingPages(): Page[]; - readonly _timeoutSettings: TimeoutSettings; +export abstract class BrowserContextBase extends platform.EventEmitter implements BrowserContext { + readonly _timeoutSettings = new TimeoutSettings(); + readonly _pageBindings = new Map(); readonly _options: BrowserContextOptions; - readonly _pageBindings: Map; + _closed = false; + private readonly _closePromise: Promise; + private _closePromiseFulfill: ((error: Error) => void) | undefined; + + constructor(options: BrowserContextOptions) { + super(); + this._options = options; + this._closePromise = new Promise(fulfill => this._closePromiseFulfill = fulfill); + } + + abstract _existingPages(): Page[]; + + _browserClosed() { + for (const page of this._existingPages()) + page._didClose(); + this._didCloseInternal(); + } + + _didCloseInternal() { + this._closed = true; + this.emit(Events.BrowserContext.Close); + this._closePromiseFulfill!(new Error('Context closed')); + } + + // BrowserContext methods. + abstract pages(): Promise; + abstract newPage(): Promise; + abstract cookies(...urls: string[]): Promise; + abstract setCookies(cookies: network.SetNetworkCookieParam[]): Promise; + abstract clearCookies(): Promise; + abstract setPermissions(origin: string, permissions: string[]): Promise; + abstract clearPermissions(): Promise; + abstract setGeolocation(geolocation: types.Geolocation | null): Promise; + abstract setExtraHTTPHeaders(headers: network.Headers): Promise; + abstract setOffline(offline: boolean): Promise; + abstract addInitScript(script: string | Function | { path?: string | undefined; content?: string | undefined; }, ...args: any[]): Promise; + abstract exposeFunction(name: string, playwrightFunction: Function): Promise; + abstract close(): Promise; + + 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 { + if (!optionsOrPredicate) + optionsOrPredicate = {}; + if (typeof optionsOrPredicate === 'function') + optionsOrPredicate = { predicate: optionsOrPredicate }; + const { timeout = this._timeoutSettings.timeout(), predicate = () => true } = optionsOrPredicate; + + const abortPromise = (event === Events.BrowserContext.Close) ? new Promise(() => { }) : 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) diff --git a/src/chromium/crBrowser.ts b/src/chromium/crBrowser.ts index d06e0ecb5c018..b31b239e98cf9 100644 --- a/src/chromium/crBrowser.ts +++ b/src/chromium/crBrowser.ts @@ -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; @@ -226,21 +225,15 @@ 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(); - 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 = []; } @@ -262,14 +255,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 { const targets = this._browser._allTargets().filter(target => target.context() === this && target.type() === 'page'); const pages = await Promise.all(targets.map(target => target.pageOrError())); @@ -385,8 +370,7 @@ export class CRBrowserContext extends platform.EventEmitter implements BrowserCo assert(this._browserContextId, 'Non-incognito profiles cannot be closed!'); await this._browser._client.send('Target.disposeBrowserContext', { browserContextId: this._browserContextId }); this._browser._contexts.delete(this._browserContextId); - this._closed = true; - this.emit(CommonEvents.BrowserContext.Close); + this._didCloseInternal(); } async backgroundPages(): Promise { @@ -398,11 +382,4 @@ export class CRBrowserContext extends platform.EventEmitter implements BrowserCo async createSession(page: Page): Promise { return CRTarget.fromPage(page).sessionFactory(); } - - _browserClosed() { - this._closed = true; - for (const page of this._existingPages()) - page._didClose(); - this.emit(CommonEvents.BrowserContext.Close); - } } diff --git a/src/chromium/crPage.ts b/src/chromium/crPage.ts index 607aacb580b1d..f7e7b5d70c7d2 100644 --- a/src/chromium/crPage.ts +++ b/src/chromium/crPage.ts @@ -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) @@ -336,7 +336,7 @@ export class CRPage implements PageDelegate { async updateExtraHTTPHeaders(): Promise { const headers = network.mergeHeaders([ - this._page.context()._options.extraHTTPHeaders, + this._browserContext._options.extraHTTPHeaders, this._page._state.extraHTTPHeaders ]); await this._client.send('Network.setExtraHTTPHeaders', { headers }); @@ -348,7 +348,7 @@ export class CRPage implements PageDelegate { } async _updateViewport(updateTouch: boolean): Promise { - 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 }; diff --git a/src/dom.ts b/src/dom.ts index a454a1d2d131e..2f24076b0221d 100644 --- a/src/dom.ts +++ b/src/dom.ts @@ -160,7 +160,7 @@ export class ElementHandle extends js.JSHandle { 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) diff --git a/src/firefox/ffBrowser.ts b/src/firefox/ffBrowser.ts index c3bb34189d961..542c21a1e79be 100644 --- a/src/firefox/ffBrowser.ts +++ b/src/firefox/ffBrowser.ts @@ -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; @@ -269,21 +268,15 @@ 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(); 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 = []; } @@ -412,14 +405,6 @@ export class FFBrowserContext extends platform.EventEmitter implements BrowserCo assert(this._browserContextId, 'Non-incognito profiles cannot be closed!'); await this._browser._connection.send('Target.removeBrowserContext', { browserContextId: this._browserContextId }); this._browser._contexts.delete(this._browserContextId); - this._closed = true; - this.emit(Events.BrowserContext.Close); - } - - _browserClosed() { - this._closed = true; - for (const page of this._existingPages()) - page._didClose(); - this.emit(Events.BrowserContext.Close); + this._didCloseInternal(); } } diff --git a/src/firefox/ffPage.ts b/src/firefox/ffPage.ts index d2d47303f5547..82071fe66b281 100644 --- a/src/firefox/ffPage.ts +++ b/src/firefox/ffPage.ts @@ -15,22 +15,22 @@ * limitations under the License. */ -import * as frames from '../frames'; -import { helper, RegisteredListener, debugError, assert } from '../helper'; +import * as dialog from '../dialog'; import * as dom from '../dom'; +import { Events } from '../events'; +import * as frames from '../frames'; +import { assert, debugError, helper, RegisteredListener } from '../helper'; +import { Page, PageBinding, PageDelegate, Worker } from '../page'; +import * as platform from '../platform'; +import { kScreenshotDuringNavigationError } from '../screenshotter'; +import * as types from '../types'; +import { getAccessibilityTree } from './ffAccessibility'; +import { FFBrowserContext } from './ffBrowser'; import { FFSession } from './ffConnection'; import { FFExecutionContext } from './ffExecutionContext'; -import { Page, PageDelegate, Worker, PageBinding } from '../page'; +import { RawKeyboardImpl, RawMouseImpl } from './ffInput'; import { FFNetworkManager, headersArray } from './ffNetworkManager'; -import { Events } from '../events'; -import * as dialog from '../dialog'; import { Protocol } from './protocol'; -import { RawMouseImpl, RawKeyboardImpl } from './ffInput'; -import { BrowserContext } from '../browserContext'; -import { getAccessibilityTree } from './ffAccessibility'; -import * as types from '../types'; -import * as platform from '../platform'; -import { kScreenshotDuringNavigationError } from '../screenshotter'; const UTILITY_WORLD_NAME = '__playwright_utility_world__'; @@ -45,7 +45,7 @@ export class FFPage implements PageDelegate { private _eventListeners: RegisteredListener[]; private _workers = new Map(); - constructor(session: FFSession, browserContext: BrowserContext, openerResolver: () => Promise) { + constructor(session: FFSession, browserContext: FFBrowserContext, openerResolver: () => Promise) { this._session = session; this._openerResolver = openerResolver; this.rawKeyboard = new RawKeyboardImpl(session); diff --git a/src/page.ts b/src/page.ts index 36cd3626055b5..98c6ac5f14749 100644 --- a/src/page.ts +++ b/src/page.ts @@ -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'; @@ -113,7 +113,7 @@ export class Page extends platform.EventEmitter { private _disconnected = false; private _disconnectedCallback: (e: Error) => void; readonly _disconnectedPromise: Promise; - private _browserContext: BrowserContext; + readonly _browserContext: BrowserContextBase; readonly keyboard: input.Keyboard; readonly mouse: input.Mouse; readonly _timeoutSettings: TimeoutSettings; @@ -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 = () => {}; @@ -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) { diff --git a/src/webkit/wkBrowser.ts b/src/webkit/wkBrowser.ts index f21f80348bea4..b3784d0bb760e 100644 --- a/src/webkit/wkBrowser.ts +++ b/src/webkit/wkBrowser.ts @@ -16,19 +16,18 @@ */ import { Browser, createPageInNewContext } from '../browser'; -import { BrowserContext, BrowserContextOptions, validateBrowserContextOptions, assertBrowserContextIsNotOwned, verifyGeolocation } from '../browserContext'; +import { assertBrowserContextIsNotOwned, BrowserContext, BrowserContextBase, BrowserContextOptions, validateBrowserContextOptions, verifyGeolocation } from '../browserContext'; +import { Events } from '../events'; import { assert, helper, RegisteredListener } from '../helper'; import * as network from '../network'; import { Page, PageBinding, PageEvent } from '../page'; +import * as platform from '../platform'; import { ConnectionTransport, SlowMoTransport } from '../transport'; import * as types from '../types'; -import { Events } from '../events'; import { Protocol } from './protocol'; -import { WKConnection, WKSession, kPageProxyMessageReceived, PageProxyMessageReceivedPayload } from './wkConnection'; -import { WKPageProxy } from './wkPageProxy'; -import * as platform from '../platform'; -import { TimeoutSettings } from '../timeoutSettings'; +import { kPageProxyMessageReceived, PageProxyMessageReceivedPayload, WKConnection, WKSession } from './wkConnection'; import { WKPage } from './wkPage'; +import { WKPageProxy } from './wkPageProxy'; const DEFAULT_USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_2) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.4 Safari/605.1.15'; @@ -177,21 +176,15 @@ export class WKBrowser extends platform.EventEmitter implements Browser { } } -export class WKBrowserContext extends platform.EventEmitter implements BrowserContext { +export class WKBrowserContext extends BrowserContextBase { readonly _browser: WKBrowser; readonly _browserContextId: string | undefined; - readonly _options: BrowserContextOptions; - readonly _timeoutSettings: TimeoutSettings; - private _closed = false; readonly _evaluateOnNewDocumentSources: string[]; - readonly _pageBindings = new Map(); constructor(browser: WKBrowser, browserContextId: string | undefined, options: BrowserContextOptions) { - super(); + super(options); this._browser = browser; this._browserContextId = browserContextId; - this._timeoutSettings = new TimeoutSettings(); - this._options = options; this._evaluateOnNewDocumentSources = []; } @@ -220,14 +213,6 @@ export class WKBrowserContext extends platform.EventEmitter implements BrowserCo return pages; } - setDefaultNavigationTimeout(timeout: number) { - this._timeoutSettings.setDefaultNavigationTimeout(timeout); - } - - setDefaultTimeout(timeout: number) { - this._timeoutSettings.setDefaultTimeout(timeout); - } - async pages(): Promise { const pageProxies = Array.from(this._browser._pageProxies.values()).filter(proxy => proxy._browserContext === this); const pages = await Promise.all(pageProxies.map(proxy => proxy.pageOrError())); @@ -327,14 +312,6 @@ export class WKBrowserContext extends platform.EventEmitter implements BrowserCo assert(this._browserContextId, 'Non-incognito profiles cannot be closed!'); await this._browser._browserSession.send('Browser.deleteContext', { browserContextId: this._browserContextId }); this._browser._contexts.delete(this._browserContextId); - this._closed = true; - this.emit(Events.BrowserContext.Close); - } - - _browserClosed() { - this._closed = true; - for (const page of this._existingPages()) - page._didClose(); - this.emit(Events.BrowserContext.Close); + this._didCloseInternal(); } } diff --git a/src/webkit/wkPage.ts b/src/webkit/wkPage.ts index 992d35289497f..fa9754fd49b32 100644 --- a/src/webkit/wkPage.ts +++ b/src/webkit/wkPage.ts @@ -74,7 +74,7 @@ export class WKPage implements PageDelegate { this._pageProxySession.send('Emulation.setActiveAndFocused', { active: true }), this.authenticate(this._page._state.credentials) ]; - const contextOptions = this._page.context()._options; + const contextOptions = this._browserContext._options; if (contextOptions.javaScriptEnabled === false) promises.push(this._pageProxySession.send('Emulation.setJavaScriptEnabled', { enabled: false })); if (this._page._state.viewportSize || contextOptions.viewport) @@ -130,7 +130,7 @@ export class WKPage implements PageDelegate { if (this._page._state.interceptNetwork) promises.push(session.send('Network.setInterceptionEnabled', { enabled: true, interceptRequests: true })); - const contextOptions = this._page.context()._options; + const contextOptions = this._browserContext._options; if (contextOptions.userAgent) promises.push(session.send('Page.overrideUserAgent', { value: contextOptions.userAgent })); if (this._page._state.mediaType || this._page._state.colorScheme) @@ -384,10 +384,10 @@ export class WKPage implements PageDelegate { _calculateExtraHTTPHeaders(): network.Headers { const headers = network.mergeHeaders([ - this._page.context()._options.extraHTTPHeaders, + this._browserContext._options.extraHTTPHeaders, this._page._state.extraHTTPHeaders ]); - const locale = this._page.context()._options.locale; + const locale = this._browserContext._options.locale; if (locale) headers['Accept-Language'] = locale; return headers; @@ -403,7 +403,7 @@ export class WKPage implements PageDelegate { } async _updateViewport(updateTouch: boolean): Promise { - 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 }; @@ -425,7 +425,7 @@ export class WKPage implements PageDelegate { } async updateOffline() { - await this._updateState('Network.setEmulateOfflineState', { offline: !!this._page.context()._options.offline }); + await this._updateState('Network.setEmulateOfflineState', { offline: !!this._browserContext._options.offline }); } async authenticate(credentials: types.Credentials | null) { diff --git a/test/browsercontext.spec.js b/test/browsercontext.spec.js index 76c3d006d1a74..8e5f47bb8a250 100644 --- a/test/browsercontext.spec.js +++ b/test/browsercontext.spec.js @@ -113,6 +113,13 @@ module.exports.describe = function({testRunner, expect, playwright, CHROMIUM, FF const context = await browser.newContext(); await context.close(); }); + it('close() should abort waitForEvent', async({ browser }) => { + const context = await browser.newContext(); + const promise = context.waitForEvent('page').catch(e => e); + await context.close(); + let error = await promise; + expect(error.message).toContain('Context closed'); + }); }); describe('BrowserContext({userAgent})', function() { @@ -389,7 +396,7 @@ module.exports.describe = function({testRunner, expect, playwright, CHROMIUM, FF const context = await browser.newContext(); const page = await context.newPage(); const [otherPage] = await Promise.all([ - new Promise(r => context.once('page', async event => r(await event.page()))), + context.waitForEvent('page').then(event => event.page()), page.evaluate(url => window.open(url), server.CROSS_PROCESS_PREFIX + '/empty.html').catch(e => console.log('eee = ' + e)), ]); await otherPage.waitForLoadState(); diff --git a/test/chromium/chromium.spec.js b/test/chromium/chromium.spec.js index cbf968c06a79e..1dd75e8325a47 100644 --- a/test/chromium/chromium.spec.js +++ b/test/chromium/chromium.spec.js @@ -27,7 +27,7 @@ module.exports.describe = function({testRunner, expect, playwright, FFOX, CHROMI describe('ChromiumBrowserContext', function() { it('should create a worker from a service worker', async({browser, page, server, context}) => { const [worker] = await Promise.all([ - new Promise(fulfill => context.once('serviceworker', fulfill)), + context.waitForEvent('serviceworker'), page.goto(server.PREFIX + '/serviceworkers/empty/sw.html') ]); expect(await worker.evaluate(() => self.toString())).toBe('[object ServiceWorkerGlobalScope]'); diff --git a/test/chromium/launcher.spec.js b/test/chromium/launcher.spec.js index 0137b1210b481..f1cfe4573d219 100644 --- a/test/chromium/launcher.spec.js +++ b/test/chromium/launcher.spec.js @@ -83,7 +83,7 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p const backgroundPages = await context.backgroundPages(); let backgroundPage = backgroundPages.length ? backgroundPages[0] - : await new Promise(fulfill => context.once('backgroundpage', async event => fulfill(await event.page()))); + : await context.waitForEvent('backgroundpage').then(event => event.page()); expect(backgroundPage).toBeTruthy(); expect(await context.backgroundPages()).toContain(backgroundPage); expect(await context.pages()).not.toContain(backgroundPage); diff --git a/test/launcher.spec.js b/test/launcher.spec.js index f91c2d2f2f9f4..699a11dfa5213 100644 --- a/test/launcher.spec.js +++ b/test/launcher.spec.js @@ -226,6 +226,14 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p expect(message).not.toContain('Timeout'); } }); + it('should fire close event for all contexts', async() => { + const browser = await playwright.launch(defaultBrowserOptions); + const context = await browser.newContext(); + let closed = false; + context.on('close', () => closed = true); + await browser.close(); + expect(closed).toBe(true); + }); }); describe('Playwright.launch |webSocket| option', function() { diff --git a/test/popup.spec.js b/test/popup.spec.js index 9f61de6ba007d..bf049abb9a6a6 100644 --- a/test/popup.spec.js +++ b/test/popup.spec.js @@ -29,7 +29,7 @@ module.exports.describe = function({testRunner, expect, playwright, CHROMIUM, WE await page.setContent('link'); const requestPromise = server.waitForRequest('/popup/popup.html'); const [popup] = await Promise.all([ - new Promise(fulfill => context.once('page', async pageEvent => fulfill(await pageEvent.page()))), + context.waitForEvent('page').then(pageEvent => pageEvent.page()), page.click('a'), ]); await popup.waitForLoadState();