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[],
};
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<network.Response | null> {
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<network.Response | null> {
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<network.Response | null> {
const {
waitUntil = (['load'] as LifecycleEvent[]),
timeout = this._page._timeoutSettings.navigationTimeout(),
} = options;
const watcher = new LifecycleWatcher(this, waitUntil, timeout);
async waitForNavigation(options?: WaitForNavigationOptions): Promise<network.Response | null> {
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<void> {
const {
waitUntil = (['load'] as LifecycleEvent[]),
timeout = this._page._timeoutSettings.navigationTimeout(),
} = options;
async setContent(html: string, options?: NavigateOptions): Promise<void> {
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();
}

View File

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

View File

@ -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() {