diff --git a/docs/api.md b/docs/api.md index 6fd657bf24..eced6a3818 100644 --- a/docs/api.md +++ b/docs/api.md @@ -9,7 +9,6 @@ - [class: Browser](#class-browser) - [class: BrowserContext](#class-browsercontext) - [class: Page](#class-page) -- [class: PageEvent](#class-pageevent) - [class: Frame](#class-frame) - [class: ElementHandle](#class-elementhandle) - [class: JSHandle](#class-jshandle) @@ -312,19 +311,22 @@ Emitted when Browser context gets closed. This might happen because of one of th - The [`browser.close`](#browserclose) method was called. #### event: 'page' -- <[PageEvent]> +- <[Page]> -Emitted when a new Page is created in the BrowserContext. The event will also fire for popup -pages. See also [`Page.on('popup')`](#event-popup) to receive events about popups relevant to a specific page. +The event is emitted when a new Page is created in the BrowserContext. The page may still be loading. The event will also fire for popup pages. See also [`Page.on('popup')`](#event-popup) to receive events about popups relevant to a specific page. + +The earliest moment that page is available is when it has navigated to the initial url. For example, when opening a popup with `window.open('http://example.com')`, this event will fire when the network request to "http://example.com" is done and its response has started loading in the popup. ```js -const [event] = await Promise.all([ +const [page] = await Promise.all([ context.waitForEvent('page'), page.click('a[target=_blank]'), ]); -const newPage = await event.page(); +console.log(await page.evaluate('location.href')); ``` +> **NOTE** Use [Page.waitForLoadState](#pagewaitforloadstateoptions) to wait until the page gets to a particular state (you should not need it in most cases). + #### browserContext.addCookies(cookies) - `cookies` <[Array]<[Object]>> - `name` <[string]> **required** @@ -765,25 +767,20 @@ Emitted when the JavaScript [`load`](https://developer.mozilla.org/en-US/docs/We Emitted when an uncaught exception happens within the page. #### event: 'popup' -- <[PageEvent]> Page event corresponding to "popup" window +- <[Page]> Page corresponding to "popup" 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 [event] = await Promise.all([ - page.waitForEvent('popup'), - page.click('a[target=_blank]'), -]); -const popup = await event.page(); -``` +The earliest moment that page is available is when it has navigated to the initial url. For example, when opening a popup with `window.open('http://example.com')`, this event will fire when the network request to "http://example.com" is done and its response has started loading in the popup. ```js -const [event] = await Promise.all([ +const [popup] = await Promise.all([ page.waitForEvent('popup'), page.evaluate(() => window.open('https://example.com')), ]); -const popup = await event.page(); +console.log(await popup.evaluate('location.href')); ``` +> **NOTE** Use [Page.waitForLoadState](#pagewaitforloadstateoptions) to wait until the page gets to a particular state (you should not need it in most cases). #### event: 'request' - <[Request]> @@ -1815,34 +1812,6 @@ This method returns all of the dedicated [WebWorkers](https://developer.mozilla. > **NOTE** This does not contain ServiceWorkers -### class: PageEvent - -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([options]) -- `options` <[Object]> - - `timeout` <[number]> Maximum navigation time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by using the [browserContext.setDefaultNavigationTimeout(timeout)](#browsercontextsetdefaultnavigationtimeouttimeout), [browserContext.setDefaultTimeout(timeout)](#browsercontextsetdefaulttimeouttimeout), [page.setDefaultNavigationTimeout(timeout)](#pagesetdefaultnavigationtimeouttimeout) or [page.setDefaultTimeout(timeout)](#pagesetdefaulttimeouttimeout) methods. - - `waitUntil` <"load"|"domcontentloaded"|"networkidle0"|"networkidle2"|"nowait"> When to consider the page to be loaded, defaults to `load`. Events can be either: - - `'nowait'` - navigation is committed, new url is displayed in the browser address bar. - - `'domcontentloaded'` - consider navigation to be finished when the `DOMContentLoaded` event is fired. - - `'load'` - consider navigation to be finished when the `load` event is fired. - - `'networkidle0'` - consider navigation to be finished when there are no more than 0 network connections for at least `500` ms. - - `'networkidle2'` - consider navigation to be finished when there are no more than 2 network connections for at least `500` ms. -- returns: <[Promise]<[Page]>> Promise which resolves to the created page once it loads, according to `waitUntil` option. - -This resolves when the page reaches a required load state, `load` by default. The earliest moment that page is available is when it has navigated to the initial url (corresponds to `{waitUntil: 'nowait'}` option). For example, when opening a popup with `window.open('http://example.com')`, this method will wait until the network request to "http://example.com" is done and its response has started loading in the popup. Passing different `waitUntil` options will also wait for the particular load state to happen, e.g. default `load` waits until the load event fires. - -```js -const [, popup] = await Promise.all([ - // Click opens a popup window. - page.click('button'), - // Resolves after popup has fired 'DOMContentLoaded' event. - page.waitForEvent('popup').then(event => event.page({ waitUntil: 'domcontentloaded' })), -]); -``` - -> **NOTE** Some pages will never fire `load` event. In this case, default `pageEvent.page()` without options will timeout - try using `pageEvent.page({ waitUntil: 'domcontentloaded' })` instead. - ### class: Frame At every point of time, page exposes its current frame tree via the [page.mainFrame()](#pagemainframe) and [frame.childFrames()](#framechildframes) methods. @@ -3836,7 +3805,7 @@ const backgroundPage = await backroundPageTarget.page(); #### event: 'backgroundpage' -- <[PageEvent]> +- <[Page]> Emitted when new background page is created in the context. @@ -4101,7 +4070,6 @@ const { chromium } = require('playwright'); [Mouse]: #class-mouse "Mouse" [Object]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object "Object" [Page]: #class-page "Page" -[PageEvent]: #class-pageevent "PageEvent" [Playwright]: #class-playwright "Playwright" [Promise]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise "Promise" [RegExp]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp diff --git a/src/api.ts b/src/api.ts index 831a2de6c5..ed4694d1bb 100644 --- a/src/api.ts +++ b/src/api.ts @@ -25,7 +25,7 @@ export { Frame } from './frames'; export { Keyboard, Mouse } from './input'; export { JSHandle } from './javascript'; export { Request, Response, Route } from './network'; -export { FileChooser, Page, PageEvent, Worker } from './page'; +export { FileChooser, Page, Worker } from './page'; export { Selectors } from './selectors'; export { CRBrowser as ChromiumBrowser } from './chromium/crBrowser'; diff --git a/src/chromium/crBrowser.ts b/src/chromium/crBrowser.ts index 61d6f43d91..b814075da4 100644 --- a/src/chromium/crBrowser.ts +++ b/src/chromium/crBrowser.ts @@ -20,7 +20,7 @@ import { assertBrowserContextIsNotOwned, BrowserContext, BrowserContextBase, Bro import { Events as CommonEvents } from '../events'; import { assert, debugError, helper } from '../helper'; import * as network from '../network'; -import { Page, PageBinding, PageEvent, Worker } from '../page'; +import { Page, PageBinding, Worker } from '../page'; import * as platform from '../platform'; import { ConnectionTransport, SlowMoTransport } from '../transport'; import * as types from '../types'; @@ -129,19 +129,22 @@ export class CRBrowser extends platform.EventEmitter implements Browser { const { context, target } = this._createTarget(targetInfo, session); if (CRTarget.isPageType(targetInfo.type)) { - const pageEvent = new PageEvent(context, target.pageOrError()); target.pageOrError().then(async () => { + const page = target._crPage!.page(); if (targetInfo.type === 'page') { this._firstPageCallback(); - context.emit(CommonEvents.BrowserContext.Page, pageEvent); + context.emit(CommonEvents.BrowserContext.Page, page); const opener = target.opener(); if (!opener) return; + // Opener page must have been initialized already and resumed in order to + // create this popup but there is a chance that not all responses have been + // received yet so we cannot use opener._crPage?.page() const openerPage = await opener.pageOrError(); if (openerPage instanceof Page && !openerPage.isClosed()) - openerPage.emit(CommonEvents.Page.Popup, pageEvent); + openerPage.emit(CommonEvents.Page.Popup, page); } else if (targetInfo.type === 'background_page') { - context.emit(Events.CRBrowserContext.BackgroundPage, pageEvent); + context.emit(Events.CRBrowserContext.BackgroundPage, page); } }); return; diff --git a/src/firefox/ffBrowser.ts b/src/firefox/ffBrowser.ts index 842eb2a5b2..2a8a066dae 100644 --- a/src/firefox/ffBrowser.ts +++ b/src/firefox/ffBrowser.ts @@ -20,7 +20,7 @@ import { assertBrowserContextIsNotOwned, BrowserContext, BrowserContextBase, Bro import { Events } from '../events'; import { assert, helper, RegisteredListener } from '../helper'; import * as network from '../network'; -import { Page, PageBinding, PageEvent } from '../page'; +import { Page, PageBinding } from '../page'; import * as platform from '../platform'; import { ConnectionTransport, SlowMoTransport } from '../transport'; import * as types from '../types'; @@ -130,15 +130,15 @@ export class FFBrowser extends platform.EventEmitter implements Browser { const ffPage = new FFPage(session, context, opener); this._ffPages.set(targetId, ffPage); - const pageEvent = new PageEvent(context, ffPage.pageOrError()); ffPage.pageOrError().then(async () => { this._firstPageCallback(); - context.emit(Events.BrowserContext.Page, pageEvent); + const page = ffPage._page; + context.emit(Events.BrowserContext.Page, page); if (!opener) return; const openerPage = await opener.pageOrError(); if (openerPage instanceof Page && !openerPage.isClosed()) - openerPage.emit(Events.Page.Popup, pageEvent); + openerPage.emit(Events.Page.Popup, page); }); } diff --git a/src/page.ts b/src/page.ts index 35adc19b37..6ce0d331d9 100644 --- a/src/page.ts +++ b/src/page.ts @@ -87,48 +87,6 @@ export type FileChooser = { multiple: boolean }; -export class PageEvent { - private readonly _browserContext: BrowserContextBase; - private readonly _pageOrError: Promise; - private readonly _lifecyclePromises = new Map>(); - - constructor(browserContext: BrowserContextBase, pageOrErrorPromise: Promise) { - this._browserContext = browserContext; - this._pageOrError = pageOrErrorPromise; - for (const lifecycle of types.kLifecycleEvents) - this._lifecyclePromises.set(lifecycle, this._createLifecyclePromise(lifecycle)); - } - - async _createLifecyclePromise(lifecycle: types.LifecycleEvent): Promise { - const page = await this._pageOrError; - if (!(page instanceof Page)) - return page; - - try { - const frameTask = new frames.FrameTask(page.mainFrame(), { timeout: 0 }); - await frameTask.waitForLifecycle(lifecycle); - frameTask.done(); - } catch (error) { - return error; - } - return page; - } - - async page(options: types.TimeoutOptions & { waitUntil?: types.LifecycleEvent | 'nowait' } = {}): Promise { - const { - timeout = this._browserContext._timeoutSettings.navigationTimeout(), - waitUntil = 'load', - } = options; - const lifecyclePromise = waitUntil === 'nowait' ? this._pageOrError : this._lifecyclePromises.get(waitUntil); - if (!lifecyclePromise) - throw new Error(`Unsupported waitUntil option ${String(waitUntil)}`); - const pageOrError = await helper.waitWithTimeout(lifecyclePromise, `"${waitUntil}"`, timeout); - if (pageOrError instanceof Page) - return pageOrError; - throw pageOrError; - } -} - export class Page extends platform.EventEmitter { private _closed = false; private _closedCallback: () => void; diff --git a/src/webkit/wkBrowser.ts b/src/webkit/wkBrowser.ts index a9f1f5d4af..cf38ffe697 100644 --- a/src/webkit/wkBrowser.ts +++ b/src/webkit/wkBrowser.ts @@ -20,7 +20,7 @@ import { assertBrowserContextIsNotOwned, BrowserContext, BrowserContextBase, Bro import { Events } from '../events'; import { assert, helper, RegisteredListener } from '../helper'; import * as network from '../network'; -import { Page, PageBinding, PageEvent } from '../page'; +import { Page, PageBinding } from '../page'; import * as platform from '../platform'; import { ConnectionTransport, SlowMoTransport } from '../transport'; import * as types from '../types'; @@ -137,15 +137,16 @@ export class WKBrowser extends platform.EventEmitter implements Browser { const wkPage = new WKPage(context, pageProxySession, opener || null, hasInitialAboutBlank); this._wkPages.set(pageProxyId, wkPage); - const pageEvent = new PageEvent(context, wkPage.pageOrError()); wkPage.pageOrError().then(async () => { this._firstPageCallback(); - context!.emit(Events.BrowserContext.Page, pageEvent); + const page = wkPage._page; + context!.emit(Events.BrowserContext.Page, page); if (!opener) return; - const openerPage = await opener.pageOrError(); - if (openerPage instanceof Page && !openerPage.isClosed()) - openerPage.emit(Events.Page.Popup, pageEvent); + await opener.pageOrError(); + const openerPage = opener._page; + if (!openerPage.isClosed()) + openerPage.emit(Events.Page.Popup, page); }); } diff --git a/test/browsercontext.spec.js b/test/browsercontext.spec.js index 6a0b2b6558..de38653b02 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').then(e => e.page()), + page.waitForEvent('popup'), page.evaluate(url => window.open(url), server.EMPTY_PAGE) ]); expect(popup.context()).toBe(context); @@ -479,24 +479,25 @@ module.exports.describe = function({testRunner, expect, playwright, CHROMIUM, FF }); }); - describe('Events.BrowserContext.PageEvent', function() { - it.fail(FFOX)('should have url with nowait', async({browser, server}) => { + describe('Events.BrowserContext.Page', function() { + it.fail(FFOX)('should have url', async({browser, server}) => { const context = await browser.newContext(); const page = await context.newPage(); const [otherPage] = await Promise.all([ - context.waitForEvent('page').then(event => event.page({ waitUntil: 'nowait' })), + context.waitForEvent('page'), page.evaluate(url => window.open(url), server.EMPTY_PAGE) ]); expect(otherPage.url()).toBe(server.EMPTY_PAGE); await context.close(); }); - it('should have url with domcontentloaded', async({browser, server}) => { + it('should have url after domcontentloaded', async({browser, server}) => { const context = await browser.newContext(); const page = await context.newPage(); const [otherPage] = await Promise.all([ - context.waitForEvent('page').then(event => event.page({ waitUntil: 'domcontentloaded' })), + context.waitForEvent('page'), page.evaluate(url => window.open(url), server.EMPTY_PAGE) ]); + await otherPage.waitForLoadState({ waitUntil: 'domcontentloaded' }); expect(otherPage.url()).toBe(server.EMPTY_PAGE); await context.close(); }); @@ -504,9 +505,10 @@ module.exports.describe = function({testRunner, expect, playwright, CHROMIUM, FF const context = await browser.newContext(); const page = await context.newPage(); const [otherPage] = await Promise.all([ - context.waitForEvent('page').then(event => event.page({ waitUntil: 'domcontentloaded' })), + context.waitForEvent('page'), page.evaluate(url => window.open(url), 'about:blank') ]); + await otherPage.waitForLoadState({ waitUntil: 'domcontentloaded' }); expect(otherPage.url()).toBe('about:blank'); await context.close(); }); @@ -514,19 +516,21 @@ module.exports.describe = function({testRunner, expect, playwright, CHROMIUM, FF const context = await browser.newContext(); const page = await context.newPage(); const [otherPage] = await Promise.all([ - context.waitForEvent('page').then(event => event.page({ waitUntil: 'domcontentloaded' })), + context.waitForEvent('page'), page.evaluate(() => window.open()) ]); + await otherPage.waitForLoadState({ waitUntil: 'domcontentloaded' }); expect(otherPage.url()).toBe('about:blank'); await context.close(); }); - it('should report when a new page is created and closed', async({browser, server}) => { + it.fail(FFOX)('should report when a new page is created and closed', async({browser, server}) => { const context = await browser.newContext(); const page = await context.newPage(); const [otherPage] = await Promise.all([ - context.waitForEvent('page').then(event => event.page()), + context.waitForEvent('page'), page.evaluate(url => window.open(url), server.CROSS_PROCESS_PREFIX + '/empty.html'), ]); + // The url is about:blank in FF when 'page' event is fired. expect(otherPage.url()).toContain(server.CROSS_PROCESS_PREFIX); expect(await otherPage.evaluate(() => ['Hello', 'world'].join(' '))).toBe('Hello world'); expect(await otherPage.$('body')).toBeTruthy(); @@ -547,12 +551,12 @@ module.exports.describe = function({testRunner, expect, playwright, CHROMIUM, FF }); it('should report initialized pages', async({browser, server}) => { const context = await browser.newContext(); - const pagePromise = context.waitForEvent('page').then(e => e.page()); + const pagePromise = context.waitForEvent('page'); context.newPage(); const newPage = await pagePromise; expect(newPage.url()).toBe('about:blank'); - const popupPromise = context.waitForEvent('page').then(e => e.page()); + const popupPromise = context.waitForEvent('page'); const evaluatePromise = newPage.evaluate(() => window.open('about:blank')); const popup = await popupPromise; expect(popup.url()).toBe('about:blank'); @@ -565,7 +569,7 @@ module.exports.describe = function({testRunner, expect, playwright, CHROMIUM, FF let serverResponse = null; server.setRoute('/one-style.css', (req, res) => serverResponse = res); // Open a new page. Use window.open to connect to the page later. - const [newPageEvent] = await Promise.all([ + const [newPage] = await Promise.all([ context.waitForEvent('page'), page.evaluate(url => window.open(url), server.PREFIX + '/one-style.html'), server.waitForRequest('/one-style.css') @@ -573,20 +577,20 @@ module.exports.describe = function({testRunner, expect, playwright, CHROMIUM, FF // Issue a redirect. serverResponse.writeHead(302, { location: '/injectedstyle.css' }); serverResponse.end(); - const newPage = await newPageEvent.page(); - // Connect to the opened page. + await newPage.waitForLoadState({ waitUntil: 'domcontentloaded' }); expect(newPage.url()).toBe(server.PREFIX + '/one-style.html'); // Cleanup. await context.close(); }); - it('should have an opener', async({browser, server}) => { + it.fail(FFOX)('should have an opener', async({browser, server}) => { const context = await browser.newContext(); const page = await context.newPage(); await page.goto(server.EMPTY_PAGE); const [popup] = await Promise.all([ - context.waitForEvent('page').then(e => e.page()), + context.waitForEvent('page'), page.goto(server.PREFIX + '/popup/window-open.html') ]); + // The url is still about:blank in FF when 'page' event is fired. expect(popup.url()).toBe(server.PREFIX + '/popup/popup.html'); expect(await popup.opener()).toBe(page); expect(await page.opener()).toBe(null); @@ -595,8 +599,7 @@ module.exports.describe = function({testRunner, expect, playwright, CHROMIUM, FF it('should fire page lifecycle events', async function({browser, server}) { const context = await browser.newContext(); const events = []; - context.on('page', async event => { - const page = await event.page(); + context.on('page', async page => { events.push('CREATED: ' + page.url()); page.on('close', () => events.push('DESTROYED: ' + page.url())); }); diff --git a/test/chromium/launcher.spec.js b/test/chromium/launcher.spec.js index 12b15971bc..d5c81432ad 100644 --- a/test/chromium/launcher.spec.js +++ b/test/chromium/launcher.spec.js @@ -83,7 +83,7 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, b const backgroundPages = context.backgroundPages(); let backgroundPage = backgroundPages.length ? backgroundPages[0] - : await context.waitForEvent('backgroundpage').then(event => event.page()); + : await context.waitForEvent('backgroundpage'); expect(backgroundPage).toBeTruthy(); expect(context.backgroundPages()).toContain(backgroundPage); expect(context.pages()).not.toContain(backgroundPage); diff --git a/test/elementhandle.spec.js b/test/elementhandle.spec.js index 9c9a432d4f..5bb2fa2cbb 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(e => e.page()), + page.waitForEvent('popup'), page.evaluate(url => window.__popup = window.open(url), server.EMPTY_PAGE), ]); const divHandle = await page.evaluateHandle(() => { @@ -218,6 +218,7 @@ module.exports.describe = function({testRunner, expect, FFOX, CHROMIUM, WEBKIT}) return div; }); expect(await divHandle.ownerFrame()).toBe(page.mainFrame()); + await popup.waitForLoadState({ waitUntil: 'domcontentloaded' }); await page.evaluate(() => { const div = document.querySelector('div'); window.__popup.document.body.appendChild(div); diff --git a/test/navigation.spec.js b/test/navigation.spec.js index 8e8dc4664b..e7db690c4d 100644 --- a/test/navigation.spec.js +++ b/test/navigation.spec.js @@ -835,14 +835,99 @@ module.exports.describe = function({testRunner, expect, playwright, MAC, WIN, FF it('should work with pages that have loaded before being connected to', async({page, context, server}) => { await page.goto(server.EMPTY_PAGE); const [popup] = await Promise.all([ - page.waitForEvent('popup').then(e => e.page()), + page.waitForEvent('popup'), page.evaluate(() => window._popup = window.open(document.location.href)), ]); - expect(popup.url()).toBe(server.EMPTY_PAGE); + // The url is about:blank in FF. + // expect(popup.url()).toBe(server.EMPTY_PAGE); await popup.waitForLoadState(); expect(popup.url()).toBe(server.EMPTY_PAGE); }); - }); + it.fail(FFOX)('should wait for load state of empty url popup', async({browser, page}) => { + const [popup] = await Promise.all([ + page.waitForEvent('popup'), + page.evaluate(() => window.open('') && 1), + ]); + await popup.waitForLoadState({ waitUntil: 'load' }); + expect(await popup.evaluate(() => document.readyState)).toBe('complete'); + }); + it('should wait for load state of about:blank popup ', async({browser, page}) => { + const [popup] = await Promise.all([ + page.waitForEvent('popup'), + page.evaluate(() => window.open('about:blank') && 1), + ]); + await popup.waitForLoadState({ waitUntil: 'load' }); + expect(await popup.evaluate(() => document.readyState)).toBe('complete'); + }); + it('should wait for load state of about:blank popup with noopener ', async({browser, page}) => { + const [popup] = await Promise.all([ + page.waitForEvent('popup'), + page.evaluate(() => window.open('about:blank', null, 'noopener') && 1), + ]); + await popup.waitForLoadState({ waitUntil: 'load' }); + expect(await popup.evaluate(() => document.readyState)).toBe('complete'); + }); + it('should wait for load state of popup with network url ', async({browser, page, server}) => { + await page.goto(server.EMPTY_PAGE); + const [popup] = await Promise.all([ + page.waitForEvent('popup'), + page.evaluate(url => window.open(url) && 1, server.EMPTY_PAGE), + ]); + await popup.waitForLoadState({ waitUntil: 'load' }); + expect(await popup.evaluate(() => document.readyState)).toBe('complete'); + }); + it('should wait for load state of popup with network url and noopener ', async({browser, page, server}) => { + await page.goto(server.EMPTY_PAGE); + const [popup] = await Promise.all([ + page.waitForEvent('popup'), + page.evaluate(url => window.open(url, null, 'noopener') && 1, server.EMPTY_PAGE), + ]); + await popup.waitForLoadState({ waitUntil: 'load' }); + expect(await popup.evaluate(() => document.readyState)).toBe('complete'); + }); + it('should work with clicking target=_blank', async({browser, page, server}) => { + await page.goto(server.EMPTY_PAGE); + await page.setContent('yo'); + const [popup] = await Promise.all([ + page.waitForEvent('popup'), + page.click('a'), + ]); + await popup.waitForLoadState({ waitUntil: 'load' }); + expect(await popup.evaluate(() => document.readyState)).toBe('complete'); + }); + it('should wait for load state of newPage', async({browser, context, page, server}) => { + const [newPage] = await Promise.all([ + context.waitForEvent('page'), + context.newPage(), + ]); + await newPage.waitForLoadState({ waitUntil: 'load' }); + expect(await newPage.evaluate(() => document.readyState)).toBe('complete'); + }); + it('should resolve after popup load', async({browser, server}) => { + const context = await browser.newContext(); + const page = await context.newPage(); + await page.goto(server.EMPTY_PAGE); + // Stall the 'load' by delaying css. + let cssResponse; + server.setRoute('/one-style.css', (req, res) => cssResponse = res); + const [popup] = await Promise.all([ + page.waitForEvent('popup'), + server.waitForRequest('/one-style.css'), + page.evaluate(url => window.popup = window.open(url), server.PREFIX + '/one-style.html'), + ]); + let resolved = false; + const loadSatePromise = popup.waitForLoadState({waitUntil: 'load'}).then(() => resolved = true); + // Round trips! + for (let i = 0; i < 5; i++) + await page.evaluate('window'); + expect(resolved).toBe(false); + cssResponse.end(''); + await loadSatePromise; + expect(resolved).toBe(true); + expect(popup.url()).toBe(server.PREFIX + '/one-style.html'); + await context.close(); + }); +}); describe('Page.goBack', function() { it('should work', async({page, server}) => { diff --git a/test/page.spec.js b/test/page.spec.js index f9ddf6a347..339b1c14cc 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([ - page.waitForEvent('popup').then(e => e.page()), + page.waitForEvent('popup'), 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([ - page.waitForEvent('popup').then(e => e.page()), + page.waitForEvent('popup'), page.evaluate(() => window.open('about:blank')), ]); await page.close(); @@ -227,7 +227,7 @@ module.exports.describe = function({testRunner, expect, headless, playwright, FF it.fail(FFOX)('should not throw when there are console messages in detached iframes', async({page, server}) => { await page.goto(server.EMPTY_PAGE); const [popup] = await Promise.all([ - page.waitForEvent('popup').then(e => e.page()), + page.waitForEvent('popup'), page.evaluate(async() => { // 1. Create a popup that Playwright is not connected to. const win = window.open(''); @@ -1060,7 +1060,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 = page.waitForEvent('popup').then(e => e.page()); + const newPagePromise = page.waitForEvent('popup'); 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 0901165685..53f3182314 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([ - context.waitForEvent('page').then(pageEvent => pageEvent.page()), + context.waitForEvent('page'), page.click('a'), ]); const userAgent = await popup.evaluate(() => window.initialUserAgent); @@ -48,8 +48,8 @@ module.exports.describe = function({testRunner, expect, playwright, CHROMIUM, WE route.continue(); intercepted = true; }); - const [popup] = await Promise.all([ - context.waitForEvent('page').then(pageEvent => pageEvent.page()), + await Promise.all([ + context.waitForEvent('page'), page.click('a'), ]); await context.close(); @@ -106,9 +106,10 @@ module.exports.describe = function({testRunner, expect, playwright, CHROMIUM, WE const page = await context.newPage(); await page.goto(server.EMPTY_PAGE); const [popup] = await Promise.all([ - page.waitForEvent('popup').then(e => e.page()), + page.waitForEvent('popup'), page.evaluate(url => window._popup = window.open(url), server.PREFIX + '/title.html'), ]); + await popup.waitForLoadState({waitUntil: 'domcontentloaded'}); expect(await popup.title()).toBe('Woof-Woof'); await context.close(); }); @@ -183,7 +184,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([ - page.waitForEvent('popup').then(e => e.page()), + page.waitForEvent('popup'), page.evaluate(() => window.__popup = window.open('about:blank')), ]); expect(await page.evaluate(() => !!window.opener)).toBe(false); @@ -195,7 +196,7 @@ module.exports.describe = function({testRunner, expect, playwright, CHROMIUM, WE const page = await context.newPage(); await page.goto(server.EMPTY_PAGE); const [popup] = await Promise.all([ - page.waitForEvent('popup').then(e => e.page()), + page.waitForEvent('popup'), page.evaluate(() => window.__popup = window.open(window.location.href, 'Title', 'toolbar=no,location=no,directories=no,status=no,menubar=no,scrollbars=yes,resizable=yes,width=780,height=200,top=0,left=0')), ]); expect(await page.evaluate(() => !!window.opener)).toBe(false); @@ -205,75 +206,16 @@ module.exports.describe = function({testRunner, expect, playwright, CHROMIUM, WE it('should emit for immediately closed popups', async({browser}) => { const context = await browser.newContext(); const page = await context.newPage(); - const [popupEvent] = await Promise.all([ + const [popup] = await Promise.all([ page.waitForEvent('popup'), page.evaluate(() => { const win = window.open('about:blank'); win.close(); }), ]); - expect(popupEvent).toBeTruthy(); - await context.close(); - }); - it('should resolve page() after initial navigation', async({browser, server}) => { - const context = await browser.newContext(); - const page = await context.newPage(); - await page.goto(server.EMPTY_PAGE); - // First popup navigation is about:blank. - const [popupEvent] = await Promise.all([ - page.waitForEvent('popup'), - page.evaluate(() => window.popup = window.open('about:blank')), - ]); - // Stall the 'load' for second navigation. - server.setRoute('/one-style.css', (req, res) => {}); - await Promise.all([ - server.waitForRequest('/one-style.css'), - page.evaluate(url => window.popup.location.href = url, server.PREFIX + '/one-style.html'), - ]); - // Second navigation should be committed, but page() should resolve because first navigation is done. - const popup = await popupEvent.page(); expect(popup).toBeTruthy(); await context.close(); }); - it('should resolve page() after load', async({browser, server}) => { - const context = await browser.newContext(); - const page = await context.newPage(); - await page.goto(server.EMPTY_PAGE); - // Stall the 'load' by delaying css. - let cssResponse; - server.setRoute('/one-style.css', (req, res) => cssResponse = res); - const [popupEvent] = await Promise.all([ - page.waitForEvent('popup'), - server.waitForRequest('/one-style.css'), - page.evaluate(url => window.popup = window.open(url), server.PREFIX + '/one-style.html'), - ]); - let resolved = false; - const popupPromise = popupEvent.page().then(page => { resolved = true; return page; }); - expect(resolved).toBe(false); - // Round trips! - for (let i = 0; i < 5; i++) - await page.evaluate('window'); - expect(resolved).toBe(false); - cssResponse.end(''); - const popup = await popupPromise; - expect(resolved).toBe(true); - expect(popup.url()).toBe(server.PREFIX + '/one-style.html'); - await context.close(); - }); - it('should respect timeout in page()', async({browser, server}) => { - const context = await browser.newContext(); - const page = await context.newPage(); - await page.goto(server.EMPTY_PAGE); - // Stall the 'load' by delaying css. - server.setRoute('/one-style.css', (req, res) => {}); - const [popupEvent] = await Promise.all([ - page.waitForEvent('popup'), - page.evaluate(url => window.popup = window.open(url), server.PREFIX + '/one-style.html'), - ]); - const error = await popupEvent.page({ timeout: 1 }).catch(e => e); - expect(error.message).toBe('waiting for "load" failed: timeout 1ms exceeded'); - await context.close(); - }); it.fail(FFOX)('should be able to capture alert', async({browser}) => { // Firefox: // - immediately closes dialog by itself, without protocol call; @@ -284,52 +226,29 @@ module.exports.describe = function({testRunner, expect, playwright, CHROMIUM, WE const win = window.open('about:blank'); win.alert('hello'); }); - const popupEvent = await page.waitForEvent('popup'); - const popup = await popupEvent.page(); + const popup = await page.waitForEvent('popup'); const dialog = await popup.waitForEvent('dialog'); expect(dialog.message()).toBe('hello'); await dialog.dismiss(); await evaluatePromise; await context.close(); }); - it.fail(FFOX)('should work with empty url', async({browser}) => { + it('should work with empty url', async({browser}) => { const context = await browser.newContext(); const page = await context.newPage(); const [popup] = await Promise.all([ - page.waitForEvent('popup').then(e => e.page()), + page.waitForEvent('popup'), page.evaluate(() => window.__popup = window.open('')), ]); expect(await page.evaluate(() => !!window.opener)).toBe(false); expect(await popup.evaluate(() => !!window.opener)).toBe(true); await context.close(); }); - it('should work with empty url and nowait', async({browser}) => { + it('should work with noopener and about:blank', async({browser}) => { const context = await browser.newContext(); const page = await context.newPage(); const [popup] = await Promise.all([ - page.waitForEvent('popup').then(e => e.page({ waitUntil: 'nowait' })), - page.evaluate(() => window.__popup = window.open()), - ]); - expect(await page.evaluate(() => !!window.opener)).toBe(false); - expect(await popup.evaluate(() => !!window.opener)).toBe(true); - await context.close(); - }); - it('should work with noopener', async({browser}) => { - const context = await browser.newContext(); - const page = await context.newPage(); - const [popup] = await Promise.all([ - 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); - expect(await popup.evaluate(() => !!window.opener)).toBe(false); - await context.close(); - }); - it('should work with noopener and nowait', async({browser}) => { - const context = await browser.newContext(); - const page = await context.newPage(); - const [popup] = await Promise.all([ - page.waitForEvent('popup').then(e => e.page({ waitUntil: 'nowait' })), + page.waitForEvent('popup'), page.evaluate(() => window.__popup = window.open('about:blank', null, 'noopener')), ]); expect(await page.evaluate(() => !!window.opener)).toBe(false); @@ -341,19 +260,7 @@ module.exports.describe = function({testRunner, expect, playwright, CHROMIUM, WE const page = await context.newPage(); await page.goto(server.EMPTY_PAGE); const [popup] = await Promise.all([ - page.waitForEvent('popup').then(e => e.page()), - page.evaluate(url => window.__popup = window.open(url, null, 'noopener'), server.EMPTY_PAGE), - ]); - expect(await page.evaluate(() => !!window.opener)).toBe(false); - expect(await popup.evaluate(() => !!window.opener)).toBe(false); - await context.close(); - }); - it('should work with noopener and url and nowait', async({browser, server}) => { - const context = await browser.newContext(); - const page = await context.newPage(); - await page.goto(server.EMPTY_PAGE); - const [popup] = await Promise.all([ - page.waitForEvent('popup').then(e => e.page({ waitUntil: 'nowait' })), + page.waitForEvent('popup'), page.evaluate(url => window.__popup = window.open(url, null, 'noopener'), server.EMPTY_PAGE), ]); expect(await page.evaluate(() => !!window.opener)).toBe(false); @@ -366,20 +273,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(e => e.page()), - page.click('a'), - ]); - expect(await page.evaluate(() => !!window.opener)).toBe(false); - expect(await popup.evaluate(() => !!window.opener)).toBe(true); - await context.close(); - }); - it('should work with clicking target=_blank and nowait', async({browser, server}) => { - const context = await browser.newContext(); - const page = await context.newPage(); - await page.goto(server.EMPTY_PAGE); - await page.setContent('yo'); - const [popup] = await Promise.all([ - page.waitForEvent('popup').then(e => e.page({ waitUntil: 'nowait' })), + page.waitForEvent('popup'), page.click('a'), ]); expect(await page.evaluate(() => !!window.opener)).toBe(false); @@ -393,7 +287,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(e => e.page()), + page.waitForEvent('popup'), page.$eval('a', a => a.click()), ]); expect(await page.evaluate(() => !!window.opener)).toBe(false); @@ -408,7 +302,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(e => e.page()), + page.waitForEvent('popup'), page.click('a'), ]); expect(await page.evaluate(() => !!window.opener)).toBe(false); @@ -421,7 +315,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(e => e.page()), + page.waitForEvent('popup'), page.click('a'), ]); let badSecondPopup = false;