From a064a6341b9ec19aef9253db1d79d228bd9018c1 Mon Sep 17 00:00:00 2001 From: Andrey Lushnikov Date: Mon, 28 Jan 2019 17:16:12 -0800 Subject: [PATCH] feat(page): introduce page.setDefaultTimeout (#3854) Method `page.setDefaultTimeout` overrides default 30 seconds timeout for all `page.waitFor*` methods, including navigation and waiting for selectors. Fix #3319. --- docs/api.md | 60 ++++++++++++++++++++++++++++------------- lib/DOMWorld.js | 10 ++++--- lib/FrameManager.js | 20 +++++--------- lib/Page.js | 17 +++++++++--- lib/TimeoutSettings.js | 57 +++++++++++++++++++++++++++++++++++++++ lib/externs.d.ts | 2 ++ test/navigation.spec.js | 19 +++++++++++++ test/page.spec.js | 40 +++++++++++++++++++++++++++ test/waittask.spec.js | 8 ++++++ 9 files changed, 194 insertions(+), 39 deletions(-) create mode 100644 lib/TimeoutSettings.js diff --git a/docs/api.md b/docs/api.md index 5b846b401acc5..423219a43cbf2 100644 --- a/docs/api.md +++ b/docs/api.md @@ -131,6 +131,7 @@ * [page.setContent(html[, options])](#pagesetcontenthtml-options) * [page.setCookie(...cookies)](#pagesetcookiecookies) * [page.setDefaultNavigationTimeout(timeout)](#pagesetdefaultnavigationtimeouttimeout) + * [page.setDefaultTimeout(timeout)](#pagesetdefaulttimeouttimeout) * [page.setExtraHTTPHeaders(headers)](#pagesetextrahttpheadersheaders) * [page.setGeolocation(options)](#pagesetgeolocationoptions) * [page.setJavaScriptEnabled(enabled)](#pagesetjavascriptenabledenabled) @@ -1396,7 +1397,7 @@ Shortcut for [page.mainFrame().focus(selector)](#framefocusselector). #### page.goBack([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 [page.setDefaultNavigationTimeout(timeout)](#pagesetdefaultnavigationtimeouttimeout) method. + - `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 [page.setDefaultNavigationTimeout(timeout)](#pagesetdefaultnavigationtimeouttimeout) or [page.setDefaultTimeout(timeout)](#pagesetdefaulttimeouttimeout) methods. - `waitUntil` <[string]|[Array]<[string]>> When to consider navigation succeeded, defaults to `load`. Given an array of event strings, navigation is considered to be successful after all events have been fired. 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. @@ -1409,7 +1410,7 @@ Navigate to the previous page in history. #### page.goForward([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 [page.setDefaultNavigationTimeout(timeout)](#pagesetdefaultnavigationtimeouttimeout) method. + - `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 [page.setDefaultNavigationTimeout(timeout)](#pagesetdefaultnavigationtimeouttimeout) or [page.setDefaultTimeout(timeout)](#pagesetdefaulttimeouttimeout) methods. - `waitUntil` <[string]|[Array]<[string]>> When to consider navigation succeeded, defaults to `load`. Given an array of event strings, navigation is considered to be successful after all events have been fired. 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. @@ -1423,7 +1424,7 @@ Navigate to the next page in history. #### page.goto(url[, options]) - `url` <[string]> URL to navigate page to. The url should include scheme, e.g. `https://`. - `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 [page.setDefaultNavigationTimeout(timeout)](#pagesetdefaultnavigationtimeouttimeout) method. + - `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 [page.setDefaultNavigationTimeout(timeout)](#pagesetdefaultnavigationtimeouttimeout) or [page.setDefaultTimeout(timeout)](#pagesetdefaulttimeouttimeout) methods. - `waitUntil` <[string]|[Array]<[string]>> When to consider navigation succeeded, defaults to `load`. Given an array of event strings, navigation is considered to be successful after all events have been fired. 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. @@ -1581,7 +1582,7 @@ Shortcut for [page.mainFrame().executionContext().queryObjects(prototypeHandle)] #### page.reload([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 [page.setDefaultNavigationTimeout(timeout)](#pagesetdefaultnavigationtimeouttimeout) method. + - `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 [page.setDefaultNavigationTimeout(timeout)](#pagesetdefaultnavigationtimeouttimeout) or [page.setDefaultTimeout(timeout)](#pagesetdefaulttimeouttimeout) methods. - `waitUntil` <[string]|[Array]<[string]>> When to consider navigation succeeded, defaults to `load`. Given an array of event strings, navigation is considered to be successful after all events have been fired. 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. @@ -1639,7 +1640,7 @@ Toggles ignoring cache for each request based on the enabled state. By default, #### page.setContent(html[, options]) - `html` <[string]> HTML markup to assign to the page. - `options` <[Object]> Parameters which might have the following properties: - - `timeout` <[number]> Maximum time in milliseconds for resources to load, defaults to 30 seconds, pass `0` to disable timeout. + - `timeout` <[number]> Maximum time in milliseconds for resources to load, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by using the [page.setDefaultNavigationTimeout(timeout)](#pagesetdefaultnavigationtimeouttimeout) or [page.setDefaultTimeout(timeout)](#pagesetdefaulttimeouttimeout) methods. - `waitUntil` <[string]|[Array]<[string]>> When to consider setting markup succeeded, defaults to `load`. Given an array of event strings, setting content is considered to be successful after all events have been fired. Events can be either: - `load` - consider setting content to be finished when the `load` event is fired. - `domcontentloaded` - consider setting content to be finished when the `DOMContentLoaded` event is fired. @@ -1667,12 +1668,35 @@ await page.setCookie(cookieObject1, cookieObject2); #### page.setDefaultNavigationTimeout(timeout) - `timeout` <[number]> Maximum navigation time in milliseconds -This setting will change the default maximum navigation time of 30 seconds for the following methods: +This setting will change the default maximum navigation time for the following methods and related shortcuts: +- [page.goBack([options])](#pagegobackoptions) +- [page.goForward([options])](#pagegoforwardoptions) - [page.goto(url[, options])](#pagegotourl-options) +- [page.reload([options])](#pagereloadoptions) +- [page.setContent(html[, options])](#pagesetcontenthtml-options) +- [page.waitForNavigation([options])](#pagewaitfornavigationoptions) + +> **NOTE** [`page.setDefaultNavigationTimeout`](#pagesetdefaultnavigationtimeouttimeout) takes priority over [`page.setDefaultTimeout`](#pagesetdefaulttimeouttimeout) + + +#### page.setDefaultTimeout(timeout) +- `timeout` <[number]> Maximum time in milliseconds + +This setting will change the default maximum time for the following methods and related shortcuts: - [page.goBack([options])](#pagegobackoptions) - [page.goForward([options])](#pagegoforwardoptions) +- [page.goto(url[, options])](#pagegotourl-options) - [page.reload([options])](#pagereloadoptions) +- [page.setContent(html[, options])](#pagesetcontenthtml-options) +- [page.waitFor(selectorOrFunctionOrTimeout[, options[, ...args]])](#pagewaitforselectororfunctionortimeout-options-args) +- [page.waitForFunction(pageFunction[, options[, ...args]])](#pagewaitforfunctionpagefunction-options-args) - [page.waitForNavigation([options])](#pagewaitfornavigationoptions) +- [page.waitForRequest(urlOrPredicate[, options])](#pagewaitforrequesturlorpredicate-options) +- [page.waitForResponse(urlOrPredicate[, options])](#pagewaitforresponseurlorpredicate-options) +- [page.waitForSelector(selector[, options])](#pagewaitforselectorselector-options) +- [page.waitForXPath(xpath[, options])](#pagewaitforxpathxpath-options) + +> **NOTE** [`page.setDefaultNavigationTimeout`](#pagesetdefaultnavigationtimeouttimeout) takes priority over [`page.setDefaultTimeout`](#pagesetdefaulttimeouttimeout) #### page.setExtraHTTPHeaders(headers) - `headers` <[Object]> An object containing additional HTTP headers to be sent with every request. All header values must be strings. @@ -1846,7 +1870,7 @@ Shortcut for [page.mainFrame().waitFor(selectorOrFunctionOrTimeout[, options[, . - `polling` <[string]|[number]> An interval at which the `pageFunction` is executed, defaults to `raf`. If `polling` is a number, then it is treated as an interval in milliseconds at which the function would be executed. If `polling` is a string, then it can be one of the following values: - `raf` - to constantly execute `pageFunction` in `requestAnimationFrame` callback. This is the tightest polling mode which is suitable to observe styling changes. - `mutation` - to execute `pageFunction` on every DOM mutation. - - `timeout` <[number]> maximum time to wait for in milliseconds. Defaults to `30000` (30 seconds). Pass `0` to disable timeout. + - `timeout` <[number]> maximum time to wait for in milliseconds. Defaults to `30000` (30 seconds). Pass `0` to disable timeout. The default value can be changed by using the [page.setDefaultTimeout(timeout)](#pagesetdefaulttimeouttimeout) method. - `...args` <...[Serializable]|[JSHandle]> Arguments to pass to `pageFunction` - returns: <[Promise]<[JSHandle]>> Promise which resolves when the `pageFunction` returns a truthy value. It resolves to a JSHandle of the truthy value. @@ -1874,7 +1898,7 @@ Shortcut for [page.mainFrame().waitForFunction(pageFunction[, options[, ...args] #### 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 [page.setDefaultNavigationTimeout(timeout)](#pagesetdefaultnavigationtimeouttimeout) method. + - `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 [page.setDefaultNavigationTimeout(timeout)](#pagesetdefaultnavigationtimeouttimeout) or [page.setDefaultTimeout(timeout)](#pagesetdefaulttimeouttimeout) methods. - `waitUntil` <[string]|[Array]<[string]>> When to consider navigation succeeded, defaults to `load`. Given an array of event strings, navigation is considered to be successful after all events have been fired. 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. @@ -1899,7 +1923,7 @@ Shortcut for [page.mainFrame().waitForNavigation(options)](#framewaitfornavigati #### page.waitForRequest(urlOrPredicate[, options]) - `urlOrPredicate` <[string]|[Function]> A URL or predicate to wait for. - `options` <[Object]> Optional waiting parameters - - `timeout` <[number]> Maximum wait time in milliseconds, defaults to 30 seconds, pass `0` to disable the timeout. + - `timeout` <[number]> Maximum wait time in milliseconds, defaults to 30 seconds, pass `0` to disable the timeout. The default value can be changed by using the [page.setDefaultTimeout(timeout)](#pagesetdefaulttimeouttimeout) method. - returns: <[Promise]<[Request]>> Promise which resolves to the matched request. ```js @@ -1911,7 +1935,7 @@ return firstRequest.url(); #### page.waitForResponse(urlOrPredicate[, options]) - `urlOrPredicate` <[string]|[Function]> A URL or predicate to wait for. - `options` <[Object]> Optional waiting parameters - - `timeout` <[number]> Maximum wait time in milliseconds, defaults to 30 seconds, pass `0` to disable the timeout. + - `timeout` <[number]> Maximum wait time in milliseconds, defaults to 30 seconds, pass `0` to disable the timeout. The default value can be changed by using the [page.setDefaultTimeout(timeout)](#pagesetdefaulttimeouttimeout) method. - returns: <[Promise]<[Response]>> Promise which resolves to the matched response. ```js @@ -1925,7 +1949,7 @@ return finalResponse.ok(); - `options` <[Object]> Optional waiting parameters - `visible` <[boolean]> wait for element to be present in DOM and to be visible, i.e. to not have `display: none` or `visibility: hidden` CSS properties. Defaults to `false`. - `hidden` <[boolean]> wait for element to not be found in the DOM or to be hidden, i.e. have `display: none` or `visibility: hidden` CSS properties. Defaults to `false`. - - `timeout` <[number]> maximum time to wait for in milliseconds. Defaults to `30000` (30 seconds). Pass `0` to disable timeout. + - `timeout` <[number]> maximum time to wait for in milliseconds. Defaults to `30000` (30 seconds). Pass `0` to disable timeout. The default value can be changed by using the [page.setDefaultTimeout(timeout)](#pagesetdefaulttimeouttimeout) method. - returns: <[Promise]> Promise which resolves when element specified by selector string is added to DOM. Resolves to `null` if waiting for `hidden: true` and selector is not found in DOM. Wait for the `selector` to appear in page. If at the moment of calling @@ -1954,7 +1978,7 @@ Shortcut for [page.mainFrame().waitForSelector(selector[, options])](#framewaitf - `options` <[Object]> Optional waiting parameters - `visible` <[boolean]> wait for element to be present in DOM and to be visible, i.e. to not have `display: none` or `visibility: hidden` CSS properties. Defaults to `false`. - `hidden` <[boolean]> wait for element to not be found in the DOM or to be hidden, i.e. have `display: none` or `visibility: hidden` CSS properties. Defaults to `false`. - - `timeout` <[number]> maximum time to wait for in milliseconds. Defaults to `30000` (30 seconds). Pass `0` to disable timeout. + - `timeout` <[number]> maximum time to wait for in milliseconds. Defaults to `30000` (30 seconds). Pass `0` to disable timeout. The default value can be changed by using the [page.setDefaultTimeout(timeout)](#pagesetdefaulttimeouttimeout) method. - returns: <[Promise]> Promise which resolves when element specified by xpath string is added to DOM. Resolves to `null` if waiting for `hidden: true` and xpath is not found in DOM. Wait for the `xpath` to appear in page. If at the moment of calling @@ -2538,7 +2562,7 @@ If there's no element matching `selector`, the method throws an error. #### frame.goto(url[, options]) - `url` <[string]> URL to navigate frame to. The url should include scheme, e.g. `https://`. - `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 [page.setDefaultNavigationTimeout(timeout)](#pagesetdefaultnavigationtimeouttimeout) method. + - `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 [page.setDefaultNavigationTimeout(timeout)](#pagesetdefaultnavigationtimeouttimeout) or [page.setDefaultTimeout(timeout)](#pagesetdefaulttimeouttimeout) methods. - `waitUntil` <[string]|[Array]<[string]>> When to consider navigation succeeded, defaults to `load`. Given an array of event strings, navigation is considered to be successful after all events have been fired. 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. @@ -2598,7 +2622,7 @@ frame.select('select#colors', 'red', 'green', 'blue'); // multiple selections #### frame.setContent(html[, options]) - `html` <[string]> HTML markup to assign to the page. - `options` <[Object]> Parameters which might have the following properties: - - `timeout` <[number]> Maximum time in milliseconds for resources to load, defaults to 30 seconds, pass `0` to disable timeout. + - `timeout` <[number]> Maximum time in milliseconds for resources to load, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by using the [page.setDefaultNavigationTimeout(timeout)](#pagesetdefaultnavigationtimeouttimeout) or [page.setDefaultTimeout(timeout)](#pagesetdefaulttimeouttimeout) methods. - `waitUntil` <[string]|[Array]<[string]>> When to consider setting markup succeeded, defaults to `load`. Given an array of event strings, setting content is considered to be successful after all events have been fired. Events can be either: - `load` - consider setting content to be finished when the `load` event is fired. - `domcontentloaded` - consider setting content to be finished when the `DOMContentLoaded` event is fired. @@ -2672,7 +2696,7 @@ await page.waitFor(selector => !!document.querySelector(selector), {}, selector) - `polling` <[string]|[number]> An interval at which the `pageFunction` is executed, defaults to `raf`. If `polling` is a number, then it is treated as an interval in milliseconds at which the function would be executed. If `polling` is a string, then it can be one of the following values: - `raf` - to constantly execute `pageFunction` in `requestAnimationFrame` callback. This is the tightest polling mode which is suitable to observe styling changes. - `mutation` - to execute `pageFunction` on every DOM mutation. - - `timeout` <[number]> maximum time to wait for in milliseconds. Defaults to `30000` (30 seconds). Pass `0` to disable timeout. + - `timeout` <[number]> maximum time to wait for in milliseconds. Defaults to `30000` (30 seconds). Pass `0` to disable timeout. The default value can be changed by using the [page.setDefaultTimeout(timeout)](#pagesetdefaulttimeouttimeout) method. - `...args` <...[Serializable]|[JSHandle]> Arguments to pass to `pageFunction` - returns: <[Promise]<[JSHandle]>> Promise which resolves when the `pageFunction` returns a truthy value. It resolves to a JSHandle of the truthy value. @@ -2698,7 +2722,7 @@ await page.waitForFunction(selector => !!document.querySelector(selector), {}, s #### 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 [page.setDefaultNavigationTimeout(timeout)](#pagesetdefaultnavigationtimeouttimeout) method. + - `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 [page.setDefaultNavigationTimeout(timeout)](#pagesetdefaultnavigationtimeouttimeout) or [page.setDefaultTimeout(timeout)](#pagesetdefaulttimeouttimeout) methods. - `waitUntil` <[string]|[Array]<[string]>> When to consider navigation succeeded, defaults to `load`. Given an array of event strings, navigation is considered to be successful after all events have been fired. 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. @@ -2724,7 +2748,7 @@ const [response] = await Promise.all([ - `options` <[Object]> Optional waiting parameters - `visible` <[boolean]> wait for element to be present in DOM and to be visible, i.e. to not have `display: none` or `visibility: hidden` CSS properties. Defaults to `false`. - `hidden` <[boolean]> wait for element to not be found in the DOM or to be hidden, i.e. have `display: none` or `visibility: hidden` CSS properties. Defaults to `false`. - - `timeout` <[number]> maximum time to wait for in milliseconds. Defaults to `30000` (30 seconds). Pass `0` to disable timeout. + - `timeout` <[number]> maximum time to wait for in milliseconds. Defaults to `30000` (30 seconds). Pass `0` to disable timeout. The default value can be changed by using the [page.setDefaultTimeout(timeout)](#pagesetdefaulttimeouttimeout) method. - returns: <[Promise]> Promise which resolves when element specified by selector string is added to DOM. Resolves to `null` if waiting for `hidden: true` and selector is not found in DOM. Wait for the `selector` to appear in page. If at the moment of calling @@ -2752,7 +2776,7 @@ puppeteer.launch().then(async browser => { - `options` <[Object]> Optional waiting parameters - `visible` <[boolean]> wait for element to be present in DOM and to be visible, i.e. to not have `display: none` or `visibility: hidden` CSS properties. Defaults to `false`. - `hidden` <[boolean]> wait for element to not be found in the DOM or to be hidden, i.e. have `display: none` or `visibility: hidden` CSS properties. Defaults to `false`. - - `timeout` <[number]> maximum time to wait for in milliseconds. Defaults to `30000` (30 seconds). Pass `0` to disable timeout. + - `timeout` <[number]> maximum time to wait for in milliseconds. Defaults to `30000` (30 seconds). Pass `0` to disable timeout. The default value can be changed by using the [page.setDefaultTimeout(timeout)](#pagesetdefaulttimeouttimeout) method. - returns: <[Promise]> Promise which resolves when element specified by xpath string is added to DOM. Resolves to `null` if waiting for `hidden: true` and xpath is not found in DOM. Wait for the `xpath` to appear in page. If at the moment of calling diff --git a/lib/DOMWorld.js b/lib/DOMWorld.js index f91410d34f437..e00d66621d53f 100644 --- a/lib/DOMWorld.js +++ b/lib/DOMWorld.js @@ -27,10 +27,12 @@ class DOMWorld { /** * @param {!Puppeteer.FrameManager} frameManager * @param {!Puppeteer.Frame} frame + * @param {!Puppeteer.TimeoutSettings} timeoutSettings */ - constructor(frameManager, frame) { + constructor(frameManager, frame, timeoutSettings) { this._frameManager = frameManager; this._frame = frame; + this._timeoutSettings = timeoutSettings; /** @type {?Promise} */ this._documentPromise = null; @@ -190,7 +192,7 @@ class DOMWorld { async setContent(html, options = {}) { const { waitUntil = ['load'], - timeout = 30000, + timeout = this._timeoutSettings.navigationTimeout(), } = options; // We rely upon the fact that document.open() will reset frame lifecycle with "init" // lifecycle event. @see https://crrev.com/608658 @@ -452,7 +454,7 @@ class DOMWorld { waitForFunction(pageFunction, options = {}, ...args) { const { polling = 'raf', - timeout = 30000 + timeout = this._timeoutSettings.timeout(), } = options; return new WaitTask(this, pageFunction, 'function', polling, timeout, ...args).promise; } @@ -474,7 +476,7 @@ class DOMWorld { const { visible: waitForVisible = false, hidden: waitForHidden = false, - timeout = 30000, + timeout = this._timeoutSettings.timeout(), } = options; const polling = waitForVisible || waitForHidden ? 'raf' : 'mutation'; const title = `${isXPath ? 'XPath' : 'selector'} "${selectorOrXPath}"${waitForHidden ? ' to be hidden' : ''}`; diff --git a/lib/FrameManager.js b/lib/FrameManager.js index 0b68ca4ec9e93..5c854102b4779 100644 --- a/lib/FrameManager.js +++ b/lib/FrameManager.js @@ -29,13 +29,14 @@ class FrameManager extends EventEmitter { * @param {!Protocol.Page.FrameTree} frameTree * @param {!Puppeteer.Page} page * @param {!Puppeteer.NetworkManager} networkManager + * @param {!Puppeteer.TimeoutSettings} timeoutSettings */ - constructor(client, frameTree, page, networkManager) { + constructor(client, frameTree, page, networkManager, timeoutSettings) { super(); this._client = client; this._page = page; this._networkManager = networkManager; - this._defaultNavigationTimeout = 30000; + this._timeoutSettings = timeoutSettings; /** @type {!Map} */ this._frames = new Map(); /** @type {!Map} */ @@ -55,13 +56,6 @@ class FrameManager extends EventEmitter { this._handleFrameTree(frameTree); } - /** - * @param {number} timeout - */ - setDefaultNavigationTimeout(timeout) { - this._defaultNavigationTimeout = timeout; - } - /** * @param {!Puppeteer.Frame} frame * @param {string} url @@ -73,7 +67,7 @@ class FrameManager extends EventEmitter { const { referer = this._networkManager.extraHTTPHeaders()['referer'], waitUntil = ['load'], - timeout = this._defaultNavigationTimeout, + timeout = this._timeoutSettings.navigationTimeout(), } = options; const watcher = new LifecycleWatcher(this, frame, waitUntil, timeout); @@ -120,7 +114,7 @@ class FrameManager extends EventEmitter { assertNoLegacyNavigationOptions(options); const { waitUntil = ['load'], - timeout = this._defaultNavigationTimeout, + timeout = this._timeoutSettings.navigationTimeout(), } = options; const watcher = new LifecycleWatcher(this, frame, waitUntil, timeout); const error = await Promise.race([ @@ -373,8 +367,8 @@ class Frame { this._loaderId = ''; /** @type {!Set} */ this._lifecycleEvents = new Set(); - this._mainWorld = new DOMWorld(frameManager, this); - this._secondaryWorld = new DOMWorld(frameManager, this); + this._mainWorld = new DOMWorld(frameManager, this, frameManager._timeoutSettings); + this._secondaryWorld = new DOMWorld(frameManager, this, frameManager._timeoutSettings); /** @type {!Set} */ this._childFrames = new Set(); diff --git a/lib/Page.js b/lib/Page.js index dc4c2f44e0859..c2637052cfad2 100644 --- a/lib/Page.js +++ b/lib/Page.js @@ -30,6 +30,7 @@ const {Coverage} = require('./Coverage'); const {Worker} = require('./Worker'); const {createJSHandle} = require('./JSHandle'); const {Accessibility} = require('./Accessibility'); +const {TimeoutSettings} = require('./TimeoutSettings'); const writeFileAsync = helper.promisify(fs.writeFile); class Page extends EventEmitter { @@ -78,11 +79,12 @@ class Page extends EventEmitter { this._target = target; this._keyboard = new Keyboard(client); this._mouse = new Mouse(client, this._keyboard); + this._timeoutSettings = new TimeoutSettings(); this._touchscreen = new Touchscreen(client, this._keyboard); this._accessibility = new Accessibility(client); this._networkManager = new NetworkManager(client); /** @type {!FrameManager} */ - this._frameManager = new FrameManager(client, frameTree, this, this._networkManager); + this._frameManager = new FrameManager(client, frameTree, this, this._networkManager, this._timeoutSettings); this._networkManager.setFrameManager(this._frameManager); this._emulationManager = new EmulationManager(client); this._tracing = new Tracing(client); @@ -268,7 +270,14 @@ class Page extends EventEmitter { * @param {number} timeout */ setDefaultNavigationTimeout(timeout) { - this._frameManager.setDefaultNavigationTimeout(timeout); + this._timeoutSettings.setDefaultNavigationTimeout(timeout); + } + + /** + * @param {number} timeout + */ + setDefaultTimeout(timeout) { + this._timeoutSettings.setDefaultTimeout(timeout); } /** @@ -664,7 +673,7 @@ class Page extends EventEmitter { */ async waitForRequest(urlOrPredicate, options = {}) { const { - timeout = 30000 + timeout = this._timeoutSettings.timeout(), } = options; return helper.waitForEvent(this._networkManager, Events.NetworkManager.Request, request => { if (helper.isString(urlOrPredicate)) @@ -682,7 +691,7 @@ class Page extends EventEmitter { */ async waitForResponse(urlOrPredicate, options = {}) { const { - timeout = 30000 + timeout = this._timeoutSettings.timeout(), } = options; return helper.waitForEvent(this._networkManager, Events.NetworkManager.Response, response => { if (helper.isString(urlOrPredicate)) diff --git a/lib/TimeoutSettings.js b/lib/TimeoutSettings.js new file mode 100644 index 0000000000000..c6b08d39671c9 --- /dev/null +++ b/lib/TimeoutSettings.js @@ -0,0 +1,57 @@ +/** + * Copyright 2019 Google Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const DEFAULT_TIMEOUT = 30000; + +class TimeoutSettings { + constructor() { + this._defaultTimeout = null; + this._defaultNavigationTimeout = null; + } + + /** + * @param {number} timeout + */ + setDefaultTimeout(timeout) { + this._defaultTimeout = timeout; + } + + /** + * @param {number} timeout + */ + setDefaultNavigationTimeout(timeout) { + this._defaultNavigationTimeout = timeout; + } + + /** + * @return {number} + */ + navigationTimeout() { + if (this._defaultNavigationTimeout !== null) + return this._defaultNavigationTimeout; + if (this._defaultTimeout !== null) + return this._defaultTimeout; + return DEFAULT_TIMEOUT; + } + + timeout() { + if (this._defaultTimeout !== null) + return this._defaultTimeout; + return DEFAULT_TIMEOUT; + } +} + +module.exports = {TimeoutSettings}; diff --git a/lib/externs.d.ts b/lib/externs.d.ts index 9ddb2f2eef5b8..5dd0a2585bb81 100644 --- a/lib/externs.d.ts +++ b/lib/externs.d.ts @@ -7,6 +7,7 @@ import {Mouse as RealMouse, Keyboard as RealKeyboard, Touchscreen as RealTouchsc import {Frame as RealFrame, FrameManager as RealFrameManager} from './FrameManager.js'; import {JSHandle as RealJSHandle, ElementHandle as RealElementHandle} from './JSHandle.js'; import {DOMWorld as RealDOMWorld} from './DOMWorld.js'; +import {TimeoutSettings as RealTimeoutSettings} from './TimeoutSettings.js'; import {ExecutionContext as RealExecutionContext} from './ExecutionContext.js'; import { NetworkManager as RealNetworkManager, Request as RealRequest, Response as RealResponse } from './NetworkManager.js'; import * as child_process from 'child_process'; @@ -30,6 +31,7 @@ declare global { export class ElementHandle extends RealElementHandle {} export class JSHandle extends RealJSHandle {} export class DOMWorld extends RealDOMWorld {} + export class TimeoutSettings extends RealTimeoutSettings {} export class ExecutionContext extends RealExecutionContext {} export class Page extends RealPage { } export class Response extends RealResponse { } diff --git a/test/navigation.spec.js b/test/navigation.spec.js index 8311eff3183c9..77643d6cc6eb2 100644 --- a/test/navigation.spec.js +++ b/test/navigation.spec.js @@ -118,6 +118,25 @@ module.exports.addTests = function({testRunner, expect}) { expect(error.message).toContain('Navigation Timeout Exceeded: 1ms'); expect(error).toBeInstanceOf(TimeoutError); }); + it('should fail when exceeding default maximum timeout', async({page, server}) => { + // Hang for request to the empty.html + server.setRoute('/empty.html', (req, res) => { }); + let error = null; + page.setDefaultTimeout(1); + await page.goto(server.PREFIX + '/empty.html').catch(e => error = e); + expect(error.message).toContain('Navigation Timeout Exceeded: 1ms'); + expect(error).toBeInstanceOf(TimeoutError); + }); + it('should prioritize default navigation timeout over default timeout', async({page, server}) => { + // Hang for request to the empty.html + server.setRoute('/empty.html', (req, res) => { }); + let error = null; + page.setDefaultTimeout(0); + page.setDefaultNavigationTimeout(1); + await page.goto(server.PREFIX + '/empty.html').catch(e => error = e); + expect(error.message).toContain('Navigation Timeout Exceeded: 1ms'); + expect(error).toBeInstanceOf(TimeoutError); + }); it('should disable timeout when its set to 0', async({page, server}) => { let error = null; let loaded = false; diff --git a/test/page.spec.js b/test/page.spec.js index 335d64a4d22ee..6dbf9c2549c8a 100644 --- a/test/page.spec.js +++ b/test/page.spec.js @@ -17,6 +17,7 @@ const fs = require('fs'); const path = require('path'); const utils = require('./utils'); const {waitEvent} = utils; +const {TimeoutError} = utils.requireRoot('Errors'); const DeviceDescriptors = utils.requireRoot('DeviceDescriptors'); const iPhone = DeviceDescriptors['iPhone 6']; @@ -421,6 +422,17 @@ module.exports.addTests = function({testRunner, expect, headless}) { ]); expect(request.url()).toBe(server.PREFIX + '/digits/2.png'); }); + it('should respect timeout', async({page, server}) => { + let error = null; + await page.waitForRequest(() => false, {timeout: 1}).catch(e => error = e); + expect(error).toBeInstanceOf(TimeoutError); + }); + it('should respect default timeout', async({page, server}) => { + let error = null; + page.setDefaultTimeout(1); + await page.waitForRequest(() => false).catch(e => error = e); + expect(error).toBeInstanceOf(TimeoutError); + }); it('should work with no timeout', async({page, server}) => { await page.goto(server.EMPTY_PAGE); const [request] = await Promise.all([ @@ -448,6 +460,17 @@ module.exports.addTests = function({testRunner, expect, headless}) { ]); expect(response.url()).toBe(server.PREFIX + '/digits/2.png'); }); + it('should respect timeout', async({page, server}) => { + let error = null; + await page.waitForResponse(() => false, {timeout: 1}).catch(e => error = e); + expect(error).toBeInstanceOf(TimeoutError); + }); + it('should respect default timeout', async({page, server}) => { + let error = null; + page.setDefaultTimeout(1); + await page.waitForResponse(() => false).catch(e => error = e); + expect(error).toBeInstanceOf(TimeoutError); + }); it('should work with predicate', async({page, server}) => { await page.goto(server.EMPTY_PAGE); const [response] = await Promise.all([ @@ -617,6 +640,23 @@ module.exports.addTests = function({testRunner, expect, headless}) { const result = await page.content(); expect(result).toBe(`${doctype}${expectedOutput}`); }); + it('should respect timeout', async({page, server}) => { + const imgPath = '/img.png'; + // stall for image + server.setRoute(imgPath, (req, res) => {}); + let error = null; + await page.setContent(``, {timeout: 1}).catch(e => error = e); + expect(error).toBeInstanceOf(TimeoutError); + }); + it('should respect default navigation timeout', async({page, server}) => { + page.setDefaultNavigationTimeout(1); + const imgPath = '/img.png'; + // stall for image + server.setRoute(imgPath, (req, res) => {}); + let error = null; + await page.setContent(``).catch(e => error = e); + expect(error).toBeInstanceOf(TimeoutError); + }); it('should await resources to load', async({page, server}) => { const imgPath = '/img.png'; let imgResponse = null; diff --git a/test/waittask.spec.js b/test/waittask.spec.js index ad5dca91c25ad..b9b840a7bc964 100644 --- a/test/waittask.spec.js +++ b/test/waittask.spec.js @@ -170,6 +170,14 @@ module.exports.addTests = function({testRunner, expect, product}) { expect(error.message).toContain('waiting for function failed: timeout'); expect(error).toBeInstanceOf(TimeoutError); }); + it('should respect default timeout', async({page}) => { + page.setDefaultTimeout(1); + let error = null; + await page.waitForFunction('false').catch(e => error = e); + expect(error).toBeTruthy(); + expect(error.message).toContain('waiting for function failed: timeout'); + expect(error).toBeInstanceOf(TimeoutError); + }); it('should disable timeout when its set to 0', async({page}) => { const watchdog = page.waitForFunction(() => { window.__counter = (window.__counter || 0) + 1;