feat(urlmatch): support url matching in waitForNavigation (#300)

This commit is contained in:
Dmitry Gozman 2019-12-18 18:03:02 -08:00 committed by Andrey Lushnikov
parent 1c2b6444e9
commit 3669dad243
3 changed files with 67 additions and 28 deletions

View File

@ -42,6 +42,8 @@ export type NavigateOptions = {
waitUntil?: LifecycleEvent | LifecycleEvent[], waitUntil?: LifecycleEvent | LifecycleEvent[],
}; };
export type WaitForNavigationOptions = NavigateOptions & types.URLMatch;
export type GotoOptions = NavigateOptions & { export type GotoOptions = NavigateOptions & {
referer?: string, referer?: string,
}; };
@ -284,13 +286,11 @@ export class Frame {
this._parentFrame._childFrames.add(this); this._parentFrame._childFrames.add(this);
} }
async goto(url: string, options: GotoOptions = {}): Promise<network.Response | null> { async goto(url: string, options?: GotoOptions): Promise<network.Response | null> {
const { let referer = (this._page._state.extraHTTPHeaders || {})['referer'];
referer = (this._page._state.extraHTTPHeaders || {})['referer'], if (options && options.referer !== undefined)
waitUntil = (['load'] as LifecycleEvent[]), referer = options.referer;
timeout = this._page._timeoutSettings.navigationTimeout(), const watcher = new LifecycleWatcher(this, options, false /* supportUrlMatch */);
} = options;
const watcher = new LifecycleWatcher(this, waitUntil, timeout);
let navigateResult: GotoResult; let navigateResult: GotoResult;
const navigate = async () => { const navigate = async () => {
@ -323,12 +323,8 @@ export class Frame {
return watcher.navigationResponse(); return watcher.navigationResponse();
} }
async waitForNavigation(options: NavigateOptions = {}): Promise<network.Response | null> { async waitForNavigation(options?: WaitForNavigationOptions): Promise<network.Response | null> {
const { const watcher = new LifecycleWatcher(this, options, true /* supportUrlMatch */);
waitUntil = (['load'] as LifecycleEvent[]),
timeout = this._page._timeoutSettings.navigationTimeout(),
} = options;
const watcher = new LifecycleWatcher(this, waitUntil, timeout);
const error = await Promise.race([ const error = await Promise.race([
watcher.timeoutOrTerminationPromise, watcher.timeoutOrTerminationPromise,
watcher.sameDocumentNavigationPromise, watcher.sameDocumentNavigationPromise,
@ -430,11 +426,7 @@ export class Frame {
}); });
} }
async setContent(html: string, options: NavigateOptions = {}): Promise<void> { async setContent(html: string, options?: NavigateOptions): Promise<void> {
const {
waitUntil = (['load'] as LifecycleEvent[]),
timeout = this._page._timeoutSettings.navigationTimeout(),
} = options;
const context = await this._utilityContext(); const context = await this._utilityContext();
if (this._page._delegate.needsLifecycleResetOnSetContent()) if (this._page._delegate.needsLifecycleResetOnSetContent())
this._firedLifecycleEvents.clear(); this._firedLifecycleEvents.clear();
@ -443,7 +435,7 @@ export class Frame {
document.write(html); document.write(html);
document.close(); document.close();
}, html); }, html);
const watcher = new LifecycleWatcher(this, waitUntil, timeout); const watcher = new LifecycleWatcher(this, options, false /* supportUrlMatch */);
const error = await Promise.race([ const error = await Promise.race([
watcher.timeoutOrTerminationPromise, watcher.timeoutOrTerminationPromise,
watcher.lifecyclePromise, watcher.lifecyclePromise,
@ -869,14 +861,22 @@ class LifecycleWatcher {
private _hasSameDocumentNavigation: boolean; private _hasSameDocumentNavigation: boolean;
private _targetUrl?: string; private _targetUrl?: string;
private _expectedDocumentId?: string; private _expectedDocumentId?: string;
private _urlMatch?: types.URLMatch;
constructor(frame: Frame, waitUntil: LifecycleEvent | LifecycleEvent[], timeout: number) { constructor(frame: Frame, options: WaitForNavigationOptions | undefined, supportUrlMatch: boolean) {
if (Array.isArray(waitUntil)) options = options || {};
waitUntil = waitUntil.slice(); let {
else if (typeof waitUntil === 'string') waitUntil = (['load'] as LifecycleEvent[]),
timeout = frame._page._timeoutSettings.navigationTimeout()
} = options;
if (!Array.isArray(waitUntil))
waitUntil = [waitUntil]; waitUntil = [waitUntil];
if (waitUntil.some(e => !kLifecycleEvents.has(e))) for (const event of waitUntil) {
throw new Error('Unsupported waitUntil option'); if (!kLifecycleEvents.has(event))
throw new Error(`Unsupported waitUntil option ${String(event)}`);
}
if (supportUrlMatch)
this._urlMatch = options;
this._expectedLifecycle = waitUntil.slice(); this._expectedLifecycle = waitUntil.slice();
this._frame = frame; this._frame = frame;
this.sameDocumentNavigationPromise = new Promise(f => this._sameDocumentNavigationCompleteCallback = f); this.sameDocumentNavigationPromise = new Promise(f => this._sameDocumentNavigationCompleteCallback = f);
@ -892,7 +892,12 @@ class LifecycleWatcher {
this._checkLifecycleComplete(); this._checkLifecycleComplete();
} }
private _urlMatches(urlString: string): boolean {
return !this._urlMatch || helper.urlMatches(urlString, this._urlMatch);
}
setExpectedDocumentId(documentId: string, url: string) { setExpectedDocumentId(documentId: string, url: string) {
assert(!this._urlMatch, 'Should not have url match when expecting a particular navigation');
this._expectedDocumentId = documentId; this._expectedDocumentId = documentId;
this._targetUrl = url; this._targetUrl = url;
if (this._navigationRequest && this._navigationRequest._documentId !== documentId) if (this._navigationRequest && this._navigationRequest._documentId !== documentId)
@ -916,7 +921,7 @@ class LifecycleWatcher {
_onNavigationRequest(frame: Frame, request: network.Request) { _onNavigationRequest(frame: Frame, request: network.Request) {
assert(request._documentId); assert(request._documentId);
if (frame !== this._frame) if (frame !== this._frame || !this._urlMatches(request.url()))
return; return;
if (this._expectedDocumentId === undefined || this._expectedDocumentId === request._documentId) { if (this._expectedDocumentId === undefined || this._expectedDocumentId === request._documentId) {
this._navigationRequest = request; this._navigationRequest = request;
@ -926,7 +931,7 @@ class LifecycleWatcher {
} }
_onCommittedNewDocumentNavigation(frame: Frame) { _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._expectedDocumentId = frame._lastDocumentId;
this._targetUrl = frame.url(); this._targetUrl = frame.url();
} }

View File

@ -297,7 +297,7 @@ export class Page extends EventEmitter {
return waitPromise; return waitPromise;
} }
waitForNavigation(options?: frames.NavigateOptions): Promise<network.Response | null> { async waitForNavigation(options?: frames.WaitForNavigationOptions): Promise<network.Response | null> {
return this.mainFrame().waitForNavigation(options); return this.mainFrame().waitForNavigation(options);
} }

View File

@ -582,6 +582,40 @@ module.exports.describe = function({testRunner, expect, playwright, FFOX, CHROME
navigationPromise 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() { describe('Page.goBack', function() {