diff --git a/docs/README.md b/docs/README.md
index 9114c00b68746..cc48d08e94038 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -46,6 +46,7 @@
- [Travis CI](./ci.md#travis-ci)
- [CircleCI](./ci.md#circleci)
- [AppVeyor](./ci.md#appveyor)
+ - Troubleshooting
1. Test runners
- Jest
- Mocha
diff --git a/docs/api.md b/docs/api.md
index f84cf9caf44c0..4b79efc9b2feb 100644
--- a/docs/api.md
+++ b/docs/api.md
@@ -1242,11 +1242,10 @@ Returns frame matching the specified criteria. Either `name` or `url` must be sp
#### 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 [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:
+ - `waitUntil` <"load"|"domcontentloaded"|"networkidle"> When to consider navigation succeeded, defaults to `load`. Events can be either:
- `'domcontentloaded'` - consider navigation to be finished when the `DOMContentLoaded` event is fired.
- `'load'` - consider navigation to be finished when the `load` 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.
+ - `'networkidle'` - consider navigation to be finished when there are no network connections for at least `500` ms.
- returns: <[Promise][Response]>> Promise which resolves to the main resource response. In case of multiple redirects, the navigation will resolve with the response of the last redirect. If
can not go back, resolves to `null`.
@@ -1255,11 +1254,10 @@ 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 [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:
+ - `waitUntil` <"load"|"domcontentloaded"|"networkidle"> When to consider navigation succeeded, defaults to `load`. Events can be either:
- `'domcontentloaded'` - consider navigation to be finished when the `DOMContentLoaded` event is fired.
- `'load'` - consider navigation to be finished when the `load` 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.
+ - `'networkidle'` - consider navigation to be finished when there are no network connections for at least `500` ms.
- returns: <[Promise][Response]>> Promise which resolves to the main resource response. In case of multiple redirects, the navigation will resolve with the response of the last redirect. If
can not go forward, resolves to `null`.
@@ -1269,11 +1267,10 @@ Navigate to the next page in history.
- `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 [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:
+ - `waitUntil` <"load"|"domcontentloaded"|"networkidle"> When to consider navigation succeeded, defaults to `load`. Events can be either:
- `'domcontentloaded'` - consider navigation to be finished when the `DOMContentLoaded` event is fired.
- `'load'` - consider navigation to be finished when the `load` 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.
+ - `'networkidle'` - consider navigation to be finished when there are no network connections for at least `500` ms.
- `referer` <[string]> Referer header value. If provided it will take preference over the referer header value set by [page.setExtraHTTPHeaders()](#pagesetextrahttpheadersheaders).
- returns: <[Promise][Response]>> Promise which resolves to the main resource response. In case of multiple redirects, the navigation will resolve with the response of the last redirect.
@@ -1441,11 +1438,10 @@ await browser.close();
#### 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 [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:
+ - `waitUntil` <"load"|"domcontentloaded"|"networkidle"> When to consider navigation succeeded, defaults to `load`. Events can be either:
- `'domcontentloaded'` - consider navigation to be finished when the `DOMContentLoaded` event is fired.
- `'load'` - consider navigation to be finished when the `load` 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.
+ - `'networkidle'` - consider navigation to be finished when there are no network connections for at least `500` ms.
- returns: <[Promise][Response]>> Promise which resolves to the main resource response. In case of multiple redirects, the navigation will resolve with the response of the last redirect.
#### page.route(url, handler)
@@ -1527,11 +1523,10 @@ Shortcut for [page.mainFrame().selectOption()](#frameselectoptionselector-values
- `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. 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 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:
+ - `waitUntil` <"load"|"domcontentloaded"|"networkidle"> 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.
- - `'networkidle0'` - consider setting content to be finished when there are no more than 0 network connections for at least `500` ms.
- - `'networkidle2'` - consider setting content to be finished when there are no more than 2 network connections for at least `500` ms.
+ - `'networkidle'` - consider setting content to be finished when there are no network connections for at least `500` ms.
- returns: <[Promise]>
#### page.setDefaultNavigationTimeout(timeout)
@@ -1736,11 +1731,10 @@ await page.waitForFunction(selector => !!document.querySelector(selector), selec
Shortcut for [page.mainFrame().waitForFunction(pageFunction, arg, options]])](#framewaitforfunctionpagefunction-arg-options).
#### page.waitForLoadState([state[, options]])
-- `state` <"load"|"domcontentloaded"|"networkidle0"|"networkidle2"> Load state to wait for, defaults to `load`. If the state has been already reached while loading current document, the method resolves immediately.
+- `state` <"load"|"domcontentloaded"|"networkidle"> Load state to wait for, defaults to `load`. If the state has been already reached while loading current document, the method resolves immediately.
- `'load'` - wait for the `load` event to be fired.
- `'domcontentloaded'` - wait for the `DOMContentLoaded` event to be fired.
- - `'networkidle0'` - wait until there are no more than 0 network connections for at least `500` ms.
- - `'networkidle2'` - wait until there are no more than 2 network connections for at least `500` ms.
+ - `'networkidle'` - wait until there are no network connections for at least `500` ms.
- `options` <[Object]>
- `timeout` <[number]> Maximum waiting 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.
- returns: <[Promise]> Promise which resolves when the required load state has been reached.
@@ -1767,11 +1761,10 @@ Shortcut for [page.mainFrame().waitForLoadState([options])](#framewaitforloadsta
- `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.
- `url` <[string]|[RegExp]|[Function]> A glob pattern, regex pattern or predicate receiving [URL] to match while waiting for the navigation.
- - `waitUntil` <"load"|"domcontentloaded"|"networkidle0"|"networkidle2"> When to consider navigation succeeded, defaults to `load`. Events can be either:
+ - `waitUntil` <"load"|"domcontentloaded"|"networkidle"> When to consider navigation succeeded, defaults to `load`. Events can be either:
- `'domcontentloaded'` - consider navigation to be finished when the `DOMContentLoaded` event is fired.
- `'load'` - consider navigation to be finished when the `load` 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.
+ - `'networkidle'` - consider navigation to be finished when there are no network connections for at least `500` ms.
- returns: <[Promise][Response]>> Promise which resolves to the main resource response. In case of multiple redirects, the navigation will resolve with the response of the last redirect. In case of navigation to a different anchor or navigation due to History API usage, the navigation will resolve with `null`.
This resolves when the page navigates to a new URL or reloads. It is useful for when you run code
@@ -2154,11 +2147,10 @@ console.log(frame === contentFrame); // -> true
- `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 [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:
+ - `waitUntil` <"load"|"domcontentloaded"|"networkidle"> When to consider navigation succeeded, defaults to `load`. Events can be either:
- `'domcontentloaded'` - consider navigation to be finished when the `DOMContentLoaded` event is fired.
- `'load'` - consider navigation to be finished when the `load` 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.
+ - `'networkidle'` - consider navigation to be finished when there are no network connections for at least `500` ms.
- `referer` <[string]> Referer header value. If provided it will take preference over the referer header value set by [page.setExtraHTTPHeaders()](#pagesetextrahttpheadersheaders).
- returns: <[Promise][Response]>> Promise which resolves to the main resource response. In case of multiple redirects, the navigation will resolve with the response of the last redirect.
@@ -2261,11 +2253,10 @@ frame.selectOption('select#colors', 'red', 'green', 'blue');
- `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. 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:
+ - `waitUntil` <"load"|"domcontentloaded"|"networkidle"> When to consider navigation succeeded, defaults to `load`. Events can be either:
- `'domcontentloaded'` - consider setting content to be finished when the `DOMContentLoaded` event is fired.
- `'load'` - consider setting content to be finished when the `load` event is fired.
- - `'networkidle0'` - consider setting content to be finished when there are no more than 0 network connections for at least `500` ms.
- - `'networkidle2'` - consider setting content to be finished when there are no more than 2 network connections for at least `500` ms.
+ - `'networkidle'` - consider setting content to be finished when there are no network connections for at least `500` ms.
- returns: <[Promise]>
#### frame.setInputFiles(selector, files[, options])
@@ -2389,11 +2380,10 @@ await frame.waitForFunction(selector => !!document.querySelector(selector), sele
```
#### frame.waitForLoadState([state[, options]])
-- `state` <"load"|"domcontentloaded"|"networkidle0"|"networkidle2"> Load state to wait for, defaults to `load`. If the state has been already reached while loading current document, the method resolves immediately.
+- `state` <"load"|"domcontentloaded"|"networkidle"> Load state to wait for, defaults to `load`. If the state has been already reached while loading current document, the method resolves immediately.
- `'load'` - wait for the `load` event to be fired.
- `'domcontentloaded'` - wait for the `DOMContentLoaded` event to be fired.
- - `'networkidle0'` - wait until there are no more than 0 network connections for at least `500` ms.
- - `'networkidle2'` - wait until there are no more than 2 network connections for at least `500` ms.
+ - `'networkidle'` - wait until there are no network connections for at least `500` ms.
- `options` <[Object]>
- `timeout` <[number]> Maximum waiting 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.
- returns: <[Promise]> Promise which resolves when the required load state has been reached.
@@ -2409,11 +2399,10 @@ await frame.waitForLoadState(); // The promise resolves after 'load' event.
- `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.
- `url` <[string]|[RegExp]|[Function]> URL string, URL regex pattern or predicate receiving [URL] to match while waiting for the navigation.
- - `waitUntil` <"load"|"domcontentloaded"|"networkidle0"|"networkidle2"> When to consider navigation succeeded, defaults to `load`. Events can be either:
+ - `waitUntil` <"load"|"domcontentloaded"|"networkidle"> When to consider navigation succeeded, defaults to `load`. Events can be either:
- `'domcontentloaded'` - consider navigation to be finished when the `DOMContentLoaded` event is fired.
- `'load'` - consider navigation to be finished when the `load` 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.
+ - `'networkidle'` - consider navigation to be finished when there are no network connections for at least `500` ms.
- returns: <[Promise][Response]>> Promise which resolves to the main resource response. In case of multiple redirects, the navigation will resolve with the response of the last redirect. In case of navigation to a different anchor or navigation due to History API usage, the navigation will resolve with `null`.
This resolves when the frame navigates to a new URL. It is useful for when you run code
diff --git a/docs/ci.md b/docs/ci.md
index b2d17cb333992..6c2ec77828240 100644
--- a/docs/ci.md
+++ b/docs/ci.md
@@ -1,4 +1,4 @@
-# Continuous integration.md
+# Continuous integration
Playwright tests can be executed to run on your CI environments. To simplify this, we have created sample configurations for common CI providers that can be used to bootstrap your setup.
diff --git a/docs/core-concepts.md b/docs/core-concepts.md
index f95fe6b09cf95..064ff95cb77e2 100644
--- a/docs/core-concepts.md
+++ b/docs/core-concepts.md
@@ -168,16 +168,7 @@ await page.click('css:light=div');
Selectors using the same or different engines can be combined using the `>>` separator. For example,
```js
-await page.click('css=article >> css=.bar > .baz >> css=span[attr=value]');
-```
-
-is equivalent to
-
-```js
-document
- .querySelector('article')
- .querySelector('.bar > .baz')
- .querySelector('span[attr=value]')
+await page.click('#free-month-promo >> text=Learn more');
```
diff --git a/docs/extensibility.md b/docs/extensibility.md
new file mode 100644
index 0000000000000..771a636e31b1f
--- /dev/null
+++ b/docs/extensibility.md
@@ -0,0 +1,51 @@
+# Extensibility
+
+#### Contents
+
+- [Custom selector engines](#custom-selector-engines)
+
+## Custom selector engines
+
+Playwright supports custom selector engines, registered with [selectors.register(name, script[, options])](api.md#selectorsregistername-script-options).
+
+Selector engine should have the following properties:
+
+- `create` function to create a relative selector from `root` (root is either a `Document`, `ShadowRoot` or `Element`) to a `target` element.
+- `query` function to query first element matching `selector` relative to the `root`.
+- `queryAll` function to query all elements matching `selector` relative to the `root`.
+
+By default the engine is run directly in the frame's JavaScript context and, for example, can call an application-defined function. To isolate the engine from any JavaScript in the frame, but leave access to the DOM, resgister the engine with `{contentScript: true}` option. Content script engine is safer because it is protected from any tampering with the global objects, for example altering `Node.prototype` methods. All built-in selector engines run as content scripts. Note that running as a content script is not guaranteed when the engine is used together with other custom engines.
+
+An example of registering selector engine that queries elements based on a tag name:
+```js
+// Must be a function that evaluates to a selector engine instance.
+const createTagNameEngine = () => ({
+ // Creates a selector that matches given target when queried at the root.
+ // Can return undefined if unable to create one.
+ create(root, target) {
+ return root.querySelector(target.tagName) === target ? target.tagName : undefined;
+ },
+
+ // Returns the first element matching given selector in the root's subtree.
+ query(root, selector) {
+ return root.querySelector(selector);
+ },
+
+ // Returns all elements matching given selector in the root's subtree.
+ queryAll(root, selector) {
+ return Array.from(root.querySelectorAll(selector));
+ }
+});
+
+// Register the engine. Selectors will be prefixed with "tag=".
+await selectors.register('tag', createTagNameEngine);
+
+// Now we can use 'tag=' selectors.
+const button = await page.$('tag=button');
+
+// We can combine it with other selector engines using `>>` combinator.
+await page.click('tag=div >> span >> "Click me"');
+
+// We can use it in any methods supporting selectors.
+const buttonCount = await page.$$eval('tag=button', buttons => buttons.length);
+```
diff --git a/docs/loading.md b/docs/loading.md
index 3c7d7d4c951b2..7e7c5d566e1e4 100644
--- a/docs/loading.md
+++ b/docs/loading.md
@@ -38,7 +38,7 @@ Page load takes time retrieving the response body over the network, parsing, exe
- page executes some scripts and loads resources like stylesheets and images
- [`load`](api.md#event-load) event is fired
- page executes dynamically loaded scripts
-- `networkidle0` is fired - no new network requests made for at least `500` ms
+- `networkidle` is fired - no new network requests made for at least `500` ms
### Common scenarios
diff --git a/src/frames.ts b/src/frames.ts
index 321c63a46da50..500bcec081a02 100644
--- a/src/frames.ts
+++ b/src/frames.ts
@@ -206,12 +206,9 @@ export class FrameManager {
frame._firedLifecycleEvents.clear();
// Keep the current navigation request if any.
frame._inflightRequests = new Set(Array.from(frame._inflightRequests).filter(request => request._documentId === frame._lastDocumentId));
- this._stopNetworkIdleTimer(frame, 'networkidle0');
+ frame._stopNetworkIdleTimer();
if (frame._inflightRequests.size === 0)
- this._startNetworkIdleTimer(frame, 'networkidle0');
- this._stopNetworkIdleTimer(frame, 'networkidle2');
- if (frame._inflightRequests.size <= 2)
- this._startNetworkIdleTimer(frame, 'networkidle2');
+ frame._startNetworkIdleTimer();
}
requestStarted(request: network.Request) {
@@ -282,9 +279,7 @@ export class FrameManager {
return;
frame._inflightRequests.delete(request);
if (frame._inflightRequests.size === 0)
- this._startNetworkIdleTimer(frame, 'networkidle0');
- if (frame._inflightRequests.size === 2)
- this._startNetworkIdleTimer(frame, 'networkidle2');
+ frame._startNetworkIdleTimer();
}
private _inflightRequestStarted(request: network.Request) {
@@ -293,25 +288,7 @@ export class FrameManager {
return;
frame._inflightRequests.add(request);
if (frame._inflightRequests.size === 1)
- this._stopNetworkIdleTimer(frame, 'networkidle0');
- if (frame._inflightRequests.size === 3)
- this._stopNetworkIdleTimer(frame, 'networkidle2');
- }
-
- private _startNetworkIdleTimer(frame: Frame, event: types.LifecycleEvent) {
- assert(!frame._networkIdleTimers.has(event));
- if (frame._firedLifecycleEvents.has(event))
- return;
- frame._networkIdleTimers.set(event, setTimeout(() => {
- this.frameLifecycleEvent(frame._id, event);
- }, 500));
- }
-
- private _stopNetworkIdleTimer(frame: Frame, event: types.LifecycleEvent) {
- const timeoutId = frame._networkIdleTimers.get(event);
- if (timeoutId)
- clearTimeout(timeoutId);
- frame._networkIdleTimers.delete(event);
+ frame._stopNetworkIdleTimer();
}
interceptConsoleMessage(message: ConsoleMessage): boolean {
@@ -341,7 +318,7 @@ export class Frame {
private _childFrames = new Set();
_name = '';
_inflightRequests = new Set();
- readonly _networkIdleTimers = new Map();
+ private _networkIdleTimer: NodeJS.Timer | undefined;
private _setContentCounter = 0;
readonly _detachedPromise: Promise;
private _detachedCallback = () => {};
@@ -859,6 +836,19 @@ export class Frame {
this._setContext(contextType, null);
}
}
+
+ _startNetworkIdleTimer() {
+ assert(!this._networkIdleTimer);
+ if (this._firedLifecycleEvents.has('networkidle'))
+ return;
+ this._networkIdleTimer = setTimeout(() => { this._page._frameManager.frameLifecycleEvent(this._id, 'networkidle'); }, 500);
+ }
+
+ _stopNetworkIdleTimer() {
+ if (this._networkIdleTimer)
+ clearTimeout(this._networkIdleTimer);
+ this._networkIdleTimer = undefined;
+ }
}
type Task = (context: dom.FrameExecutionContext) => Promise;
diff --git a/src/types.ts b/src/types.ts
index 70a6f7de30ad9..e428dd901a88f 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -42,8 +42,8 @@ export type WaitForElementOptions = TimeoutOptions & { waitFor?: 'attached' | 'd
export type Polling = 'raf' | 'mutation' | number;
export type WaitForFunctionOptions = TimeoutOptions & { polling?: Polling };
-export type LifecycleEvent = 'load' | 'domcontentloaded' | 'networkidle0' | 'networkidle2';
-export const kLifecycleEvents: Set = new Set(['load', 'domcontentloaded', 'networkidle0', 'networkidle2']);
+export type LifecycleEvent = 'load' | 'domcontentloaded' | 'networkidle';
+export const kLifecycleEvents: Set = new Set(['load', 'domcontentloaded', 'networkidle']);
export type NavigateOptions = TimeoutOptions & {
waitUntil?: LifecycleEvent,
diff --git a/test/assets/networkidle.js b/test/assets/networkidle.js
index 4be3059a71037..0f5988b4b9314 100644
--- a/test/assets/networkidle.js
+++ b/test/assets/networkidle.js
@@ -3,10 +3,11 @@ async function sleep(delay) {
}
async function main() {
+ window.ws = new WebSocket('ws://localhost:' + window.location.port + '/ws');
+ window.ws.addEventListener('message', message => {});
+
const roundOne = Promise.all([
fetch('fetch-request-a.js'),
- fetch('fetch-request-b.js'),
- fetch('fetch-request-c.js'),
]);
await roundOne;
diff --git a/test/autowaiting.spec.js b/test/autowaiting.spec.js
index 89cf74a4c88c4..2fe769fa44f0b 100644
--- a/test/autowaiting.spec.js
+++ b/test/autowaiting.spec.js
@@ -54,7 +54,7 @@ describe('Auto waiting', () => {
]);
expect(messages.join('|')).toBe('popup|click');
});
- it('should await download when clicking anchor', async function({page, server}) {
+ it.fail(CHROMIUM)('should await download when clicking anchor', async function({page, server}) {
server.setRoute('/download', (req, res) => {
res.setHeader('Content-Type', 'application/octet-stream');
res.setHeader('Content-Disposition', 'attachment');
diff --git a/test/navigation.spec.js b/test/navigation.spec.js
index d4107128e3ddf..c487066444488 100644
--- a/test/navigation.spec.js
+++ b/test/navigation.spec.js
@@ -172,9 +172,14 @@ describe('Page.goto', function() {
await page.goto(server.CROSS_PROCESS_PREFIX + '/empty.html');
await page.goto(httpsServer.EMPTY_PAGE).catch(e => void 0);
});
- it('should throw if networkidle is passed as an option', async({page, server}) => {
+ it('should throw if networkidle0 is passed as an option', async({page, server}) => {
let error = null;
- await page.goto(server.EMPTY_PAGE, {waitUntil: 'networkidle'}).catch(err => error = err);
+ await page.goto(server.EMPTY_PAGE, {waitUntil: 'networkidle0'}).catch(err => error = err);
+ expect(error.message).toContain('Unsupported waitUntil option');
+ });
+ it('should throw if networkidle2 is passed as an option', async({page, server}) => {
+ let error = null;
+ await page.goto(server.EMPTY_PAGE, {waitUntil: 'networkidle2'}).catch(err => error = err);
expect(error.message).toContain('Unsupported waitUntil option');
});
it('should fail when main resources failed to load', async({page, server}) => {
@@ -407,23 +412,18 @@ describe('Page.goto', function() {
});
describe('network idle', function() {
- it('should navigate to empty page with networkidle0', async({page, server}) => {
- const response = await page.goto(server.EMPTY_PAGE, { waitUntil: 'networkidle0' });
- expect(response.status()).toBe(200);
- });
- it('should navigate to empty page with networkidle2', async({page, server}) => {
- const response = await page.goto(server.EMPTY_PAGE, { waitUntil: 'networkidle2' });
+ it('should navigate to empty page with networkidle', async({page, server}) => {
+ const response = await page.goto(server.EMPTY_PAGE, { waitUntil: 'networkidle' });
expect(response.status()).toBe(200);
});
/**
* @param {import('../src/frames').Frame} frame
* @param {TestServer} server
- * @param {'networkidle0'|'networkidle2'} signal
* @param {() => Promise} action
* @param {boolean} isSetContent
*/
- async function networkIdleTest(frame, server, signal, action, isSetContent) {
+ async function networkIdleTest(frame, server, action, isSetContent) {
const finishResponse = response => {
response.statusCode = 404;
response.end(`File not found`);
@@ -437,19 +437,13 @@ describe('Page.goto', function() {
let responses = {};
// Hold on to a bunch of requests without answering.
server.setRoute('/fetch-request-a.js', (req, res) => responses.a = res);
- server.setRoute('/fetch-request-b.js', (req, res) => responses.b = res);
- server.setRoute('/fetch-request-c.js', (req, res) => responses.c = res);
const initialFetchResourcesRequested = Promise.all([
waitForRequest('/fetch-request-a.js'),
- waitForRequest('/fetch-request-b.js'),
- waitForRequest('/fetch-request-c.js')
]);
let secondFetchResourceRequested;
- if (signal === 'networkidle0') {
- server.setRoute('/fetch-request-d.js', (req, res) => responses.d = res);
- secondFetchResourceRequested = waitForRequest('/fetch-request-d.js');
- }
+ server.setRoute('/fetch-request-d.js', (req, res) => responses.d = res);
+ secondFetchResourceRequested = waitForRequest('/fetch-request-d.js');
const waitForLoadPromise = isSetContent ? Promise.resolve() : frame.waitForNavigation({ waitUntil: 'load' });
@@ -470,139 +464,73 @@ describe('Page.goto', function() {
expect(actionFinished).toBe(false);
expect(responses.a).toBeTruthy();
- expect(responses.b).toBeTruthy();
- expect(responses.c).toBeTruthy();
let timer;
let timerTriggered = false;
- if (signal === 'networkidle0') {
- // Finishing first response should leave 2 requests alive and trigger networkidle2.
- finishResponse(responses.a);
- // Finishing two more responses should trigger the second round.
- finishResponse(responses.b);
- finishResponse(responses.c);
+ // Finishing response should trigger the second round.
+ finishResponse(responses.a);
+
+ // Wait for the second round to be requested.
+ await secondFetchResourceRequested;
+ expect(actionFinished).toBe(false);
+ // Finishing the last response should trigger networkidle.
+ timer = setTimeout(() => timerTriggered = true, 500);
+ finishResponse(responses.d);
- // Wait for the second round to be requested.
- await secondFetchResourceRequested;
- expect(actionFinished).toBe(false);
- // Finishing the last response should trigger networkidle0.
- timer = setTimeout(() => timerTriggered = true, 500);
- finishResponse(responses.d);
- } else {
- timer = setTimeout(() => timerTriggered = true, 500);
- // Finishing first response should leave 2 requests alive and trigger networkidle2.
- finishResponse(responses.a);
- }
const response = await actionPromise;
clearTimeout(timer);
expect(timerTriggered).toBe(true);
if (!isSetContent)
expect(response.ok()).toBe(true);
-
- if (signal === 'networkidle2') {
- // Cleanup.
- finishResponse(responses.b);
- finishResponse(responses.c);
- }
}
- it('should wait for networkidle0 to succeed navigation', async({page, server}) => {
- await networkIdleTest(page.mainFrame(), server, 'networkidle0', () => {
- return page.goto(server.PREFIX + '/networkidle.html', { waitUntil: 'networkidle0' });
- });
- });
- it('should wait for networkidle2 to succeed navigation', async({page, server}) => {
- await networkIdleTest(page.mainFrame(), server, 'networkidle2', () => {
- return page.goto(server.PREFIX + '/networkidle.html', { waitUntil: 'networkidle2' });
+ it('should wait for networkidle to succeed navigation', async({page, server}) => {
+ await networkIdleTest(page.mainFrame(), server, () => {
+ return page.goto(server.PREFIX + '/networkidle.html', { waitUntil: 'networkidle' });
});
});
- it('should wait for networkidle0 to succeed navigation with request from previous navigation', async({page, server}) => {
+ it('should wait for networkidle to succeed navigation with request from previous navigation', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
server.setRoute('/foo.js', () => {});
await page.setContent(``);
- await networkIdleTest(page.mainFrame(), server, 'networkidle0', () => {
- return page.goto(server.PREFIX + '/networkidle.html', { waitUntil: 'networkidle0' });
- });
- });
- it('should wait for networkidle2 to succeed navigation with request from previous navigation', async({page, server}) => {
- await page.goto(server.EMPTY_PAGE);
- server.setRoute('/foo.js', () => {});
- await page.setContent(``);
- await networkIdleTest(page.mainFrame(), server, 'networkidle2', () => {
- return page.goto(server.PREFIX + '/networkidle.html', { waitUntil: 'networkidle2' });
- });
- });
- it('should wait for networkidle0 in waitForNavigation', async({page, server}) => {
- await networkIdleTest(page.mainFrame(), server, 'networkidle0', () => {
- const promise = page.waitForNavigation({ waitUntil: 'networkidle0' });
- page.goto(server.PREFIX + '/networkidle.html');
- return promise;
+ await networkIdleTest(page.mainFrame(), server, () => {
+ return page.goto(server.PREFIX + '/networkidle.html', { waitUntil: 'networkidle' });
});
});
- it('should wait for networkidle2 in waitForNavigation', async({page, server}) => {
- await networkIdleTest(page.mainFrame(), server, 'networkidle2', () => {
- const promise = page.waitForNavigation({ waitUntil: 'networkidle2' });
+ it('should wait for networkidle in waitForNavigation', async({page, server}) => {
+ await networkIdleTest(page.mainFrame(), server, () => {
+ const promise = page.waitForNavigation({ waitUntil: 'networkidle' });
page.goto(server.PREFIX + '/networkidle.html');
return promise;
});
});
- it('should wait for networkidle0 in setContent', async({page, server}) => {
- await page.goto(server.EMPTY_PAGE);
- await networkIdleTest(page.mainFrame(), server, 'networkidle0', () => {
- return page.setContent(``, { waitUntil: 'networkidle0' });
- }, true);
- });
- it('should wait for networkidle2 in setContent', async({page, server}) => {
+ it('should wait for networkidle in setContent', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
- await networkIdleTest(page.mainFrame(), server, 'networkidle2', () => {
- return page.setContent(``, { waitUntil: 'networkidle2' });
+ await networkIdleTest(page.mainFrame(), server, () => {
+ return page.setContent(``, { waitUntil: 'networkidle' });
}, true);
});
- it('should wait for networkidle0 in setContent with request from previous navigation', async({page, server}) => {
+ it('should wait for networkidle in setContent with request from previous navigation', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
server.setRoute('/foo.js', () => {});
await page.setContent(``);
- await networkIdleTest(page.mainFrame(), server, 'networkidle0', () => {
- return page.setContent(``, { waitUntil: 'networkidle0' });
+ await networkIdleTest(page.mainFrame(), server, () => {
+ return page.setContent(``, { waitUntil: 'networkidle' });
}, true);
});
- it('should wait for networkidle2 in setContent with request from previous navigation', async({page, server}) => {
- await page.goto(server.EMPTY_PAGE);
- server.setRoute('/foo.js', () => {});
- await page.setContent(``);
- await networkIdleTest(page.mainFrame(), server, 'networkidle2', () => {
- return page.setContent(``, { waitUntil: 'networkidle2' });
- }, true);
- });
- it('should wait for networkidle0 when navigating iframe', async({page, server}) => {
+ it('should wait for networkidle when navigating iframe', async({page, server}) => {
await page.goto(server.PREFIX + '/frames/one-frame.html');
const frame = page.mainFrame().childFrames()[0];
- await networkIdleTest(frame, server, 'networkidle0', () => frame.goto(server.PREFIX + '/networkidle.html', { waitUntil: 'networkidle0' }));
+ await networkIdleTest(frame, server, () => frame.goto(server.PREFIX + '/networkidle.html', { waitUntil: 'networkidle' }));
});
- it('should wait for networkidle2 when navigating iframe', async({page, server}) => {
- await page.goto(server.PREFIX + '/frames/one-frame.html');
- const frame = page.mainFrame().childFrames()[0];
- await networkIdleTest(frame, server, 'networkidle2', () => frame.goto(server.PREFIX + '/networkidle.html', { waitUntil: 'networkidle2' }));
- });
- it('should wait for networkidle0 in setContent from the child frame', async({page, server}) => {
+ it('should wait for networkidle in setContent from the child frame', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
- await networkIdleTest(page.mainFrame(), server, 'networkidle0', () => {
- return page.setContent(``, { waitUntil: 'networkidle0' });
+ await networkIdleTest(page.mainFrame(), server, () => {
+ return page.setContent(``, { waitUntil: 'networkidle' });
}, true);
});
- it('should wait for networkidle2 in setContent from the child frame', async({page, server}) => {
- await page.goto(server.EMPTY_PAGE);
- await networkIdleTest(page.mainFrame(), server, 'networkidle2', () => {
- return page.setContent(``, { waitUntil: 'networkidle2' });
- }, true);
- });
- it('should wait for networkidle0 from the child frame', async({page, server}) => {
- await networkIdleTest(page.mainFrame(), server, 'networkidle0', () => {
- return page.goto(server.PREFIX + '/networkidle-frame.html', { waitUntil: 'networkidle0' });
- });
- });
- it('should wait for networkidle2 from the child frame', async({page, server}) => {
- await networkIdleTest(page.mainFrame(), server, 'networkidle2', () => {
- return page.goto(server.PREFIX + '/networkidle-frame.html', { waitUntil: 'networkidle2' });
+ it('should wait for networkidle from the child frame', async({page, server}) => {
+ await networkIdleTest(page.mainFrame(), server, () => {
+ return page.goto(server.PREFIX + '/networkidle-frame.html', { waitUntil: 'networkidle' });
});
});
});
diff --git a/utils/generate_types/test/test.ts b/utils/generate_types/test/test.ts
index ca9867c89c983..0578035682696 100644
--- a/utils/generate_types/test/test.ts
+++ b/utils/generate_types/test/test.ts
@@ -31,7 +31,7 @@ type AssertNotAny = {notRealProperty: number} extends S ? false : true;
(async () => {
const browser = await playwright.chromium.launch();
const page = await browser.newPage();
- await page.goto('https://news.ycombinator.com', { waitUntil: 'networkidle0' });
+ await page.goto('https://news.ycombinator.com', { waitUntil: 'networkidle' });
await page.pdf({ path: 'hn.pdf', format: 'A4' });
browser.close();