diff --git a/docs/README.md b/docs/README.md index 1c9c0a4749..235d5b0d7d 100644 --- a/docs/README.md +++ b/docs/README.md @@ -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) diff --git a/docs/core-concepts.md b/docs/core-concepts.md index 93a5f88e4b..37da756650 100644 --- a/docs/core-concepts.md +++ b/docs/core-concepts.md @@ -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 diff --git a/docs/loading.md b/docs/loading.md deleted file mode 100644 index e7e2905f8d..0000000000 --- a/docs/loading.md +++ /dev/null @@ -1,128 +0,0 @@ -# Navigation and loading - -Playwright logically splits the process of showing a new document in the page into **navigation** and **loading**. - - -- [Navigation](#navigation) -- [Loading](#loading) -- [Common scenarios](#common-scenarios) -- [Loading a popup](#loading-a-popup) - - -## 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 diff --git a/docs/multi-pages.md b/docs/multi-pages.md index 542c193940..cb1c7d46b0 100644 --- a/docs/multi-pages.md +++ b/docs/multi-pages.md @@ -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(); diff --git a/docs/navigations.md b/docs/navigations.md new file mode 100644 index 0000000000..b4344fb485 --- /dev/null +++ b/docs/navigations.md @@ -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. + + +- [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) + + +## 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)