Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

api(waitForLoadState): restore it #1390

Merged
merged 1 commit into from
Mar 16, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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 -->
Expand Down Expand Up @@ -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.
Expand Down
10 changes: 5 additions & 5 deletions src/frames.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down Expand Up @@ -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});
};
Expand All @@ -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);
Expand Down Expand Up @@ -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) => {
Expand Down
17 changes: 17 additions & 0 deletions src/helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
14 changes: 9 additions & 5 deletions src/page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down Expand Up @@ -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);
}
Expand All @@ -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);
}
Expand All @@ -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);
}
Expand Down Expand Up @@ -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;
}
Expand Down
18 changes: 0 additions & 18 deletions src/platform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
14 changes: 7 additions & 7 deletions test/navigation.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -1011,34 +1011,34 @@ 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);
await Promise.all([
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;
});
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);
Expand All @@ -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);
});
});
Expand Down Expand Up @@ -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);
Expand Down