diff --git a/src/frames.ts b/src/frames.ts index 492f396b32..7777255c9e 100644 --- a/src/frames.ts +++ b/src/frames.ts @@ -42,6 +42,8 @@ export type NavigateOptions = { waitUntil?: LifecycleEvent | LifecycleEvent[], }; +export type WaitForNavigationOptions = NavigateOptions & types.URLMatch; + export type GotoOptions = NavigateOptions & { referer?: string, }; @@ -284,13 +286,11 @@ export class Frame { this._parentFrame._childFrames.add(this); } - async goto(url: string, options: GotoOptions = {}): Promise { - const { - referer = (this._page._state.extraHTTPHeaders || {})['referer'], - waitUntil = (['load'] as LifecycleEvent[]), - timeout = this._page._timeoutSettings.navigationTimeout(), - } = options; - const watcher = new LifecycleWatcher(this, waitUntil, timeout); + async goto(url: string, options?: GotoOptions): Promise { + let referer = (this._page._state.extraHTTPHeaders || {})['referer']; + if (options && options.referer !== undefined) + referer = options.referer; + const watcher = new LifecycleWatcher(this, options, false /* supportUrlMatch */); let navigateResult: GotoResult; const navigate = async () => { @@ -323,12 +323,8 @@ export class Frame { return watcher.navigationResponse(); } - async waitForNavigation(options: NavigateOptions = {}): Promise { - const { - waitUntil = (['load'] as LifecycleEvent[]), - timeout = this._page._timeoutSettings.navigationTimeout(), - } = options; - const watcher = new LifecycleWatcher(this, waitUntil, timeout); + async waitForNavigation(options?: WaitForNavigationOptions): Promise { + const watcher = new LifecycleWatcher(this, options, true /* supportUrlMatch */); const error = await Promise.race([ watcher.timeoutOrTerminationPromise, watcher.sameDocumentNavigationPromise, @@ -430,11 +426,7 @@ export class Frame { }); } - async setContent(html: string, options: NavigateOptions = {}): Promise { - const { - waitUntil = (['load'] as LifecycleEvent[]), - timeout = this._page._timeoutSettings.navigationTimeout(), - } = options; + async setContent(html: string, options?: NavigateOptions): Promise { const context = await this._utilityContext(); if (this._page._delegate.needsLifecycleResetOnSetContent()) this._firedLifecycleEvents.clear(); @@ -443,7 +435,7 @@ export class Frame { document.write(html); document.close(); }, html); - const watcher = new LifecycleWatcher(this, waitUntil, timeout); + const watcher = new LifecycleWatcher(this, options, false /* supportUrlMatch */); const error = await Promise.race([ watcher.timeoutOrTerminationPromise, watcher.lifecyclePromise, @@ -869,14 +861,22 @@ class LifecycleWatcher { private _hasSameDocumentNavigation: boolean; private _targetUrl?: string; private _expectedDocumentId?: string; + private _urlMatch?: types.URLMatch; - constructor(frame: Frame, waitUntil: LifecycleEvent | LifecycleEvent[], timeout: number) { - if (Array.isArray(waitUntil)) - waitUntil = waitUntil.slice(); - else if (typeof waitUntil === 'string') + constructor(frame: Frame, options: WaitForNavigationOptions | undefined, supportUrlMatch: boolean) { + options = options || {}; + let { + waitUntil = (['load'] as LifecycleEvent[]), + timeout = frame._page._timeoutSettings.navigationTimeout() + } = options; + if (!Array.isArray(waitUntil)) waitUntil = [waitUntil]; - if (waitUntil.some(e => !kLifecycleEvents.has(e))) - throw new Error('Unsupported waitUntil option'); + for (const event of waitUntil) { + if (!kLifecycleEvents.has(event)) + throw new Error(`Unsupported waitUntil option ${String(event)}`); + } + if (supportUrlMatch) + this._urlMatch = options; this._expectedLifecycle = waitUntil.slice(); this._frame = frame; this.sameDocumentNavigationPromise = new Promise(f => this._sameDocumentNavigationCompleteCallback = f); @@ -892,7 +892,12 @@ class LifecycleWatcher { this._checkLifecycleComplete(); } + private _urlMatches(urlString: string): boolean { + return !this._urlMatch || helper.urlMatches(urlString, this._urlMatch); + } + setExpectedDocumentId(documentId: string, url: string) { + assert(!this._urlMatch, 'Should not have url match when expecting a particular navigation'); this._expectedDocumentId = documentId; this._targetUrl = url; if (this._navigationRequest && this._navigationRequest._documentId !== documentId) @@ -916,7 +921,7 @@ class LifecycleWatcher { _onNavigationRequest(frame: Frame, request: network.Request) { assert(request._documentId); - if (frame !== this._frame) + if (frame !== this._frame || !this._urlMatches(request.url())) return; if (this._expectedDocumentId === undefined || this._expectedDocumentId === request._documentId) { this._navigationRequest = request; @@ -926,7 +931,7 @@ class LifecycleWatcher { } _onCommittedNewDocumentNavigation(frame: Frame) { - if (frame === this._frame && this._expectedDocumentId === undefined) { + if (frame === this._frame && this._expectedDocumentId === undefined && this._urlMatches(frame.url())) { this._expectedDocumentId = frame._lastDocumentId; this._targetUrl = frame.url(); } diff --git a/src/page.ts b/src/page.ts index a1851e28b5..96e18c8735 100644 --- a/src/page.ts +++ b/src/page.ts @@ -297,7 +297,7 @@ export class Page extends EventEmitter { return waitPromise; } - waitForNavigation(options?: frames.NavigateOptions): Promise { + async waitForNavigation(options?: frames.WaitForNavigationOptions): Promise { return this.mainFrame().waitForNavigation(options); } diff --git a/test/navigation.spec.js b/test/navigation.spec.js index 2791bb0216..a830186b5a 100644 --- a/test/navigation.spec.js +++ b/test/navigation.spec.js @@ -582,6 +582,40 @@ module.exports.describe = function({testRunner, expect, playwright, FFOX, CHROME navigationPromise ]); }); + it('should work with url match', async({page, server}) => { + let response1 = null; + const response1Promise = page.waitForNavigation({ url: /one-style\.html/ }).then(response => response1 = response); + let response2 = null; + const response2Promise = page.waitForNavigation({ pathname: '/frame.html' }).then(response => response2 = response); + let response3 = null; + const response3Promise = page.waitForNavigation({ searchParams: { 'foo': 'bar' }, strictSearchParams: true }).then(response => response3 = response); + expect(response1).toBe(null); + expect(response2).toBe(null); + expect(response3).toBe(null); + await page.goto(server.EMPTY_PAGE); + expect(response1).toBe(null); + expect(response2).toBe(null); + expect(response3).toBe(null); + await page.goto(server.PREFIX + '/frame.html'); + expect(response1).toBe(null); + await response2Promise; + expect(response2).not.toBe(null); + expect(response3).toBe(null); + await page.goto(server.PREFIX + '/one-style.html'); + await response1Promise; + expect(response1).not.toBe(null); + expect(response2).not.toBe(null); + expect(response3).toBe(null); + await page.goto(server.PREFIX + '/frame.html?foo=bar'); + await response3Promise; + expect(response1).not.toBe(null); + expect(response2).not.toBe(null); + expect(response3).not.toBe(null); + await page.goto(server.PREFIX + '/empty.html'); + expect(response1.url()).toBe(server.PREFIX + '/one-style.html'); + expect(response2.url()).toBe(server.PREFIX + '/frame.html'); + expect(response3.url()).toBe(server.PREFIX + '/frame.html?foo=bar'); + }); }); describe('Page.goBack', function() {