api(waitForLoadState): restore it (#1390)

This commit is contained in:
Pavel Feldman 2020-03-16 14:39:44 -07:00 committed by GitHub
parent 6731d37546
commit 64b175ce10
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 78 additions and 35 deletions

View File

@ -686,6 +686,7 @@ page.removeListener('request', logRequest);
- [page.waitFor(selectorOrFunctionOrTimeout[, options[, ...args]])](#pagewaitforselectororfunctionortimeout-options-args)
- [page.waitForEvent(event[, optionsOrPredicate])](#pagewaitforeventevent-optionsorpredicate)
- [page.waitForFunction(pageFunction[, options[, ...args]])](#pagewaitforfunctionpagefunction-options-args)
- [page.waitForLoadState([options])](#pagewaitforloadstateoptions)
- [page.waitForNavigation([options])](#pagewaitfornavigationoptions)
- [page.waitForRequest(urlOrPredicate[, options])](#pagewaitforrequesturlorpredicate-options)
- [page.waitForResponse(urlOrPredicate[, options])](#pagewaitforresponseurlorpredicate-options)
@ -1703,6 +1704,26 @@ await page.waitForFunction(selector => !!document.querySelector(selector), {}, s
Shortcut for [page.mainFrame().waitForFunction(pageFunction[, options[, ...args]])](#framewaitforfunctionpagefunction-options-args).
#### page.waitForLoadState([options])
- `options` <[Object]> Navigation parameters which might have the following properties:
- `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"> When to consider navigation succeeded, defaults to `load`. Events can be either:
- `'load'` - consider navigation to be finished when the `load` event is fired.
- `'domcontentloaded'` - consider navigation to be finished when the `DOMContentLoaded` 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]> Promise which resolves when the load state has been achieved.
This resolves when the page reaches a required load state, `load` by default. The navigation can be in progress when it is called.
If navigation is already at a required state, resolves immediately.
```js
await page.click('button'); // Click triggers navigation.
await page.waitForLoadState(); // The promise resolves after navigation has finished.
```
Shortcut for [page.mainFrame().waitForLoadState([options])](#framewaitforloadstateoptions).
#### page.waitForNavigation([options])
- `options` <[Object]> Navigation parameters which might have the following properties:
- `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.
@ -1887,6 +1908,7 @@ An example of getting text from an iframe element:
- [frame.url()](#frameurl)
- [frame.waitFor(selectorOrFunctionOrTimeout[, options[, ...args]])](#framewaitforselectororfunctionortimeout-options-args)
- [frame.waitForFunction(pageFunction[, options[, ...args]])](#framewaitforfunctionpagefunction-options-args)
- [frame.waitForLoadState([options])](#framewaitforloadstateoptions)
- [frame.waitForNavigation([options])](#framewaitfornavigationoptions)
- [frame.waitForSelector(selector[, options])](#framewaitforselectorselector-options)
<!-- GEN:stop -->
@ -2374,6 +2396,24 @@ const selector = '.foo';
await page.waitForFunction(selector => !!document.querySelector(selector), {}, selector);
```
#### frame.waitForLoadState([options])
- `options` <[Object]> Navigation parameters which might have the following properties:
- `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"> When to consider navigation succeeded, defaults to `load`. Events can be either:
- `'load'` - consider navigation to be finished when the `load` event is fired.
- `'domcontentloaded'` - consider navigation to be finished when the `DOMContentLoaded` 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]> Promise which resolves when the load state has been achieved.
This resolves when the page reaches a required load state, `load` by default. The navigation can be in progress when it is called.
If navigation is already at a required state, resolves immediately.
```js
await frame.click('button'); // Click triggers navigation.
await frame.waitForLoadState(); // The promise resolves after navigation has finished.
```
#### frame.waitForNavigation([options])
- `options` <[Object]> Navigation parameters which might have the following properties:
- `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.

View File

@ -433,8 +433,8 @@ export class Frame {
return request ? request._finalRequest().response() : null;
}
async _waitForLoadState(options: types.NavigateOptions = {}): Promise<void> {
const {timeout = this._page._timeoutSettings.navigationTimeout()} = options;
async waitForLoadState(options: types.NavigateOptions = {}): Promise<void> {
const { timeout = this._page._timeoutSettings.navigationTimeout() } = options;
const disposer = new Disposer();
const error = await Promise.race([
this._createFrameDestroyedPromise(),
@ -464,7 +464,7 @@ export class Frame {
let resolve: (error: {error?: Error, documentId: string}) => void;
const promise = new Promise<{error?: Error, documentId: string}>(x => resolve = x);
const watch = (documentId: string, error?: Error) => {
if (!error && !platform.urlMatches(this.url(), url))
if (!error && !helper.urlMatches(this.url(), url))
return;
resolve({error, documentId});
};
@ -477,7 +477,7 @@ export class Frame {
let resolve: () => void;
const promise = new Promise<void>(x => resolve = x);
const watch = () => {
if (platform.urlMatches(this.url(), url))
if (helper.urlMatches(this.url(), url))
resolve();
};
const dispose = () => this._sameDocumentNavigationWatchers.delete(watch);
@ -639,7 +639,7 @@ export class Frame {
this._page._frameManager._consoleMessageTags.set(tag, () => {
// Clear lifecycle right after document.open() - see 'tag' below.
this._page._frameManager.clearFrameLifecycle(this);
this._waitForLoadState(options).then(resolve).catch(reject);
this.waitForLoadState(options).then(resolve).catch(reject);
});
});
const contentPromise = context.evaluate((html, tag) => {

View File

@ -279,6 +279,23 @@ class Helper {
static enclosingIntSize(size: types.Size): types.Size {
return { width: Math.floor(size.width + 1e-3), height: Math.floor(size.height + 1e-3) };
}
static urlMatches(urlString: string, match: types.URLMatch | undefined): boolean {
if (match === undefined || match === '')
return true;
if (helper.isString(match))
match = helper.globToRegex(match);
if (helper.isRegExp(match))
return match.test(urlString);
if (typeof match === 'string' && match === urlString)
return true;
const url = new URL(urlString);
if (typeof match === 'string')
return url.pathname === match;
assert(typeof match === 'function', 'url parameter should be string, RegExp or function');
return match(url);
}
}
export function assert(value: any, message?: string): asserts value {

View File

@ -234,7 +234,7 @@ export class Page extends platform.EventEmitter {
return this.frames().find(f => {
if (name)
return f.name() === name;
return platform.urlMatches(f.url(), url);
return helper.urlMatches(f.url(), url);
}) || null;
}
@ -332,6 +332,10 @@ export class Page extends platform.EventEmitter {
return waitPromise;
}
async waitForLoadState(options?: types.NavigateOptions): Promise<void> {
return this.mainFrame().waitForLoadState(options);
}
async waitForNavigation(options?: types.WaitForNavigationOptions): Promise<network.Response | null> {
return this.mainFrame().waitForNavigation(options);
}
@ -347,7 +351,7 @@ export class Page extends platform.EventEmitter {
const { timeout = this._timeoutSettings.timeout() } = options;
return helper.waitForEvent(this, Events.Page.Request, (request: network.Request) => {
if (helper.isString(urlOrPredicate) || helper.isRegExp(urlOrPredicate))
return platform.urlMatches(request.url(), urlOrPredicate);
return helper.urlMatches(request.url(), urlOrPredicate);
return urlOrPredicate(request);
}, timeout, this._disconnectedPromise);
}
@ -356,7 +360,7 @@ export class Page extends platform.EventEmitter {
const { timeout = this._timeoutSettings.timeout() } = options;
return helper.waitForEvent(this, Events.Page.Response, (response: network.Response) => {
if (helper.isString(urlOrPredicate) || helper.isRegExp(urlOrPredicate))
return platform.urlMatches(response.url(), urlOrPredicate);
return helper.urlMatches(response.url(), urlOrPredicate);
return urlOrPredicate(response);
}, timeout, this._disconnectedPromise);
}
@ -423,13 +427,13 @@ export class Page extends platform.EventEmitter {
if (!route)
return;
for (const { url, handler } of this._routes) {
if (platform.urlMatches(request.url(), url)) {
if (helper.urlMatches(request.url(), url)) {
handler(route, request);
return;
}
}
for (const { url, handler } of this._browserContext._routes) {
if (platform.urlMatches(request.url(), url)) {
if (helper.urlMatches(request.url(), url)) {
handler(route, request);
return;
}

View File

@ -28,7 +28,6 @@ import * as https from 'https';
import * as NodeWebSocket from 'ws';
import { assert, helper } from './helper';
import * as types from './types';
import { ConnectionTransport } from './transport';
export const isNode = typeof process === 'object' && !!process && typeof process.versions === 'object' && !!process.versions && !!process.versions.node;
@ -222,23 +221,6 @@ export function getMimeType(file: string): string {
return extensionToMime[extension] || 'application/octet-stream';
}
export function urlMatches(urlString: string, match: types.URLMatch | undefined): boolean {
if (match === undefined || match === '')
return true;
if (helper.isString(match))
match = helper.globToRegex(match);
if (helper.isRegExp(match))
return match.test(urlString);
if (typeof match === 'string' && match === urlString)
return true;
const url = new URL(urlString);
if (typeof match === 'string')
return url.pathname === match;
assert(typeof match === 'function', 'url parameter should be string, RegExp or function');
return match(url);
}
export function pngToJpeg(buffer: Buffer, quality?: number): Buffer {
assert(isNode, 'Converting from png to jpeg is only supported in Node.js');
return jpeg.encode(png.PNG.sync.read(buffer), quality).data;

View File

@ -1011,7 +1011,7 @@ module.exports.describe = function({testRunner, expect, playwright, MAC, WIN, FF
});
});
describe('Page._waitForLoadState', () => {
describe('Page.waitForLoadState', () => {
it('should pick up ongoing navigation', async({page, server}) => {
let response = null;
server.setRoute('/one-style.css', (req, res) => response = res);
@ -1019,7 +1019,7 @@ module.exports.describe = function({testRunner, expect, playwright, MAC, WIN, FF
server.waitForRequest('/one-style.css'),
page.goto(server.PREFIX + '/one-style.html', {waitUntil: 'domcontentloaded'}),
]);
const waitPromise = page.mainFrame()._waitForLoadState();
const waitPromise = page.waitForLoadState();
response.statusCode = 404;
response.end('Not found');
await waitPromise;
@ -1027,18 +1027,18 @@ module.exports.describe = function({testRunner, expect, playwright, MAC, WIN, FF
it('should respect timeout', async({page, server}) => {
server.setRoute('/one-style.css', (req, res) => response = res);
await page.goto(server.PREFIX + '/one-style.html', {waitUntil: 'domcontentloaded'});
const error = await page.mainFrame()._waitForLoadState({ timeout: 1 }).catch(e => e);
const error = await page.waitForLoadState({ timeout: 1 }).catch(e => e);
expect(error.message).toBe('Navigation timeout of 1 ms exceeded');
});
it('should resolve immediately if loaded', async({page, server}) => {
await page.goto(server.PREFIX + '/one-style.html');
await page.mainFrame()._waitForLoadState();
await page.waitForLoadState();
});
it('should resolve immediately if load state matches', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
server.setRoute('/one-style.css', (req, res) => response = res);
await page.goto(server.PREFIX + '/one-style.html', {waitUntil: 'domcontentloaded'});
await page.mainFrame()._waitForLoadState({ waitUntil: 'domcontentloaded' });
await page.waitForLoadState({ waitUntil: 'domcontentloaded' });
});
it('should work with pages that have loaded before being connected to', async({page, context, server}) => {
await page.goto(server.EMPTY_PAGE);
@ -1047,7 +1047,7 @@ module.exports.describe = function({testRunner, expect, playwright, MAC, WIN, FF
page.evaluate(() => window._popup = window.open(document.location.href)),
]);
expect(popup.url()).toBe(server.EMPTY_PAGE);
await popup.mainFrame()._waitForLoadState();
await popup.waitForLoadState();
expect(popup.url()).toBe(server.EMPTY_PAGE);
});
});
@ -1171,7 +1171,7 @@ module.exports.describe = function({testRunner, expect, playwright, MAC, WIN, FF
await frame.goto(server.PREFIX + '/one-style.html', {waitUntil: 'domcontentloaded'});
const request = await requestPromise;
let resolved = false;
const loadPromise = frame._waitForLoadState().then(() => resolved = true);
const loadPromise = frame.waitForLoadState().then(() => resolved = true);
// give the promise a chance to resolve, even though it shouldn't
await page.evaluate('1');
expect(resolved).toBe(false);