docs: update navigation and loading page (#3655)

* docs: update navigation and loading doc

* dgozman comments

* doclint
This commit is contained in:
Arjun Attam 2020-08-27 17:47:23 -07:00 committed by GitHub
parent cfbec44285
commit 7444de4b73
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 170 additions and 131 deletions

View file

@ -30,7 +30,7 @@ Playwright is a library to automate [Chromium](https://www.chromium.org/Home), [
- [Network](./network.md)
- [Assertions](./assertions.md)
- [Verification](./verification.md)
- [Navigation and Loading](./loading.md)
- [Navigations](./navigations.md)
- [Multi-page scenarios](./multi-pages.md)
1. Tutorials
- [Authentication](./auth.md)

View file

@ -100,7 +100,7 @@ console.log(page.url());
window.location.href = 'https://example.com';
```
> Read more on [page navigation and loading](loading.md).
> Read more on [page navigation and loading](navigations.md).
A page can have one or more [Frame](api.md#class-frame) objects attached to
it. Each page has a main frame and page-level interactions (like `click`) are

View file

@ -1,128 +0,0 @@
# Navigation and loading
Playwright logically splits the process of showing a new document in the page into **navigation** and **loading**.
<!-- GEN:toc-top-level -->
- [Navigation](#navigation)
- [Loading](#loading)
- [Common scenarios](#common-scenarios)
- [Loading a popup](#loading-a-popup)
<!-- GEN:stop -->
## Navigation
Page navigation can be either initiated by the Playwright call:
```js
// Load a page
await page.goto('https://example.com');
// Reload a page
await page.reload();
// Click a link
await page.click('text="Continue"');
```
or by the page itself:
```js
// Programmatic navigation
window.location.href = 'https://example.com';
// Single page app navigation
history.pushState({}, 'title', '#deep-link');
```
Navigation intent may result in being canceled, for example transformed into a download or hitting an unresolved DNS address. Only when the navigation succeeds, page starts **loading** the document.
## Loading
Page load takes time retrieving the response body over the network, parsing, executing the scripts and firing the events. Typical load scenario goes through the following load states:
- [`page.url()`](api.md#pageurl) is set to the new url
- document content is loaded over network and parsed
- [`domcontentloaded`](api.md#event-domcontentloaded) event is fired
- page executes some scripts and loads resources like stylesheets and images
- [`load`](api.md#event-load) event is fired
- page executes dynamically loaded scripts
- `networkidle` is fired - no new network requests made for at least `500` ms
## Common scenarios
By default, Playwright handles navigations seamlessly so that you did not need to think about them. Consider the following scenario, where everything is handled by Playwright behind the scenes:
```js
await page.goto('http://example.com');
// If the page does a client-side redirect to 'http://example.com/login'.
// Playwright will automatically wait for the login page to load.
// Playwright waits for the lazy loaded #username and #password inputs
// to appear before filling the values.
await page.fill('#username', 'John Doe');
await page.fill('#password', '********');
// Playwright waits for the login button to become enabled and clicks it.
await page.click('text=Login');
// Clicking the button navigates to the logged-in page and Playwright
// automatically waits for that.
```
Explicit loading handling may be required for more complicated scenarios though.
## Loading a popup
When popup is opened, explicitly calling [`page.waitForLoadState()`](api.md#pagewaitforloadstatestate-options) ensures that popup is loaded to the desired state.
```js
const [ popup ] = await Promise.all([
page.waitForEvent('popup'),
page.click('a[target="_blank"]'), // <-- opens popup
]);
await popup.waitForLoadState('load');
await popup.evaluate(() => window.globalVariableInitializedByOnLoadHandler);
```
### Unusual client-side redirects
Usually, the client-side redirect happens before the `load` event, and `page.goto()` method automatically waits for the redirect. However, when redirecting from a link click or after the `load` event, it would be easier to explicitly [`waitForNavigation()`](api.md#pagewaitfornavigationoptions) to a specific url.
```js
await Promise.all([
page.waitForNavigation({ url: '**/login' }),
page.click('a'), // Triggers a navigation with a script redirect.
]);
```
Notice the `Promise.all` to click and wait for navigation. Awaiting these methods one after the other is racy, because navigation could happen too fast.
### Click triggers navigation after a timeout
When `onclick` handler triggers a navigation from a `setTimeout`, use an explicit [`waitForNavigation()`](api.md#pagewaitfornavigationoptions) call as a last resort.
```js
await Promise.all([
page.waitForNavigation(), // Waits for the next navigation.
page.click('a'), // Triggers a navigation after a timeout.
]);
```
Notice the `Promise.all` to click and wait for navigation. Awaiting these methods one after the other is racy, because navigation could happen too fast.
### Unpredictable patterns
When the page has a complex loading pattern, the custom waiting function is most reliable.
```js
await page.goto('http://example.com');
await page.waitForFunction(() => window.amILoadedYet());
// Ready to take a screenshot, according to the page itself.
await page.screenshot();
```
When clicking on a button triggers some asynchronous processing, issues a couple GET requests and pushes a new history state multiple times, explicit [`waitForNavigation()`](api.md#pagewaitfornavigationoptions) to a specific url is the most reliable option.
```js
await Promise.all([
page.waitForNavigation({ url: '**/invoice#processed' }),
page.click('text=Process the invoice'), // Triggers some complex handling.
]);
```
### Lazy loading, hydration
TBD

View file

@ -72,11 +72,15 @@ created in the context. This can be used to handle new pages opened by
// Get page after a specific action (e.g. clicking a link)
const [newPage] = await Promise.all([
context.waitForEvent('page'),
page.evaluate(() => window.open('https://google.com', '_blank'))
page.click('a[target="_blank"]') // Opens a new tab
])
await newPage.waitForLoadState();
console.log(await newPage.title());
```
If the action that triggers the new page is unknown, the following pattern can be used.
```js
// Get all new pages (including popups) in the context
context.on('page', async page => {
await page.waitForLoadState();
@ -104,7 +108,11 @@ const [popup] = await Promise.all([
]);
await popup.waitForLoadState();
await popup.title();
```
If the action that triggers the popup is unknown, the following pattern can be used.
```js
// Get all popups when they open
page.on('popup', async popup => {
await popup.waitForLoadState();

159
docs/navigations.md Normal file
View file

@ -0,0 +1,159 @@
# Navigations
Playwright can navigate to URLs and handle navigations caused by page interactions. This guide covers common scenarios to wait for page navigations and loading to complete.
<!-- GEN:toc-top-level -->
- [Navigation lifecycle](#navigation-lifecycle)
- [Scenarios initiated by browser UI](#scenarios-initiated-by-browser-ui)
- [Scenarios initiated by page interaction](#scenarios-initiated-by-page-interaction)
- [Advanced patterns](#advanced-patterns)
<!-- GEN:stop -->
## Navigation lifecycle
Playwright splits the process of showing a new document in a page into **navigation** and **loading**.
**Navigations** can be initiated by changing the page URL or by interacting with the page (e.g., clicking a link). Navigation ends when response headers have been parsed and session history is updated. The navigation intent may be canceled, for example, on hitting an unresolved DNS address or transformed into a file download. Only after the navigation succeeds, page starts **loading** the document.
**Loading** covers getting the remaining response body over the network, parsing, executing the scripts and firing load events:
- [`page.url()`](api.md#pageurl) is set to the new url
- document content is loaded over network and parsed
- [`domcontentloaded`](api.md#event-domcontentloaded) event is fired
- page executes some scripts and loads resources like stylesheets and images
- [`load`](api.md#event-load) event is fired
- page executes dynamically loaded scripts
- `networkidle` is fired when no new network requests are made for 500 ms
## Scenarios initiated by browser UI
Navigations can be initiated by changing the URL bar, reloading the page or going back or forward in session history.
### Auto-wait
Navigating to a URL auto-waits for the page to fire the `load` event. If the page does a client-side redirect before `load`, `page.goto` will auto-wait for the redirected page to fire the `load` event.
```js
// Navigate the page
await page.goto('https://example.com');
```
### Custom wait
Override the default behavior to wait until a specific event, like `networkidle`.
```js
// Navigate and wait until network is idle
await page.goto('https://example.com', { waitUntil: 'networkidle' });
```
### Wait for element
In lazy-loaded pages, it can be useful to wait until an element is visible with [`page.waitForSelector`](./api.md#pagewaitforselectorselector-options). Alternatively, page interactions like [`page.click`](./api.md#pageclickselector-options) auto-wait for elements.
```js
// Navigate and wait for element
await page.goto('https://example.com');
await page.waitForSelector('text=Example Domain');
// Navigate and click element
// Click will auto-wait for the element
await page.goto('https://example.com');
await page.click('text=Example Domain');
```
#### API reference
- [`page.goto(url[, options])`](./api.md#pagegotourl-options)
- [`page.reload([options])`](./api.md#pagereloadoptions)
- [`page.goBack([options])`](./api.md#pagegobackoptions)
- [`page.goForward([options])`](./api.md#pagegoforwardoptions)
## Scenarios initiated by page interaction
In the scenarios below, `page.click` initiates a navigation and then waits for the navigation to complete.
### Auto-wait
By default, `page.click` will wait for the navigation step to complete. This can be combined with a page interaction on the navigated page which would auto-wait for an element.
```js
// Click will auto-wait for navigation to complete
await page.click('text=Login');
// Fill will auto-wait for element on navigated page
await page.fill('#username', 'John Doe');
```
### Custom wait
`page.click` can be combined with [`page.waitForLoadState`](./api.md#pagewaitforloadstatestate-options) to wait for a loading event.
```js
await page.click('button'); // Click triggers navigation
await page.waitForLoadState('networkidle'); // This resolves after 'networkidle'
```
### Wait for element
In lazy-loaded pages, it can be useful to wait until an element is visible with [`page.waitForSelector`](./api.md#pagewaitforselectorselector-options). Alternatively, page interactions like [`page.click`](./api.md#pageclickselector-options) auto-wait for elements.
```js
// Click triggers navigation
await page.click('text=Login');
// Click will auto-wait for the element
await page.waitForSelector('#username', 'John Doe');
// Click triggers navigation
await page.click('text=Login');
// Fill will auto-wait for element
await page.fill('#username', 'John Doe');
```
### Asynchronous navigation
Clicking an element could trigger asychronous processing before initiating the navigation. In these cases, it is recommended to explicitly call [`page.waitForNavigation`](api.md#pagewaitfornavigationoptions). For example:
* Navigation is triggered from a `setTimeout`
* Page waits for network requests before navigation
```js
await Promise.all([
page.click('a'), // Triggers a navigation after a timeout
page.waitForNavigation(), // Waits for the next navigation
]);
```
The `Promise.all` pattern prevents a race condition between `page.click` and `page.waitForNavigation` when navigation happens quickly.
### Multiple navigations
Clicking an element could trigger multiple navigations. In these cases, it is recommended to explicitly [`page.waitForNavigation`](api.md#pagewaitfornavigationoptions) to a specific url. For example:
* Client-side redirects issued after the `load` event
* Multiple pushes to history state
```js
await Promise.all([
page.waitForNavigation({ url: '**/login' }),
page.click('a'), // Triggers a navigation with a script redirect
]);
```
The `Promise.all` pattern prevents a race condition between `page.click` and `page.waitForNavigation` when navigation happens quickly.
### Loading a popup
When popup is opened, explicitly calling [`page.waitForLoadState`](api.md#pagewaitforloadstatestate-options) ensures that popup is loaded to the desired state.
```js
const [ popup ] = await Promise.all([
page.waitForEvent('popup'),
page.click('a[target="_blank"]'), // Opens popup
]);
await popup.waitForLoadState('load');
```
#### API reference
- [`page.click(selector[, options])`](./api.md#pageclickselector-options)
- [`page.waitForLoadState([state[, options]])`](./api.md#pagewaitforloadstatestate-options)
- [`page.waitForSelector(selector[, options])`](./api.md#pagewaitforselectorselector-options)
- [`page.waitForNavigation([options])`](./api.md#pagewaitfornavigationoptions)
- [`page.waitForFunction(pageFunction[, arg, options])`](./api.md#pagewaitforfunctionpagefunction-arg-options)
## Advanced patterns
For pages that have complicated loading patterns, [`page.waitForFunction`](./api.md#pagewaitforfunctionpagefunction-arg-options) is a powerful and extensible approach to define a custom wait criteria.
```js
await page.goto('http://example.com');
await page.waitForFunction(() => window.amILoadedYet());
// Ready to take a screenshot, according to the page itself.
await page.screenshot();
```
#### API reference
- [`page.waitForFunction(pageFunction[, arg, options])`](./api.md#pagewaitforfunctionpagefunction-arg-options)