From 1cf9057bf065568ba831cb0e912b870f11131705 Mon Sep 17 00:00:00 2001 From: Pavel Date: Fri, 13 Mar 2020 13:49:16 -0700 Subject: [PATCH] api(waitForLoadState): restore it --- docs/api.md | 40 ++++++++++++++++++++++++++++++++++++++++ src/frames.ts | 10 +++++----- src/helper.ts | 17 +++++++++++++++++ src/page.ts | 14 +++++++++----- src/platform.ts | 18 ------------------ test/navigation.spec.js | 14 +++++++------- 6 files changed, 78 insertions(+), 35 deletions(-) diff --git a/docs/api.md b/docs/api.md index 3f790857a23b9..3bae3530a01e2 100644 --- a/docs/api.md +++ b/docs/api.md @@ -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) @@ -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. diff --git a/src/frames.ts b/src/frames.ts index 7740a276951f7..aeeb3418565cc 100644 --- a/src/frames.ts +++ b/src/frames.ts @@ -433,8 +433,8 @@ export class Frame { return request ? request._finalRequest.response() : null; } - async _waitForLoadState(options: types.NavigateOptions = {}): Promise { - const {timeout = this._page._timeoutSettings.navigationTimeout()} = options; + async waitForLoadState(options: types.NavigateOptions = {}): Promise { + 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(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) => { diff --git a/src/helper.ts b/src/helper.ts index c4c7b25990a18..8b3a1aac17a1b 100644 --- a/src/helper.ts +++ b/src/helper.ts @@ -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 { diff --git a/src/page.ts b/src/page.ts index 160267baa4da1..e8bc66952efa0 100644 --- a/src/page.ts +++ b/src/page.ts @@ -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 { + return this.mainFrame().waitForLoadState(options); + } + async waitForNavigation(options?: types.WaitForNavigationOptions): Promise { 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; } diff --git a/src/platform.ts b/src/platform.ts index f88946367b7f9..ad02c3638e9ec 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -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; diff --git a/test/navigation.spec.js b/test/navigation.spec.js index 37ab40226e7cd..4bbc410e201b4 100644 --- a/test/navigation.spec.js +++ b/test/navigation.spec.js @@ -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);