diff --git a/docs/src/api/class-frame.md b/docs/src/api/class-frame.md
index 33f8a2df5e..097c6cc0fd 100644
--- a/docs/src/api/class-frame.md
+++ b/docs/src/api/class-frame.md
@@ -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.
diff --git a/docs/src/api/class-page.md b/docs/src/api/class-page.md
index cffd8be27a..b35458eac0 100644
--- a/docs/src/api/class-page.md
+++ b/docs/src/api/class-page.md
@@ -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"));
diff --git a/docs/src/navigations.md b/docs/src/navigations.md
index 1f941f42a4..a833d5bd68 100644
--- a/docs/src/navigations.md
+++ b/docs/src/navigations.md
@@ -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();
-```
diff --git a/docs/src/test-timeouts-js.md b/docs/src/test-timeouts-js.md
index 4e8682c071..88f71bbb4e 100644
--- a/docs/src/test-timeouts-js.md
+++ b/docs/src/test-timeouts-js.md
@@ -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:
Set default
{`config = { timeout: 60000 }`}
Override
`test.setTimeout(120000)` |
|Expect timeout|5000 ms|Timeout for each assertion:
Set default
{`config = { expect: { timeout: 10000 } }`}
Override
`expect(locator).toBeVisible({ timeout: 10000 })` |
-|Action timeout| no timeout |Timeout for each action:
Set default
{`config = { use: { actionTimeout: 10000 } }`}
Override
`locator.click({ timeout: 10000 })` |
-|Navigation timeout| no timeout |Timeout for each navigation action:
Set default
{`config = { use: { navigationTimeout: 30000 } }`}
Override
`page.goto('/', { timeout: 30000 })` |
-|Global timeout|no timeout |Global timeout for the whole test run:
Set in config
`config = { globalTimeout: 60*60*1000 }`
|
-|`beforeAll`/`afterAll` timeout|30000 ms|Timeout for the hook:
Set in hook
`test.setTimeout(60000)`
|
-|Fixture timeout|no timeout |Timeout for an individual fixture:
Set in fixture
`{ scope: 'test', timeout: 30000 }`
|
## 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:
Set default
{`config = { use: { actionTimeout: 10000 } }`}
Override
`locator.click({ timeout: 10000 })` |
+|Navigation timeout| no timeout |Timeout for each navigation action:
Set default
{`config = { use: { navigationTimeout: 30000 } }`}
Override
`page.goto('/', { timeout: 30000 })` |
+|Global timeout|no timeout |Global timeout for the whole test run:
Set in config
`config = { globalTimeout: 60*60*1000 }`
|
+|`beforeAll`/`afterAll` timeout|30000 ms|Timeout for the hook:
Set in hook
`test.setTimeout(60000)`
|
+|Fixture timeout|no timeout |Timeout for an individual fixture:
Set in fixture
`{ scope: 'test', timeout: 30000 }`
|
+
### 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.
diff --git a/packages/playwright-core/types/types.d.ts b/packages/playwright-core/types/types.d.ts
index 7db8d1bdb1..2a7622eb96 100644
--- a/packages/playwright-core/types/types.d.ts
+++ b/packages/playwright-core/types/types.d.ts
@@ -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;
/**
+ * **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