chore: update navigation and timers docs (#22941)

This commit is contained in:
Pavel Feldman 2023-05-11 09:56:48 -07:00 committed by GitHub
parent 4d41535081
commit e41b21dc7b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 200 additions and 436 deletions

View File

@ -2243,6 +2243,8 @@ class FrameExamples
## async method: Frame.waitForTimeout
* since: v1.8
* discouraged: Never wait for timeout in production. Tests that wait for time are
inherently flaky. Use [Locator] actions and web assertions that wait automatically.
Waits for the given [`param: timeout`] in milliseconds.

View File

@ -584,6 +584,12 @@ Math.random = () => 42;
await page.addInitScript({ path: './preload.js' });
```
```js
await page.addInitScript(mock => {
window.mock = mock;
}, mock);
```
```java
// In your playwright script, assuming the preload.js file is in same directory
page.addInitScript(Paths.get("./preload.js"));

View File

@ -3,9 +3,152 @@ id: navigations
title: "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.
Playwright can navigate to URLs and handle navigations caused by the page interactions.
## Navigation lifecycle
## Basic navigation
Simplest form of a navigation is opening a URL:
```js
// Navigate the page
await page.goto('https://example.com');
```
```java
// Navigate the page
page.navigate("https://example.com");
```
```python async
# Navigate the page
await page.goto("https://example.com")
```
```python sync
# Navigate the page
page.goto("https://example.com")
```
```csharp
// Navigate the page
await page.GotoAsync("https://example.com");
```
The code above loads the page and waits for the web page to fire the
[load](https://developer.mozilla.org/en-US/docs/Web/API/Window/load_event) event.
The load event is fired when the whole page has loaded, including all dependent
resources such as stylesheets, scripts, iframes, and images.
:::note
If the page does a client-side redirect before `load`, [`method: Page.goto`] will
wait for the redirected page to fire the `load` event.
:::
## When is the page loaded?
Modern pages perform numerous activities after the `load` event was fired. They
fetch data lazily, populate UI, load expensive resources, scripts and styles after
the `load` event was fired. There is no way to tell that the page is `loaded`,
it depends on the page, framework, etc. So when can you start interacting with
it?
In Playwright you can interact with the page at any moment. It will automatically
wait for the target elements to become [actionable](./actionability.md).
```js
// Navigate and click element
// Click will auto-wait for the element
await page.goto('https://example.com');
await page.getByText('Example Domain').click();
```
```java
// Navigate and click element
// Click will auto-wait for the element
page.navigate("https://example.com");
page.getByText("Example Domain").click();
```
```python async
# Navigate and click element
# Click will auto-wait for the element
await page.goto("https://example.com")
await page.get_by_text("example domain").click()
```
```python sync
# Navigate and click element
# Click will auto-wait for the element
page.goto("https://example.com")
page.get_by_text("example domain").click()
```
```csharp
// Navigate and click element
// Click will auto-wait for the element
await page.GotoAsync("https://example.com");
await page.GetByText("Example Domain").ClickAsync();
```
For the scenario above, Playwright will wait for the text to become visible,
will wait for the rest of the actionability checks to pass for that element,
and will click it.
Playwright operates as a very fast user - the moment it sees the button, it
clicks it. In the general case, you don't need to worry about whether all the
resources loaded, etc.
## Hydration
At some point in time, you'll stumble upon a use case where Playwright performs
an action, but nothing seemingly happens. Or you enter some text into the input
field and will disappear. The most probable reason behind that is a poor page
[hydration](https://en.wikipedia.org/wiki/Hydration_(web_development)).
When page is hydrated, first, a static version of the page is sent to the browser.
Then the dynamic part is sent and the page becomes "live". As a very fast user,
Playwright will start interacting with the page the moment it sees it. And if
the button on a page is enabled, but the listeners have not yet been added,
Playwright will do its job, but the click won't have any effect.
A simple way to verify if your page suffers from a poor hydration is to open Chrome
DevTools, pick "Slow 3G" network emulation in the Network panel and reload the page.
Once you see the element of interest, interact with it. You'll see that the button
clicks will be ignored and the entered text will be reset by the subsequent page
load code. The right fix for this issue is to make sure that all the interactive
controls are disabled until after the hydration, when the page is fully functional.
## Waiting for navigation
Clicking an element could trigger multiple navigations. In these cases, it is
recommended to explicitly [`method: Page.waitForURL`] to a specific url.
```js
await page.getByText('Click me').click();
await page.waitForURL('**/login');
```
```java
page.getByText("Click me").click();
page.waitForURL("**/login");
```
```python async
await page.get_by_text("Click me").click()
await page.wait_for_url("**/login")
```
```python sync
page.get_by_text("Click me").click()
page.wait_for_url("**/login")
```
```csharp
await page.GetByText("Click me").ClickAsync();
await page.WaitForURL("**/login");
```
## Navigation events
Playwright splits the process of showing a new document in a page into **navigation** and **loading**.
@ -23,391 +166,3 @@ events:
- page executes some scripts and loads resources like stylesheets and images
- [`event: Page.load`] event is fired
- page executes dynamically loaded scripts
## 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`, [`method: Page.goto`] will auto-wait for the redirected page to fire the `load` event.
```js
// Navigate the page
await page.goto('https://example.com');
```
```java
// Navigate the page
page.navigate("https://example.com");
```
```python async
# Navigate the page
await page.goto("https://example.com")
```
```python sync
# Navigate the page
page.goto("https://example.com")
```
```csharp
// Navigate the page
await page.GotoAsync("https://example.com");
```
### Wait for element
In lazy-loaded pages, it can be useful to wait until an element is visible with [`method: Locator.waitFor`].
Alternatively, page interactions like [`method: Page.click`] auto-wait for elements.
```js
// Navigate and wait for element
await page.goto('https://example.com');
await page.getByText('Example Domain').waitFor();
// Navigate and click element
// Click will auto-wait for the element
await page.goto('https://example.com');
await page.getByText('Example Domain').click();
```
```java
// Navigate and wait for element
page.navigate("https://example.com");
page.getByText("Example Domain").waitFor();
// Navigate and click element
// Click will auto-wait for the element
page.navigate("https://example.com");
page.getByText("Example Domain").click();
```
```python async
# Navigate and wait for element
await page.goto("https://example.com")
await page.get_by_text("example domain").wait_for()
# Navigate and click element
# Click will auto-wait for the element
await page.goto("https://example.com")
await page.get_by_text("example domain").click()
```
```python sync
# Navigate and wait for element
page.goto("https://example.com")
page.get_by_text("example domain").wait_for()
# Navigate and click element
# Click will auto-wait for the element
page.goto("https://example.com")
page.get_by_text("example domain").click()
```
```csharp
// Navigate and wait for element
await page.GotoAsync("https://example.com");
await page.GetByText("Example Domain").WaitForAsync();
// Navigate and click element
// Click will auto-wait for the element
await page.GotoAsync("https://example.com");
await page.GetByText("Example Domain").ClickAsync();
```
## Scenarios initiated by page interaction
In the scenarios below, [`method: Locator.click`] initiates a navigation and then waits for the navigation to complete.
### Auto-wait
By default, [`method: Locator.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.getByText('Login').click();
// Fill will auto-wait for element on navigated page
await page.getByLabel('User Name').fill('John Doe');
```
```java
// Click will auto-wait for navigation to complete
page.getByText("Login").click();
// Fill will auto-wait for element on navigated page
page.getByLabel("User Name").fill("John Doe");
```
```python async
# Click will auto-wait for navigation to complete
await page.get_by_text("Login").click()
# Fill will auto-wait for element on navigated page
await page.get_by_label("User Name").fill("John Doe")
```
```python sync
# Click will auto-wait for navigation to complete
page.get_by_text("Login").click()
# Fill will auto-wait for element on navigated page
page.get_by_label("User Name").fill("John Doe")
```
```csharp
// Click will auto-wait for navigation to complete
await page.GetByText("Login").ClickAsync();
// Fill will auto-wait for element on navigated page
await page.GetByLabel("User Name").FillAsync("John Doe");
```
### Wait for element
In lazy-loaded pages, it can be useful to wait until an element is visible with [`method: Locator.waitFor`].
Alternatively, page interactions like [`method: Locator.click`] auto-wait for elements.
```js
// Click will auto-wait for the element and trigger navigation
await page.getByText('Login').click();
// Wait for the element
await page.getByLabel('User Name').waitFor();
// Click triggers navigation
await page.getByText('Login').click();
// Fill will auto-wait for element
await page.getByLabel('User Name').fill('John Doe');
```
```java
// Click will auto-wait for the element and trigger navigation
page.getByText("Login").click();
// Wait for the element
page.getByLabel("User Name").waitFor();
// Click triggers navigation
page.getByText("Login").click();
// Fill will auto-wait for element
page.getByLabel("User Name").fill("John Doe");
```
```python async
# Click will auto-wait for the element and trigger navigation
await page.get_by_text("Login").click()
# Wait for the element
await page.get_by_label("User Name").wait_for()
# Click triggers navigation
await page.get_by_text("Login").click()
# Fill will auto-wait for element
await page.get_by_label("User Name").fill("John Doe")
```
```python sync
# Click triggers navigation
page.get_by_text("Login").click()
# Click will auto-wait for the element
page.get_by_label("User Name").wait_for()
# Click triggers navigation
page.get_by_text("Login").click()
# Fill will auto-wait for element
page.get_by_label("User Name").fill("John Doe")
```
```csharp
// Click will auto-wait for the element and trigger navigation
await page.GetByText("Login").ClickAsync();
// Wait for the element
await page.GetByLabel("User Name").WaitForAsync();
// Click triggers navigation
await page.GetByText("Login").ClickAsync();
// Fill will auto-wait for element
await page.GetByLabel("User Name").FillAsync("John Doe");
```
### Asynchronous navigation
Clicking an element could trigger asynchronous processing before initiating the navigation. In these cases, it is
recommended to explicitly call [`method: Page.waitForNavigation`]. For example:
* Navigation is triggered from a `setTimeout`
* Page waits for network requests before navigation
```js
// Start waiting for navigation before clicking. Note no await.
const navigationPromise = page.waitForNavigation();
await page.getByText('Navigate after timeout').click();
await navigationPromise;
```
```java
// Using waitForNavigation with a callback prevents a race condition
// between clicking and waiting for a navigation.
page.waitForNavigation(() -> { // Waits for the next navigation
page.getByText("Navigate after timeout").click(); // Triggers a navigation after a timeout
});
```
```python async
# Waits for the next navigation. Using Python context manager
# prevents a race condition between clicking and waiting for a navigation.
async with page.expect_navigation():
# Triggers a navigation after a timeout
await page.get_by_text("Navigate after timeout").click()
```
```python sync
# Waits for the next navigation. Using Python context manager
# prevents a race condition between clicking and waiting for a navigation.
with page.expect_navigation():
# Triggers a navigation after a timeout
page.get_by_text("Navigate after timeout").click()
```
```csharp
// Using waitForNavigation with a callback prevents a race condition
// between clicking and waiting for a navigation.
await page.RunAndWaitForNavigationAsync(async () =>
{
// Triggers a navigation after a timeout
await page.GetByText("Navigate after timeout").ClickAsync();
});
```
### Multiple navigations
Clicking an element could trigger multiple navigations. In these cases, it is recommended to explicitly
[`method: Page.waitForNavigation`] to a specific url. For example:
* Client-side redirects issued after the `load` event
* Multiple pushes to history state
```js
// Start waiting for navigation before clicking. Note no await.
const navigationPromise = page.waitForNavigation({ url: '**/login' });
// This action triggers the navigation with a script redirect.
await page.getByText('Click me').click();
await navigationPromise;
```
```java
// Running action in the callback of waitForNavigation prevents a race
// condition between clicking and waiting for a navigation.
page.waitForNavigation(new Page.WaitForNavigationOptions().setUrl("**/login"), () -> {
page.getByText("Click me").click(); // Triggers a navigation with a script redirect
});
```
```python async
# Using Python context manager prevents a race condition
# between clicking and waiting for a navigation.
async with page.expect_navigation(url="**/login"):
# Triggers a navigation with a script redirect
await page.get_by_text("Click me").click()
```
```python sync
# Using Python context manager prevents a race condition
# between clicking and waiting for a navigation.
with page.expect_navigation(url="**/login"):
# Triggers a navigation with a script redirect
page.get_by_text("Click me").click()
```
```csharp
// Running action in the callback of waitForNavigation prevents a race
// condition between clicking and waiting for a navigation.
await page.RunAndWaitForNavigationAsync(async () =>
{
// Triggers a navigation with a script redirect.
await page.GetByText("Click me").ClickAsync();
}, new()
{
UrlString = "**/login"
});
```
### Loading a popup
When popup is opened, explicitly calling [`method: Page.waitForLoadState`] ensures that popup is loaded to the desired state.
```js
// Start waiting for popup before clicking. Note no await.
const popupPromise = page.waitForEvent('popup');
await page.getByText('Open popup').click();
const popup = await popupPromise;
// Wait for the popup to load.
await popup.waitForLoadState('load');
```
```java
Page popup = page.waitForPopup(() -> {
page.getByText("Open popup").click(); // Opens popup
});
popup.waitForLoadState(LoadState.LOAD);
```
```python async
async with page.expect_popup() as popup_info:
await page.get_by_text("Open popup").click() # Opens popup
popup = await popup_info.value
await popup.wait_for_load_state("load")
```
```python sync
with page.expect_popup() as popup_info:
page.get_by_text("Open popup").click() # Opens popup
popup = popup_info.value
popup.wait_for_load_state("load")
```
```csharp
var popup = await page.RunAndWaitForPopupAsync(async () =>
{
await page.GetByText("Open popup").ClickAsync(); // Opens popup
});
popup.WaitForLoadStateAsync(LoadState.Load);
```
## Advanced patterns
For pages that have complicated loading patterns, [`method: Page.waitForFunction`] 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();
```
```java
page.navigate("http://example.com");
page.waitForFunction("() => window.amILoadedYet()");
// Ready to take a screenshot, according to the page itself.
page.screenshot();
```
```python async
await page.goto("http://example.com")
await page.wait_for_function("() => window.amILoadedYet()")
# Ready to take a screenshot, according to the page itself.
await page.screenshot()
```
```python sync
page.goto("http://example.com")
page.wait_for_function("() => window.amILoadedYet()")
# Ready to take a screenshot, according to the page itself.
page.screenshot()
```
```csharp
await page.GotoAsync("http://example.com");
await page.WaitForFunctionAsync("() => window.amILoadedYet()");
// Ready to take a screenshot, according to the page itself.
await page.ScreenshotAsync();
```

View File

@ -9,11 +9,6 @@ Playwright Test has multiple configurable timeouts for various tasks.
|:----------|:----------------|:--------------------------------|
|Test timeout|30000 ms|Timeout for each test, includes test, hooks and fixtures:<br/><span style={{textTransform:'uppercase',fontSize:'smaller',fontWeight:'bold',opacity:'0.6'}}>Set default</span><br/><code>{`config = { timeout: 60000 }`}</code><br/><span style={{textTransform: 'uppercase',fontSize: 'smaller', fontWeight: 'bold', opacity: '0.6'}}>Override</span><br/>`test.setTimeout(120000)` |
|Expect timeout|5000 ms|Timeout for each assertion:<br/><span style={{textTransform:'uppercase',fontSize:'smaller',fontWeight:'bold',opacity:'0.6'}}>Set default</span><br/><code>{`config = { expect: { timeout: 10000 } }`}</code><br/><span style={{textTransform: 'uppercase',fontSize: 'smaller', fontWeight: 'bold', opacity: '0.6'}}>Override</span><br/>`expect(locator).toBeVisible({ timeout: 10000 })` |
|Action timeout| no timeout |Timeout for each action:<br/><span style={{textTransform:'uppercase',fontSize:'smaller',fontWeight:'bold',opacity:'0.6'}}>Set default</span><br/><code>{`config = { use: { actionTimeout: 10000 } }`}</code><br/><span style={{textTransform: 'uppercase',fontSize: 'smaller', fontWeight: 'bold', opacity: '0.6'}}>Override</span><br/>`locator.click({ timeout: 10000 })` |
|Navigation timeout| no timeout |Timeout for each navigation action:<br/><span style={{textTransform:'uppercase',fontSize:'smaller',fontWeight:'bold',opacity:'0.6'}}>Set default</span><br/><code>{`config = { use: { navigationTimeout: 30000 } }`}</code><br/><span style={{textTransform: 'uppercase',fontSize: 'smaller', fontWeight: 'bold', opacity: '0.6'}}>Override</span><br/>`page.goto('/', { timeout: 30000 })` |
|Global timeout|no timeout |Global timeout for the whole test run:<br/><span style={{textTransform:'uppercase',fontSize:'smaller',fontWeight:'bold',opacity:'0.6'}}>Set in config</span><br/>`config = { globalTimeout: 60*60*1000 }`<br/> |
|`beforeAll`/`afterAll` timeout|30000 ms|Timeout for the hook:<br/><span style={{textTransform:'uppercase',fontSize:'smaller',fontWeight:'bold',opacity:'0.6'}}>Set in hook</span><br/>`test.setTimeout(60000)`<br/> |
|Fixture timeout|no timeout |Timeout for an individual fixture:<br/><span style={{textTransform:'uppercase',fontSize:'smaller',fontWeight:'bold',opacity:'0.6'}}>Set in fixture</span><br/>`{ scope: 'test', timeout: 30000 }`<br/> |
## Test timeout
@ -115,7 +110,44 @@ export default defineConfig({
});
```
API reference: [`property: TestConfig.expect`].
## Global timeout
Playwright Test supports a timeout for the whole test run. This prevents excess resource usage when everything went wrong. There is no default global timeout, but you can set a reasonable one in the config, for example one hour. Global timeout produces the following error:
```
Running 1000 tests using 10 workers
514 skipped
486 passed
Timed out waiting 3600s for the entire test run
```
You can set global timeout in the config.
```js
// playwright.config.ts
import { defineConfig } from '@playwright/test';
export default defineConfig({
globalTimeout: 60 * 60 * 1000,
});
```
API reference: [`property: TestConfig.globalTimeout`].
## Advanced: low level timeouts
These are the low-level timeouts that are pre-configured by the test runner, you should not need to change these.
If you happen to be in this section because your test are flaky, it is very likely that you should be looking for the solution elsewhere.
|Timeout |Default |Description |
|:----------|:----------------|:--------------------------------|
|Action timeout| no timeout |Timeout for each action:<br/><span style={{textTransform:'uppercase',fontSize:'smaller',fontWeight:'bold',opacity:'0.6'}}>Set default</span><br/><code>{`config = { use: { actionTimeout: 10000 } }`}</code><br/><span style={{textTransform: 'uppercase',fontSize: 'smaller', fontWeight: 'bold', opacity: '0.6'}}>Override</span><br/>`locator.click({ timeout: 10000 })` |
|Navigation timeout| no timeout |Timeout for each navigation action:<br/><span style={{textTransform:'uppercase',fontSize:'smaller',fontWeight:'bold',opacity:'0.6'}}>Set default</span><br/><code>{`config = { use: { navigationTimeout: 30000 } }`}</code><br/><span style={{textTransform: 'uppercase',fontSize: 'smaller', fontWeight: 'bold', opacity: '0.6'}}>Override</span><br/>`page.goto('/', { timeout: 30000 })` |
|Global timeout|no timeout |Global timeout for the whole test run:<br/><span style={{textTransform:'uppercase',fontSize:'smaller',fontWeight:'bold',opacity:'0.6'}}>Set in config</span><br/>`config = { globalTimeout: 60*60*1000 }`<br/> |
|`beforeAll`/`afterAll` timeout|30000 ms|Timeout for the hook:<br/><span style={{textTransform:'uppercase',fontSize:'smaller',fontWeight:'bold',opacity:'0.6'}}>Set in hook</span><br/>`test.setTimeout(60000)`<br/> |
|Fixture timeout|no timeout |Timeout for an individual fixture:<br/><span style={{textTransform:'uppercase',fontSize:'smaller',fontWeight:'bold',opacity:'0.6'}}>Set in fixture</span><br/>`{ scope: 'test', timeout: 30000 }`<br/> |
### Set timeout for a single assertion
@ -126,22 +158,6 @@ test('basic test', async ({ page }) => {
await expect(page.getByRole('button')).toHaveText('Sign in', { timeout: 10000 });
});
```
## Action and navigation timeouts
Test usually performs some actions by calling Playwright APIs, for example `locator.click()`. These actions do not have a timeout by default, but you can set one. Action that timed out produces the following error:
```
example.spec.ts:3:1 basic test ===========================
locator.click: Timeout 1000ms exceeded.
=========================== logs ===========================
waiting for "locator('button')"
============================================================
```
Playwright also allows to set a separate timeout for navigation actions like `page.goto()` because loading a page is usually slower.
### Set action and navigation timeouts in the config
```js title="playwright.config.ts"
@ -168,30 +184,6 @@ test('basic test', async ({ page }) => {
});
```
## Global timeout
Playwright Test supports a timeout for the whole test run. This prevents excess resource usage when everything went wrong. There is no default global timeout, but you can set a reasonable one in the config, for example one hour. Global timeout produces the following error:
```
Running 1000 tests using 10 workers
514 skipped
486 passed
Timed out waiting 3600s for the entire test run
```
You can set global timeout in the config.
```js title="playwright.config.ts"
import { defineConfig } from '@playwright/test';
export default defineConfig({
globalTimeout: 60 * 60 * 1000,
});
```
API reference: [`property: TestConfig.globalTimeout`].
## Fixture timeout
By default, [fixture](./test-fixtures) shares timeout with the test. However, for slow fixtures, especially [worker-scoped](./test-fixtures#worker-scoped-fixtures) ones, it is convenient to have a separate timeout. This way you can keep the overall test timeout small, and give the slow fixture more time.

View File

@ -276,6 +276,12 @@ export interface Page {
* await page.addInitScript({ path: './preload.js' });
* ```
*
* ```js
* await page.addInitScript(mock => {
* window.mock = mock;
* }, mock);
* ```
*
* **NOTE** The order of evaluation of multiple scripts installed via
* [browserContext.addInitScript(script[, arg])](https://playwright.dev/docs/api/class-browsercontext#browser-context-add-init-script)
* and [page.addInitScript(script[, arg])](https://playwright.dev/docs/api/class-page#page-add-init-script) is not
@ -7289,6 +7295,9 @@ export interface Frame {
}): Promise<null|Response>;
/**
* **NOTE** Never wait for timeout in production. Tests that wait for time are inherently flaky. Use [Locator] actions and web
* assertions that wait automatically.
*
* Waits for the given `timeout` in milliseconds.
*
* Note that `frame.waitForTimeout()` should only be used for debugging. Tests using the timer in production are going