From e5f82af47c7ab6788b160741574abc0a30973bb9 Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Thu, 5 Mar 2020 15:18:27 -0800 Subject: [PATCH] api(popups): emit PageEvent immediately, and resolve page() once initialized (#1229) This way we do not miss any popups, even immediately closed ones. --- docs/api.md | 27 +++++++++++++------- src/chromium/crBrowser.ts | 31 +++++++++++++++-------- src/chromium/crPage.ts | 5 +++- src/chromium/crTarget.ts | 38 +++++++++------------------- src/firefox/ffBrowser.ts | 50 ++++++++++++++++++++++++------------- src/page.ts | 14 ++++++++--- src/server/chromium.ts | 10 ++++++-- src/webkit/wkBrowser.ts | 34 +++++++++++++------------ src/webkit/wkPage.ts | 8 ++++-- src/webkit/wkPageProxy.ts | 25 ++++++------------- test/browsercontext.spec.js | 2 +- test/elementhandle.spec.js | 2 +- test/page.spec.js | 6 ++--- test/popup.spec.js | 27 ++++++++++++++------ 14 files changed, 161 insertions(+), 118 deletions(-) diff --git a/docs/api.md b/docs/api.md index e307a44298ad1..f103a95457d32 100644 --- a/docs/api.md +++ b/docs/api.md @@ -304,7 +304,15 @@ Emitted when Browser context gets closed. This might happen because of one of th - <[PageEvent]> Emitted when a new Page is created in the BrowserContext. The event will also fire for popup -pages. +pages. See also [`Page.on('popup')`](#event-popup) to receive events about popups relevant to a specific page. + +```js +const [event] = await Promise.all([ + context.waitForEvent('page'), + page.click('a[target=_blank]'), +]); +const newPage = await event.page(); +``` #### browserContext.addInitScript(script[, ...args]) - `script` <[function]|[string]|[Object]> Script to be evaluated in all pages in the browser context. @@ -726,22 +734,24 @@ Emitted when the JavaScript [`load`](https://developer.mozilla.org/en-US/docs/We Emitted when an uncaught exception happens within the page. #### event: 'popup' -- <[Page]> Page corresponding to "popup" window +- <[PageEvent]> Page event corresponding to "popup" window -Emitted when the page opens a new tab or window. +Emitted when the page opens a new tab or window. This event is emitted in addition to the [`browserContext.on('page')`](#event-page), but only for popups relevant to this page. ```js -const [popup] = await Promise.all([ - new Promise(resolve => page.once('popup', resolve)), +const [event] = await Promise.all([ + page.waitForEvent('popup'), page.click('a[target=_blank]'), ]); +const popup = await event.page(); ``` ```js -const [popup] = await Promise.all([ - new Promise(resolve => page.once('popup', resolve)), +const [event] = await Promise.all([ + page.waitForEvent('popup'), page.evaluate(() => window.open('https://example.com')), ]); +const popup = await event.page(); ``` #### event: 'request' @@ -1753,8 +1763,7 @@ This method returns all of the dedicated [WebWorkers](https://developer.mozilla. ### class: PageEvent -Event object passed to the listeners of ['page'](#event-page) on [`BrowserContext`](#class-browsercontext). Provides access -to the newly created page. +Event object passed to the listeners of [`browserContext.on('page')`](#event-page) and [`page.on('popup')`](#event-popup) events. Provides access to the newly created page. #### pageEvent.page() - returns: <[Promise]<[Page]>> Promise which resolves to the created page. diff --git a/src/chromium/crBrowser.ts b/src/chromium/crBrowser.ts index 2b3f70eff7bd1..d06e0ecb5c018 100644 --- a/src/chromium/crBrowser.ts +++ b/src/chromium/crBrowser.ts @@ -71,7 +71,7 @@ export class CRBrowser extends platform.EventEmitter implements Browser { constructor(connection: CRConnection) { super(); this._connection = connection; - this._client = connection.rootSession; + this._client = this._connection.rootSession; this._defaultContext = new CRBrowserContext(this, null, validateBrowserContextOptions({})); this._connection.on(ConnectionEvents.Disconnected, () => { @@ -120,14 +120,18 @@ export class CRBrowser extends platform.EventEmitter implements Browser { try { switch (targetInfo.type) { case 'page': { - const page = await target.page(); - const event = new PageEvent(page!); + const event = new PageEvent(target.pageOrError()); context.emit(CommonEvents.BrowserContext.Page, event); + const opener = target.opener(); + if (!opener) + break; + const openerPage = await opener.pageOrError(); + if (openerPage instanceof Page && !openerPage.isClosed()) + openerPage.emit(CommonEvents.Page.Popup, new PageEvent(target.pageOrError())); break; } case 'background_page': { - const page = await target.page(); - const event = new PageEvent(page!); + const event = new PageEvent(target.pageOrError()); context.emit(Events.CRBrowserContext.BackgroundPage, event); break; } @@ -268,16 +272,21 @@ export class CRBrowserContext extends platform.EventEmitter implements BrowserCo 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.page())); - return pages.filter(page => !!page) as Page[]; + const pages = await Promise.all(targets.map(target => target.pageOrError())); + return pages.filter(page => (page instanceof Page) && !page.isClosed()) as Page[]; } async newPage(): Promise { assertBrowserContextIsNotOwned(this); const { targetId } = await this._browser._client.send('Target.createTarget', { url: 'about:blank', browserContextId: this._browserContextId || undefined }); const target = this._browser._targets.get(targetId)!; - const page = await target.page(); - return page!; + const result = await target.pageOrError(); + if (result instanceof Page) { + if (result.isClosed()) + throw new Error('Page has been closed.'); + return result; + } + throw result; } async cookies(...urls: string[]): Promise { @@ -382,8 +391,8 @@ export class CRBrowserContext extends platform.EventEmitter implements BrowserCo async backgroundPages(): Promise { const targets = this._browser._allTargets().filter(target => target.context() === this && target.type() === 'background_page'); - const pages = await Promise.all(targets.map(target => target.page())); - return pages.filter(page => !!page) as Page[]; + const pages = await Promise.all(targets.map(target => target.pageOrError())); + return pages.filter(page => (page instanceof Page) && !page.isClosed()) as Page[]; } async createSession(page: Page): Promise { diff --git a/src/chromium/crPage.ts b/src/chromium/crPage.ts index a01dd49045fb2..607aacb580b1d 100644 --- a/src/chromium/crPage.ts +++ b/src/chromium/crPage.ts @@ -388,7 +388,10 @@ export class CRPage implements PageDelegate { const openerTarget = CRTarget.fromPage(this._page).opener(); if (!openerTarget) return null; - return await openerTarget.page(); + const openerPage = await openerTarget.pageOrError(); + if (openerPage instanceof Page && !openerPage.isClosed()) + return openerPage; + return null; } async reload(): Promise { diff --git a/src/chromium/crTarget.ts b/src/chromium/crTarget.ts index d17aa18eee874..bc8015c0753da 100644 --- a/src/chromium/crTarget.ts +++ b/src/chromium/crTarget.ts @@ -17,7 +17,6 @@ import { CRBrowser, CRBrowserContext } from './crBrowser'; import { CRSession, CRSessionEvents } from './crConnection'; -import { Events } from '../events'; import { Page, Worker } from '../page'; import { Protocol } from './protocol'; import { debugError } from '../helper'; @@ -32,9 +31,8 @@ export class CRTarget { private readonly _browserContext: CRBrowserContext; readonly _targetId: string; readonly sessionFactory: () => Promise; - private _pagePromiseFulfill: ((page: Page) => void) | null = null; - private _pagePromiseReject: ((error: Error) => void) | null = null; - private _pagePromise: Promise | null = null; + private _pagePromiseCallback: ((pageOrError: Page | Error) => void) | null = null; + private _pagePromise: Promise | null = null; _crPage: CRPage | null = null; private _workerPromise: Promise | null = null; @@ -56,12 +54,8 @@ export class CRTarget { this._browserContext = browserContext; this._targetId = targetInfo.targetId; this.sessionFactory = sessionFactory; - if (CRTarget.isPageType(targetInfo.type)) { - this._pagePromise = new Promise((fulfill, reject) => { - this._pagePromiseFulfill = fulfill; - this._pagePromiseReject = reject; - }); - } + if (CRTarget.isPageType(targetInfo.type)) + this._pagePromise = new Promise(f => this._pagePromiseCallback = f); } _didClose() { @@ -69,10 +63,6 @@ export class CRTarget { this._crPage.didClose(); } - async page(): Promise { - return this._pagePromise; - } - async initializePageSession(session: CRSession) { this._crPage = new CRPage(session, this._browser, this._browserContext); const page = this._crPage.page(); @@ -80,20 +70,16 @@ export class CRTarget { session.once(CRSessionEvents.Disconnected, () => page._didDisconnect()); try { await this._crPage.initialize(); - this._pagePromiseFulfill!(page); - } catch (error) { - this._pagePromiseReject!(error); + this._pagePromiseCallback!(page); + } catch (e) { + this._pagePromiseCallback!(e); } + } - if (this.type() !== 'page') - return; - const opener = this.opener(); - if (!opener) - return; - const openerPage = await opener.page(); - if (!openerPage) - return; - openerPage.emit(Events.Page.Popup, page); + async pageOrError(): Promise { + if (this._targetInfo.type !== 'page' && this._targetInfo.type !== 'background_page') + throw new Error('Not a page.'); + return this._pagePromise!; } async serviceWorker(): Promise { diff --git a/src/firefox/ffBrowser.ts b/src/firefox/ffBrowser.ts index cfd55946e962c..c3bb34189d961 100644 --- a/src/firefox/ffBrowser.ts +++ b/src/firefox/ffBrowser.ts @@ -18,7 +18,7 @@ import { Browser, createPageInNewContext } from '../browser'; import { BrowserContext, BrowserContextOptions, validateBrowserContextOptions, assertBrowserContextIsNotOwned } from '../browserContext'; import { Events } from '../events'; -import { assert, helper, RegisteredListener, debugError } from '../helper'; +import { assert, helper, RegisteredListener } from '../helper'; import * as network from '../network'; import * as types from '../types'; import { Page, PageEvent, PageBinding } from '../page'; @@ -165,17 +165,16 @@ export class FFBrowser extends platform.EventEmitter implements Browser { const {targetId} = payload.targetInfo; const target = this._targets.get(targetId)!; target._initPagePromise(this._connection.getSession(payload.sessionId)!); - const page = await target.page(); - if (!page) - return; - target.context().emit(Events.BrowserContext.Page, new PageEvent(page)); + + const pageEvent = new PageEvent(target.pageOrError()); + target.context().emit(Events.BrowserContext.Page, pageEvent); const opener = target.opener(); - if (opener && opener._pagePromise) { - const openerPage = await opener._pagePromise; - if (openerPage.listenerCount(Events.Page.Popup)) - openerPage.emit(Events.Page.Popup, page); - } + if (!opener) + return; + const openerPage = await opener.pageOrError(); + if (openerPage instanceof Page && !openerPage.isClosed()) + openerPage.emit(Events.Page.Popup, pageEvent); } async close() { @@ -192,7 +191,7 @@ export class FFBrowser extends platform.EventEmitter implements Browser { } class Target { - _pagePromise?: Promise; + _pagePromise?: Promise; _ffPage: FFPage | null = null; private readonly _browser: FFBrowser; private readonly _context: FFBrowserContext; @@ -233,7 +232,7 @@ class Target { return this._context; } - async page(): Promise { + async pageOrError(): Promise { if (this._type !== 'page') throw new Error(`Cannot create page for "${this._type}" target`); if (!this._pagePromise) @@ -247,12 +246,21 @@ class Target { const openerTarget = this.opener(); if (!openerTarget) return null; - return await openerTarget.page(); + const result = await openerTarget.pageOrError(); + if (result instanceof Page && !result.isClosed()) + return result; + return null; }); const page = this._ffPage._page; session.once(FFSessionEvents.Disconnected, () => page._didDisconnect()); - await this._ffPage._initialize().catch(debugError); - f(page); + let pageOrError: Page | Error; + try { + await this._ffPage._initialize(); + pageOrError = page; + } catch (e) { + pageOrError = e; + } + f(pageOrError); }); } @@ -309,8 +317,8 @@ export class FFBrowserContext extends platform.EventEmitter implements BrowserCo 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.page())); - return pages.filter(page => !!page); + const pages = await Promise.all(targets.map(target => target.pageOrError())); + return pages.filter(page => page instanceof Page && !page.isClosed()) as Page[]; } async newPage(): Promise { @@ -319,7 +327,13 @@ export class FFBrowserContext extends platform.EventEmitter implements BrowserCo browserContextId: this._browserContextId || undefined }); const target = this._browser._targets.get(targetId)!; - return target.page(); + const result = await target.pageOrError(); + if (result instanceof Page) { + if (result.isClosed()) + throw new Error('Page has been closed.'); + return result; + } + throw result; } async cookies(...urls: string[]): Promise { diff --git a/src/page.ts b/src/page.ts index 856f0dffa2311..36cd3626055b5 100644 --- a/src/page.ts +++ b/src/page.ts @@ -89,14 +89,20 @@ export type FileChooser = { }; export class PageEvent { - private readonly _page: Page; + private readonly _pageOrError: Promise; - constructor(page: Page) { - this._page = page; + constructor(pageOrErrorPromise: Promise) { + this._pageOrError = pageOrErrorPromise; } async page(/* options?: frames.NavigateOptions */): Promise { - return this._page; + const result = await this._pageOrError; + if (result instanceof Page) { + if (result.isClosed()) + throw new Error('Page has been closed.'); + return result; + } + throw result; } } diff --git a/src/server/chromium.ts b/src/server/chromium.ts index e74dc6b7868dd..1fe4bdb899850 100644 --- a/src/server/chromium.ts +++ b/src/server/chromium.ts @@ -70,10 +70,16 @@ export class Chromium implements BrowserType { const { timeout = 30000 } = options || {}; const { browserServer, transport } = await this._launchServer(options, 'persistent', userDataDir); const browser = await CRBrowser.connect(transport!, true); - const firstPage = new Promise(r => browser._defaultContext.once(Events.BrowserContext.Page, r)); + const browserContext = browser._defaultContext; + + function targets() { + return browser._allTargets().filter(target => target.context() === browserContext && target.type() === 'page'); + } + const firstTarget = targets().length ? Promise.resolve() : new Promise(f => browserContext.once('page', f)); + const firstPage = firstTarget.then(() => targets()[0].pageOrError()); await helper.waitWithTimeout(firstPage, 'first page', timeout); + // Hack: for typical launch scenario, ensure that close waits for actual process termination. - const browserContext = browser._defaultContext; browserContext.close = () => browserServer.close(); return browserContext; } diff --git a/src/webkit/wkBrowser.ts b/src/webkit/wkBrowser.ts index e8636b79c59a3..f21f80348bea4 100644 --- a/src/webkit/wkBrowser.ts +++ b/src/webkit/wkBrowser.ts @@ -17,7 +17,7 @@ import { Browser, createPageInNewContext } from '../browser'; import { BrowserContext, BrowserContextOptions, validateBrowserContextOptions, assertBrowserContextIsNotOwned, verifyGeolocation } from '../browserContext'; -import { assert, helper, RegisteredListener, debugError } from '../helper'; +import { assert, helper, RegisteredListener } from '../helper'; import * as network from '../network'; import { Page, PageBinding, PageEvent } from '../page'; import { ConnectionTransport, SlowMoTransport } from '../transport'; @@ -126,17 +126,14 @@ export class WKBrowser extends platform.EventEmitter implements Browser { this._firstPageProxyCallback = undefined; } - pageProxy.page().then(async page => { - if (!page) - return; - context!.emit(Events.BrowserContext.Page, new PageEvent(page)); - if (!opener) - return; - const openerPage = await opener.page(); - if (!openerPage || page.isClosed()) - return; - openerPage.emit(Events.Page.Popup, page); - }).catch(debugError); // Just not emit the event in case of initialization failure. + const pageEvent = new PageEvent(pageProxy.pageOrError()); + context.emit(Events.BrowserContext.Page, pageEvent); + if (!opener) + return; + opener.pageOrError().then(openerPage => { + if (openerPage instanceof Page && !openerPage.isClosed()) + openerPage.emit(Events.Page.Popup, pageEvent); + }); } _onPageProxyDestroyed(event: Protocol.Browser.pageProxyDestroyedPayload) { @@ -233,16 +230,21 @@ export class WKBrowserContext extends platform.EventEmitter implements BrowserCo 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.page())); - return pages.filter(page => !!page) as Page[]; + const pages = await Promise.all(pageProxies.map(proxy => proxy.pageOrError())); + return pages.filter(page => page instanceof Page && !page.isClosed()) as Page[]; } async newPage(): Promise { assertBrowserContextIsNotOwned(this); const { pageProxyId } = await this._browser._browserSession.send('Browser.createPage', { browserContextId: this._browserContextId }); const pageProxy = this._browser._pageProxies.get(pageProxyId)!; - const page = await pageProxy.page(); - return page!; + const result = await pageProxy.pageOrError(); + if (result instanceof Page) { + if (result.isClosed()) + throw new Error('Page has been closed.'); + return result; + } + throw result; } async cookies(...urls: string[]): Promise { diff --git a/src/webkit/wkPage.ts b/src/webkit/wkPage.ts index 37c01354f0167..992d35289497f 100644 --- a/src/webkit/wkPage.ts +++ b/src/webkit/wkPage.ts @@ -437,8 +437,12 @@ export class WKPage implements PageDelegate { } async opener(): Promise { - const openerPage = this._opener ? await this._opener.page() : null; - return openerPage && !openerPage.isClosed() ? openerPage : null; + if (!this._opener) + return null; + const openerPage = await this._opener.pageOrError(); + if (openerPage instanceof Page && !openerPage.isClosed()) + return openerPage; + return null; } async reload(): Promise { diff --git a/src/webkit/wkPageProxy.ts b/src/webkit/wkPageProxy.ts index 69f6407ecbcf9..350ac64e34a03 100644 --- a/src/webkit/wkPageProxy.ts +++ b/src/webkit/wkPageProxy.ts @@ -27,9 +27,8 @@ export class WKPageProxy { private readonly _pageProxySession: WKSession; readonly _browserContext: WKBrowserContext; private readonly _opener: WKPageProxy | null; - private readonly _pagePromise: Promise; - private _pagePromiseFulfill: (page: Page | null) => void = () => {}; - private _pagePromiseReject: (error: Error) => void = () => {}; + private readonly _pagePromise: Promise; + private _pagePromiseCallback: (page: Page | Error) => void = () => {}; private readonly _wkPage: WKPage; private _initialized = false; private readonly _sessions = new Map(); @@ -45,10 +44,7 @@ export class WKPageProxy { helper.addEventListener(this._pageProxySession, 'Target.dispatchMessageFromTarget', this._onDispatchMessageFromTarget.bind(this)), helper.addEventListener(this._pageProxySession, 'Target.didCommitProvisionalTarget', this._onDidCommitProvisionalTarget.bind(this)), ]; - this._pagePromise = new Promise((f, r) => { - this._pagePromiseFulfill = f; - this._pagePromiseReject = r; - }); + this._pagePromise = new Promise(f => this._pagePromiseCallback = f); this._wkPage = new WKPage(this._browserContext, this._pageProxySession, this._opener); } @@ -89,7 +85,7 @@ export class WKPageProxy { this._wkPage._page._frameManager.provisionalLoadFailed(this._wkPage._page.mainFrame(), event.loaderId, errorText); } - async page(): Promise { + async pageOrError(): Promise { return this._pagePromise; } @@ -112,21 +108,16 @@ export class WKPageProxy { if (!this._initialized) { assert(!targetInfo.isProvisional); this._initialized = true; - let page: Page | null = null; - let error: Error | undefined; + let pageOrError: Page | Error; try { await this._wkPage.initialize(session); - page = this._wkPage._page; + pageOrError = this._wkPage._page; } catch (e) { - if (!this._pageProxySession.isDisposed()) - error = e; + pageOrError = e; } if (targetInfo.isPaused) this._resumeTarget(targetInfo.targetId); - if (error) - this._pagePromiseReject(error); - else - this._pagePromiseFulfill(page); + this._pagePromiseCallback(pageOrError); } else { assert(targetInfo.isProvisional); (session as any)[isPovisionalSymbol] = true; diff --git a/test/browsercontext.spec.js b/test/browsercontext.spec.js index 65fb03b4158fe..76c3d006d1a74 100644 --- a/test/browsercontext.spec.js +++ b/test/browsercontext.spec.js @@ -39,7 +39,7 @@ module.exports.describe = function({testRunner, expect, playwright, CHROMIUM, FF const page = await context.newPage(); await page.goto(server.EMPTY_PAGE); const [popup] = await Promise.all([ - utils.waitEvent(page, 'popup'), + utils.waitEvent(page, 'popup').then(e => e.page()), page.evaluate(url => window.open(url), server.EMPTY_PAGE) ]); expect(popup.context()).toBe(context); diff --git a/test/elementhandle.spec.js b/test/elementhandle.spec.js index 4393c3f0ebd30..3c47a653ff0ea 100644 --- a/test/elementhandle.spec.js +++ b/test/elementhandle.spec.js @@ -209,7 +209,7 @@ module.exports.describe = function({testRunner, expect, FFOX, CHROMIUM, WEBKIT}) it('should work for adopted elements', async({page,server}) => { await page.goto(server.EMPTY_PAGE); const [popup] = await Promise.all([ - page.waitForEvent('popup').then(async popup => { await popup.waitForLoadState(); return popup; }), + page.waitForEvent('popup').then(async e => { const popup = await e.page(); await popup.waitForLoadState(); return popup; }), page.evaluate(url => window.__popup = window.open(url), server.EMPTY_PAGE), ]); const divHandle = await page.evaluateHandle(() => { diff --git a/test/page.spec.js b/test/page.spec.js index dbb27543ab128..1e065f51dff18 100644 --- a/test/page.spec.js +++ b/test/page.spec.js @@ -132,7 +132,7 @@ module.exports.describe = function({testRunner, expect, headless, playwright, FF describe('Page.opener', function() { it('should provide access to the opener page', async({page}) => { const [popup] = await Promise.all([ - new Promise(x => page.once('popup', x)), + page.waitForEvent('popup').then(e => e.page()), page.evaluate(() => window.open('about:blank')), ]); const opener = await popup.opener(); @@ -140,7 +140,7 @@ module.exports.describe = function({testRunner, expect, headless, playwright, FF }); it('should return null if parent page has been closed', async({page}) => { const [popup] = await Promise.all([ - new Promise(x => page.once('popup', x)), + page.waitForEvent('popup').then(e => e.page()), page.evaluate(() => window.open('about:blank')), ]); await page.close(); @@ -1077,7 +1077,7 @@ module.exports.describe = function({testRunner, expect, headless, playwright, FF describe('Page.Events.Close', function() { it('should work with window.close', async function({ page, context, server }) { - const newPagePromise = new Promise(f => page.once('popup', f)); + const newPagePromise = page.waitForEvent('popup').then(e => e.page()); await page.evaluate(() => window['newPage'] = window.open('about:blank')); const newPage = await newPagePromise; const closedPromise = new Promise(x => newPage.on('close', x)); diff --git a/test/popup.spec.js b/test/popup.spec.js index 892bc1277db98..9f61de6ba007d 100644 --- a/test/popup.spec.js +++ b/test/popup.spec.js @@ -139,18 +139,31 @@ module.exports.describe = function({testRunner, expect, playwright, CHROMIUM, WE const context = await browser.newContext(); const page = await context.newPage(); const [popup] = await Promise.all([ - new Promise(x => page.once('popup', x)), + page.waitForEvent('popup').then(e => e.page()), page.evaluate(() => window.__popup = window.open('about:blank')), ]); expect(await page.evaluate(() => !!window.opener)).toBe(false); expect(await popup.evaluate(() => !!window.opener)).toBe(true); await context.close(); }); + it('should emit for immediately closed popups', async({browser}) => { + const context = await browser.newContext(); + const page = await context.newPage(); + const [popupEvent] = await Promise.all([ + page.waitForEvent('popup'), + page.evaluate(() => { + const win = window.open('about:blank'); + win.close(); + }), + ]); + expect(popupEvent).toBeTruthy(); + await context.close(); + }); it('should work with empty url', async({browser}) => { const context = await browser.newContext(); const page = await context.newPage(); const [popup] = await Promise.all([ - new Promise(x => page.once('popup', x)), + page.waitForEvent('popup').then(e => e.page()), page.evaluate(() => window.__popup = window.open('')), ]); expect(await page.evaluate(() => !!window.opener)).toBe(false); @@ -161,7 +174,7 @@ module.exports.describe = function({testRunner, expect, playwright, CHROMIUM, WE const context = await browser.newContext(); const page = await context.newPage(); const [popup] = await Promise.all([ - new Promise(x => page.once('popup', x)), + page.waitForEvent('popup').then(e => e.page()), page.evaluate(() => window.__popup = window.open('about:blank', null, 'noopener')), ]); expect(await page.evaluate(() => !!window.opener)).toBe(false); @@ -174,7 +187,7 @@ module.exports.describe = function({testRunner, expect, playwright, CHROMIUM, WE await page.goto(server.EMPTY_PAGE); await page.setContent('yo'); const [popup] = await Promise.all([ - page.waitForEvent('popup').then(async popup => { await popup.waitForLoadState(); return popup; }), + page.waitForEvent('popup').then(async e => { const popup = await e.page(); await popup.waitForLoadState(); return popup; }), page.click('a'), ]); expect(await page.evaluate(() => !!window.opener)).toBe(false); @@ -188,7 +201,7 @@ module.exports.describe = function({testRunner, expect, playwright, CHROMIUM, WE await page.goto(server.EMPTY_PAGE); await page.setContent('yo'); const [popup] = await Promise.all([ - page.waitForEvent('popup').then(async popup => { await popup.waitForLoadState(); return popup; }), + page.waitForEvent('popup').then(async e => { const popup = await e.page(); await popup.waitForLoadState(); return popup; }), page.$eval('a', a => a.click()), ]); expect(await page.evaluate(() => !!window.opener)).toBe(false); @@ -203,7 +216,7 @@ module.exports.describe = function({testRunner, expect, playwright, CHROMIUM, WE await page.goto(server.EMPTY_PAGE); await page.setContent('yo'); const [popup] = await Promise.all([ - page.waitForEvent('popup').then(async popup => { await popup.waitForLoadState(); return popup; }), + page.waitForEvent('popup').then(async e => { const popup = await e.page(); await popup.waitForLoadState(); return popup; }), page.click('a'), ]); expect(await page.evaluate(() => !!window.opener)).toBe(false); @@ -216,7 +229,7 @@ module.exports.describe = function({testRunner, expect, playwright, CHROMIUM, WE await page.goto(server.EMPTY_PAGE); await page.setContent('yo'); const [popup] = await Promise.all([ - page.waitForEvent('popup').then(async popup => { await popup.waitForLoadState(); return popup; }), + page.waitForEvent('popup').then(async e => { const popup = await e.page(); await popup.waitForLoadState(); return popup; }), page.click('a'), ]); let badSecondPopup = false;