feat(navigation): introduce waitForNavigationIfNeeded (#310)

This one waits for current navigation to finish, matching by url if asked.
If there is no current navigation or current navigation/url does not match,
it waits for the next matching one.
This commit is contained in:
Dmitry Gozman 2019-12-20 15:32:30 -08:00 committed by GitHub
parent d094c602a7
commit 735d3eeed7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 85 additions and 11 deletions

View File

@ -15,7 +15,6 @@
* limitations under the License.
*/
import { EventEmitter } from 'events';
import { Events } from './events';
import { Events as CommonEvents } from '../events';
import { assert, helper } from '../helper';
@ -156,11 +155,11 @@ export class CRBrowser extends browser.Browser {
});
await this._client.send('Browser.grantPermissions', { origin, browserContextId: contextId || undefined, permissions: filtered });
},
clearPermissions: async () => {
await this._client.send('Browser.resetPermissions', { browserContextId: contextId || undefined });
}
}, options);
overrides = new CROverrides(context);
(context as any).overrides = overrides;

View File

@ -210,11 +210,11 @@ export class FFBrowser extends browser.Browser {
});
await this._connection.send('Browser.grantPermissions', {origin, browserContextId: browserContextId || undefined, permissions: filtered});
},
clearPermissions: async () => {
await this._connection.send('Browser.resetPermissions', { browserContextId: browserContextId || undefined });
}
}, options);
return context;
}

View File

@ -336,6 +336,17 @@ export class Frame {
return watcher.navigationResponse();
}
async waitForLoadState(options?: NavigateOptions): Promise<void> {
const watcher = new LifecycleWatcher(this, options, false /* supportUrlMatch */);
const error = await Promise.race([
watcher.timeoutOrTerminationPromise,
watcher.lifecyclePromise
]);
watcher.dispose();
if (error)
throw error;
}
_context(contextType: ContextType): Promise<dom.FrameExecutionContext> {
if (this._detached)
throw new Error(`Execution Context is not available in detached frame "${this.url()}" (are you trying to evaluate?)`);
@ -886,7 +897,7 @@ class LifecycleWatcher {
this._checkLifecycleComplete();
}
private _urlMatches(urlString: string): boolean {
_urlMatches(urlString: string): boolean {
return !this._urlMatch || helper.urlMatches(urlString, this._urlMatch);
}
@ -969,12 +980,13 @@ class LifecycleWatcher {
}
private _checkLifecycleComplete() {
// We expect navigation to commit.
if (!this._checkLifecycleRecursively(this._frame, this._expectedLifecycle))
return;
this._lifecycleCallback();
if (this._hasSameDocumentNavigation)
this._sameDocumentNavigationCompleteCallback();
if (this._urlMatches(this._frame.url())) {
this._lifecycleCallback();
if (this._hasSameDocumentNavigation)
this._sameDocumentNavigationCompleteCallback();
}
if (this._frame._lastDocumentId === this._expectedDocumentId)
this._newDocumentNavigationCompleteCallback();
}

View File

@ -301,6 +301,10 @@ export class Page extends EventEmitter {
return this.mainFrame().waitForNavigation(options);
}
async waitForLoadState(options?: frames.NavigateOptions): Promise<void> {
return this.mainFrame().waitForLoadState(options);
}
async waitForEvent(event: string, options: Function | (types.TimeoutOptions & { predicate?: Function }) = {}): Promise<any> {
if (typeof options === 'function')
options = { predicate: options };

View File

@ -210,7 +210,7 @@ export class WKBrowser extends browser.Browser {
setPermissions: async (origin: string, permissions: string[]): Promise<void> => {
throw new Error('Permissions are not supported in WebKit');
},
clearPermissions: async () => {
throw new Error('Permissions are not supported in WebKit');
}

View File

@ -698,6 +698,65 @@ module.exports.describe = function({testRunner, expect, playwright, FFOX, CHROME
expect(response2.url()).toBe(server.PREFIX + '/frame.html');
expect(response3.url()).toBe(server.PREFIX + '/frame.html?foo=bar');
});
it('should work with url match for same document navigations', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
let resolved = false;
const waitPromise = page.waitForNavigation({ url: /third\.html/ }).then(() => resolved = true);
expect(resolved).toBe(false);
await page.evaluate(() => {
history.pushState({}, '', '/first.html');
});
expect(resolved).toBe(false);
await page.evaluate(() => {
history.pushState({}, '', '/second.html');
});
expect(resolved).toBe(false);
await page.evaluate(() => {
history.pushState({}, '', '/third.html');
});
await waitPromise;
expect(resolved).toBe(true);
});
});
describe('Page.waitForLoadState', () => {
it('should pick up ongoing navigation', async({page, server}) => {
let response = null;
server.setRoute('/one-style.css', (req, res) => response = res);
const navigationPromise = page.goto(server.PREFIX + '/one-style.html');
await server.waitForRequest('/one-style.css');
const waitPromise = page.waitForLoadState();
response.statusCode = 404;
response.end('Not found');
await waitPromise;
await navigationPromise;
});
it('should respect timeout', async({page, server}) => {
let response = null;
server.setRoute('/one-style.css', (req, res) => response = res);
const navigationPromise = page.goto(server.PREFIX + '/one-style.html');
await server.waitForRequest('/one-style.css');
const error = await page.waitForLoadState({ timeout: 1 }).catch(e => e);
expect(error.message).toBe('Navigation timeout of 1 ms exceeded');
response.statusCode = 404;
response.end('Not found');
await navigationPromise;
});
it('should resolve immediately if loaded', async({page, server}) => {
await page.goto(server.PREFIX + '/one-style.html');
await page.waitForLoadState();
});
it('should resolve immediately if load state matches', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
let response = null;
server.setRoute('/one-style.css', (req, res) => response = res);
const navigationPromise = page.goto(server.PREFIX + '/one-style.html');
await server.waitForRequest('/one-style.css');
await page.waitForLoadState({ waitUntil: 'domcontentloaded' });
response.statusCode = 404;
response.end('Not found');
await navigationPromise;
});
});
describe('Page.goBack', function() {