diff --git a/docs/src/api/class-accessibility.md b/docs/src/api/class-accessibility.md
index ff6b0958b8..8359c5f13c 100644
--- a/docs/src/api/class-accessibility.md
+++ b/docs/src/api/class-accessibility.md
@@ -7,10 +7,10 @@ assistive technology such as [screen readers](https://en.wikipedia.org/wiki/Scre
Accessibility is a very platform-specific thing. On different platforms, there are different screen readers that might
have wildly different output.
-Blink - Chromium's rendering engine - has a concept of "accessibility tree", which is then translated into different
-platform-specific APIs. Accessibility namespace gives users access to the Blink Accessibility Tree.
+Rendering engines of Chromium, Firefox and Webkit have a concept of "accessibility tree", which is then translated into different
+platform-specific APIs. Accessibility namespace gives access to this Accessibility Tree.
-Most of the accessibility tree gets filtered out when converting from Blink AX Tree to Platform-specific AX-Tree or by
+Most of the accessibility tree gets filtered out when converting from internal browser AX Tree to Platform-specific AX-Tree or by
assistive technologies themselves. By default, Playwright tries to approximate this filtering, exposing only the
"interesting" nodes of the tree.
@@ -98,7 +98,7 @@ def find_focused_node(node):
snapshot = await page.accessibility.snapshot()
node = find_focused_node(snapshot)
if node:
- print(node["name"])
+ print(node["name"])
```
```python sync
@@ -113,7 +113,7 @@ def find_focused_node(node):
snapshot = page.accessibility.snapshot()
node = find_focused_node(snapshot)
if node:
- print(node["name"])
+ print(node["name"])
```
### option: Accessibility.snapshot.interestingOnly
diff --git a/docs/src/api/class-browser.md b/docs/src/api/class-browser.md
index 7a4772d101..8f5ebf5a3f 100644
--- a/docs/src/api/class-browser.md
+++ b/docs/src/api/class-browser.md
@@ -1,5 +1,5 @@
# class: Browser
-* extends: [EventEmitter](https://nodejs.org/api/events.html#events_class_eventemitter)
+* extends: [EventEmitter]
A Browser is created via [`method: BrowserType.launch`]. An example of using a [Browser] to create a [Page]:
diff --git a/docs/src/api/class-browsercontext.md b/docs/src/api/class-browsercontext.md
index 0ae739a63f..293bf2bf73 100644
--- a/docs/src/api/class-browsercontext.md
+++ b/docs/src/api/class-browsercontext.md
@@ -1,5 +1,5 @@
# class: BrowserContext
-* extends: [EventEmitter](https://nodejs.org/api/events.html#events_class_eventemitter)
+* extends: [EventEmitter]
BrowserContexts provide a way to operate multiple independent browser sessions.
@@ -567,7 +567,7 @@ await browser.close();
```python async
context = await browser.new_context()
page = await context.new_page()
-await context.route(r"(\.png$)|(\.jpg$)", lambda route => route.abort())
+await context.route(r"(\.png$)|(\.jpg$)", lambda route: route.abort())
page = await context.new_page()
await page.goto("https://example.com")
await browser.close()
@@ -576,7 +576,7 @@ await browser.close()
```python sync
context = browser.new_context()
page = context.new_page()
-context.route(r"(\.png$)|(\.jpg$)", lambda route => route.abort())
+context.route(r"(\.png$)|(\.jpg$)", lambda route: route.abort())
page = await context.new_page()
page = context.new_page()
page.goto("https://example.com")
diff --git a/docs/src/api/class-cdpsession.md b/docs/src/api/class-cdpsession.md
index 658c4f4b07..84b1a323e8 100644
--- a/docs/src/api/class-cdpsession.md
+++ b/docs/src/api/class-cdpsession.md
@@ -1,5 +1,5 @@
# class: CDPSession
-* extends: [EventEmitter](https://nodejs.org/api/events.html#events_class_eventemitter)
+* extends: [EventEmitter]
The `CDPSession` instances are used to talk raw Chrome Devtools Protocol:
* protocol methods can be called with `session.send` method.
diff --git a/docs/src/api/class-page.md b/docs/src/api/class-page.md
index 459612f106..3ec3e0772c 100644
--- a/docs/src/api/class-page.md
+++ b/docs/src/api/class-page.md
@@ -1,5 +1,5 @@
# class: Page
-* extends: [EventEmitter](https://nodejs.org/api/events.html#events_class_eventemitter)
+* extends: [EventEmitter]
Page provides methods to interact with a single tab in a [Browser], or an
[extension background page](https://developer.chrome.com/extensions/background_pages) in Chromium. One [Browser]
diff --git a/docs/src/api/python.md b/docs/src/api/python.md
index 1557735ef1..164bcb54b5 100644
--- a/docs/src/api/python.md
+++ b/docs/src/api/python.md
@@ -1,3 +1,22 @@
+## async method: Playwright.start
+* langs: python
+
+Starts a new instance of Playwright without using the Python context manager. This is useful in REPL applications. Requires [`method: Playwright.stop`] to be called to cleanup resources.
+
+```py
+>>> from playwright.sync_api import sync_playwright
+
+>>> playwright = sync_playwright().start()
+
+>>> browser = playwright.chromium.launch()
+>>> page = browser.newPage()
+>>> page.goto("http://whatsmyuseragent.org/")
+>>> page.screenshot(path="example.png")
+>>> browser.close()
+
+>>> playwright.stop()
+```
+
## async method: Playwright.stop
* langs: python
@@ -202,13 +221,13 @@ from a `setTimeout`. Consider this example:
```python async
async with page.expect_navigation():
- await page.click("a.delayed-navigation") # Clicking the link will indirectly cause a navigation
+ await page.click("a.delayed-navigation") # Clicking the link will indirectly cause a navigation
# Context manager waited for the navigation to happen.
```
```python sync
with page.expect_navigation():
- page.click("a.delayed-navigation") # Clicking the link will indirectly cause a navigation
+ page.click("a.delayed-navigation") # Clicking the link will indirectly cause a navigation
# Context manager waited for the navigation to happen.
```
@@ -236,13 +255,13 @@ from a `setTimeout`. Consider this example:
```python async
async with frame.expect_navigation():
- await frame.click("a.delayed-navigation") # Clicking the link will indirectly cause a navigation
+ await frame.click("a.delayed-navigation") # Clicking the link will indirectly cause a navigation
# Context manager waited for the navigation to happen.
```
```python sync
with frame.expect_navigation():
- frame.click("a.delayed-navigation") # Clicking the link will indirectly cause a navigation
+ frame.click("a.delayed-navigation") # Clicking the link will indirectly cause a navigation
# Context manager waited for the navigation to happen.
```
diff --git a/docs/src/assertions.md b/docs/src/assertions.md
index bd88e6e558..7393879850 100644
--- a/docs/src/assertions.md
+++ b/docs/src/assertions.md
@@ -4,7 +4,7 @@ title: "Assertions"
---
The Playwright API can be used to read element contents and properties for test assertions. These values are fetched from the browser page and asserted in
-Node.js.
+your script.
diff --git a/docs/src/emulation.md b/docs/src/emulation.md
index 1216c2ab79..3e9ebf1dc9 100644
--- a/docs/src/emulation.md
+++ b/docs/src/emulation.md
@@ -4,12 +4,13 @@ title: "Device and environment emulation"
---
Playwright allows overriding various parameters of the device where the browser is running:
- - viewport size, device scale factor, touch support
- - locale, timezone
- - color scheme
- - geolocation
+- viewport size, device scale factor, touch support
+- locale, timezone
+- color scheme
+- geolocation
-Most of these parameters are configured during the browser context construction, but some of them such as viewport size can be changed for individual pages.
+Most of these parameters are configured during the browser context construction, but some of them such as viewport size
+can be changed for individual pages.
@@ -17,7 +18,8 @@ Most of these parameters are configured during the browser context construction,
## Devices
-Playwright comes with a registry of device parameters for selected mobile devices. It can be used to simulate browser behavior on a mobile device:
+Playwright comes with a registry of device parameters for selected mobile devices. It can be used to simulate browser
+behavior on a mobile device:
```js
const { chromium, devices } = require('playwright');
@@ -33,32 +35,36 @@ const context = await browser.newContext({
import asyncio
from playwright.async_api import async_playwright
-async def main():
- async with async_playwright() as p:
- pixel_2 = p.devices['Pixel 2']
- browser = await p.webkit.launch(headless=False)
- context = await browser.new_context(
- **pixel_2,
- )
+async def run(playwright):
+ pixel_2 = playwright.devices['Pixel 2']
+ browser = await playwright.webkit.launch(headless=False)
+ context = await browser.new_context(
+ **pixel_2,
+ )
-asyncio.get_event_loop().run_until_complete(main())
+async def main():
+ async with async_playwright() as playwright:
+ await run(playwright)
+asyncio.run(main())
```
```python sync
from playwright.sync_api import sync_playwright
-with sync_playwright() as p:
- pixel_2 = p.devices['Pixel 2']
- browser = p.webkit.launch(headless=False)
+def run(playwright):
+ pixel_2 = playwright.devices['Pixel 2']
+ browser = playwright.webkit.launch(headless=False)
context = browser.new_context(
**pixel_2,
)
+
+with sync_playwright() as playwright:
+ run(playwright)
```
All pages created in the context above will share the same device parameters.
#### API reference
-
- [`property: Playwright.devices`]
- [`method: Browser.newContext`]
@@ -87,7 +93,6 @@ context = browser.new_context(
```
#### API reference
-
- [`method: Browser.newContext`]
@@ -141,11 +146,9 @@ page.set_viewport_size(width=1600, height=1200)
context = browser.new_context(
viewport={ 'width': 2560, 'height': 1440 },
device_scale_factor=2,
-
```
#### API reference
-
- [`method: Browser.newContext`]
- [`method: Page.setViewportSize`]
@@ -178,7 +181,6 @@ context = browser.new_context(
```
#### API reference
-
- [`method: Browser.newContext`]
@@ -248,7 +250,6 @@ context.clear_permissions()
```
#### API reference
-
- [`method: Browser.newContext`]
- [`method: BrowserContext.grantPermissions`]
- [`method: BrowserContext.clearPermissions`]
@@ -256,6 +257,7 @@ context.clear_permissions()
## Geolocation
+
Create a context with `"geolocation"` permissions granted:
```js
@@ -296,7 +298,6 @@ context.set_geolocation({"longitude": 29.979097, "latitude": 31.134256})
**Note** you can only change geolocation for all pages in the context.
#### API reference
-
- [`method: Browser.newContext`]
- [`method: BrowserContext.setGeolocation`]
@@ -304,8 +305,7 @@ context.set_geolocation({"longitude": 29.979097, "latitude": 31.134256})
## Color scheme and media
-Create a context with dark or light mode. Pages created in this context will
-follow this color scheme preference.
+Create a context with dark or light mode. Pages created in this context will follow this color scheme preference.
```js
// Create context with dark mode
@@ -362,6 +362,5 @@ page.emulate_media(media='print')
```
#### API reference
-
- [`method: Browser.newContext`]
-- [`method: Page.emulateMedia`]
+- [`method: Page.emulateMedia`]
\ No newline at end of file
diff --git a/docs/src/extensibility.md b/docs/src/extensibility.md
index 5372921381..1963d17ed6 100644
--- a/docs/src/extensibility.md
+++ b/docs/src/extensibility.md
@@ -10,14 +10,20 @@ title: "Extensibility"
Playwright supports custom selector engines, registered with [`method: Selectors.register`].
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.
+- `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, register 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.
+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,
+register 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 = () => ({
@@ -44,3 +50,59 @@ 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);
```
+
+```python async
+tag_selector = """
+ // Must evaluate to a selector engine instance.
+ {
+ // 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 playwright.selectors.register("tag", tag_selector)
+
+# now we can use "tag=" selectors.
+button = await page.query_selector("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.
+button_count = await page.eval_on_selector_all("tag=button", buttons => buttons.length)
+```
+
+```python sync
+tag_selector = """
+ // Must evaluate to a selector engine instance.
+ {
+ // 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=".
+playwright.selectors.register("tag", tag_selector)
+
+# now we can use "tag=" selectors.
+button = page.query_selector("tag=button")
+
+# we can combine it with other selector engines using `>>` combinator.
+page.click("tag=div >> span >> "click me"")
+
+# we can use it in any methods supporting selectors.
+button_count = page.eval_on_selector_all("tag=button", buttons => buttons.length)
+```
diff --git a/docs/src/input.md b/docs/src/input.md
index e81ca6cc0f..2128aa8a54 100644
--- a/docs/src/input.md
+++ b/docs/src/input.md
@@ -439,7 +439,6 @@ await page.setInputFiles('input#upload', {
```
```python async
-from playwright.async_api import FilePayload
# Select one file
await page.set_input_files('input#upload', 'myfile.pdf')
@@ -452,12 +451,13 @@ await page.set_input_files('input#upload', [])
# Upload buffer from memory
await page.set_input_files(
"input#upload",
- files=[FilePayload("test.txt", "text/plain", b"this is a test")],
+ files=[
+ {"name": "test.txt", "mimeType": "text/plain", "buffer": b"this is a test"}
+ ],
)
```
```python sync
-from playwright.sync_api import FilePayload
# Select one file
page.set_input_files('input#upload', 'myfile.pdf')
@@ -470,7 +470,9 @@ page.set_input_files('input#upload', [])
# Upload buffer from memory
page.set_input_files(
"input#upload",
- files=[FilePayload("test.txt", "text/plain", b"this is a test")],
+ files=[
+ {"name": "test.txt", "mimeType": "text/plain", "buffer": b"this is a test"}
+ ],
)
```
diff --git a/docs/src/multi-pages.md b/docs/src/multi-pages.md
index ac4347dba2..7a0a462c02 100644
--- a/docs/src/multi-pages.md
+++ b/docs/src/multi-pages.md
@@ -3,16 +3,14 @@ id: multi-pages
title: "Multi-page scenarios"
---
-Playwright can automate scenarios that span multiple browser contexts or multiple
-tabs in a browser window.
+Playwright can automate scenarios that span multiple browser contexts or multiple tabs in a browser window.
## Multiple contexts
-[Browser contexts](./core-concepts.md#browser-contexts) are isolated environments
-on a single browser instance. Playwright can create multiple browser contexts
-within a single scenario. This is useful when you want to test for
+[Browser contexts](./core-concepts.md#browser-contexts) are isolated environments on a single browser instance.
+Playwright can create multiple browser contexts within a single scenario. This is useful when you want to test for
multi-user functionality, like chat.
```js
@@ -30,8 +28,50 @@ await userContext.addCookies(userCookies);
await adminContext.addCookies(adminCookies);
```
-#### API reference
+```python async
+import asyncio
+from playwright.async_api import async_playwright
+async def run(playwright):
+ # create a chromium browser instance
+ chromium = playwright.chromium
+ browser = await chromium.launch()
+
+ # create two isolated browser contexts
+ user_context = await browser.new_context()
+ admin_context = await browser.new_context()
+
+ # load user and admin cookies
+ await user_context.add_cookies(user_cookies)
+ await admin_context.add_cookies(admin_cookies)
+
+async def main():
+ async with async_playwright() as playwright:
+ await run(playwright)
+asyncio.run(main())
+```
+
+```python sync
+from playwright.sync_api import sync_playwright
+
+def run(playwright):
+ # create a chromium browser instance
+ chromium = playwright.chromium
+ browser = chromium.launch()
+
+ # create two isolated browser contexts
+ user_context = browser.new_context()
+ admin_context = browser.new_context()
+
+ # load user and admin cookies
+ user_context.add_cookies(user_cookies)
+ admin_context.add_cookies(admin_cookies)
+
+with sync_playwright() as playwright:
+ run(playwright)
+```
+
+#### API reference
- [BrowserContext]
- [`method: Browser.newContext`]
- [`method: BrowserContext.addCookies`]
@@ -39,11 +79,9 @@ await adminContext.addCookies(adminCookies);
## Multiple pages
Each browser context can host multiple pages (tabs).
-
-* Each page behaves like a focused, active page. Bringing the page to front
- is not required.
-* Pages inside a context respect context-level emulation, like viewport sizes,
- custom network routes or browser locale.
+* Each page behaves like a focused, active page. Bringing the page to front is not required.
+* Pages inside a context respect context-level emulation, like viewport sizes, custom network routes or browser
+ locale.
```js
// Create two pages
@@ -54,17 +92,33 @@ const pageTwo = await context.newPage();
const allPages = context.pages();
```
-#### API reference
+```python async
+# create two pages
+page_one = await context.new_page()
+page_two = await context.new_page()
+# get pages of a brower context
+all_pages = context.pages()
+```
+
+```python sync
+# create two pages
+page_one = context.new_page()
+page_two = context.new_page()
+
+# get pages of a brower context
+all_pages = context.pages()
+```
+
+#### API reference
- [Page]
- [`method: BrowserContext.newPage`]
- [`method: BrowserContext.pages`]
## Handling new pages
-The `page` event on browser contexts can be used to get new pages that are
-created in the context. This can be used to handle new pages opened by
-`target="_blank"` links.
+The `page` event on browser contexts can be used to get new pages that are created in the context. This can be used to
+handle new pages opened by `target="_blank"` links.
```js
// Get page after a specific action (e.g. clicking a link)
@@ -76,27 +130,62 @@ await newPage.waitForLoadState();
console.log(await newPage.title());
```
+```python async
+# Get page after a specific action (e.g. clicking a link)
+async with context.expect_page() as new_page_info:
+ await page.click('a[target="_blank"]') # Opens a new tab
+new_page = await new_page_info.value
+
+await new_page.wait_for_load_state()
+print(await new_page.title())
+```
+
+```python sync
+# Get page after a specific action (e.g. clicking a link)
+with context.expect_page() as new_page_info:
+ page.click('a[target="_blank"]') # Opens a new tab
+new_page = new_page_info.value
+
+new_page.wait_for_load_state()
+print(new_page.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();
- await page.title();
+ console.log(await page.title());
})
```
-#### API reference
+```python async
+# Get all new pages (including popups) in the context
+async def handle_page(page):
+ await page.wait_for_load_state()
+ print(await page.title())
+context.on("page", handle_page)
+```
+
+```python sync
+# Get all new pages (including popups) in the context
+def handle_page(page):
+ page.wait_for_load_state()
+ print(page.title())
+
+context.on("page", handle_page)
+```
+
+#### API reference
- [`event: BrowserContext.page`]
## Handling popups
-If the page opens a pop-up, you can get a reference to it by listening to the
-`popup` event on the page.
+If the page opens a pop-up, you can get a reference to it by listening to the `popup` event on the page.
-This event is emitted in addition to the `browserContext.on('page')` event, but
-only for popups relevant to this page.
+This event is emitted in addition to the `browserContext.on('page')` event, but only for popups relevant to this page.
```js
// Get popup after a specific action (e.g., click)
@@ -105,7 +194,27 @@ const [popup] = await Promise.all([
page.click('#open')
]);
await popup.waitForLoadState();
-await popup.title();
+console.log(await popup.title());
+```
+
+```python async
+# Get popup after a specific action (e.g., click)
+async with page.expect_popup() as popup_info:
+ await page.click("#open")
+popup = await popup_info.value
+
+await popup.wait_for_load_state()
+print(await popup.title())
+```
+
+```python sync
+# Get popup after a specific action (e.g., click)
+with page.expect_popup() as popup_info:
+ page.click("#open")
+popup = popup_info.value
+
+popup.wait_for_load_state()
+print(popup.title())
```
If the action that triggers the popup is unknown, the following pattern can be used.
@@ -118,6 +227,23 @@ page.on('popup', async popup => {
})
```
-#### API reference
+```python async
+# Get all popups when they open
+async def handle_popup(popup):
+ await popup.wait_for_load_state()
+ print(await popup.title())
-- [`event: Page.popup`]
+page.on("popup", handle_popup)
+```
+
+```python sync
+# Get all popups when they open
+def handle_popup(popup):
+ popup.wait_for_load_state()
+ print(popup.title())
+
+page.on("popup", handle_popup)
+```
+
+#### API reference
+- [`event: Page.popup`]
\ No newline at end of file
diff --git a/docs/src/navigations.md b/docs/src/navigations.md
index 86f40b6b7d..9fc233e844 100644
--- a/docs/src/navigations.md
+++ b/docs/src/navigations.md
@@ -3,17 +3,22 @@ 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 page interactions. This guide covers common scenarios
+to wait for page navigations and loading to complete.
## 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:
+**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:
- [`method: Page.url`] is set to the new url
- document content is loaded over network and parsed
- [`event: Page.domcontentloaded`] event is fired
@@ -23,17 +28,31 @@ Playwright splits the process of showing a new document in a page into **navigat
- `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.
+
+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');
```
+```python async
+# Navigate the page
+await page.goto("https://example.com")
+```
+
+```python sync
+# Navigate the page
+page.goto("https://example.com")
+```
+
### Custom wait
+
Override the default behavior to wait until a specific event, like `networkidle`.
```js
@@ -41,8 +60,20 @@ Override the default behavior to wait until a specific event, like `networkidle`
await page.goto('https://example.com', { waitUntil: 'networkidle' });
```
+```python async
+# Navigate and wait until network is idle
+await page.goto("https://example.com", wait_until="networkidle")
+```
+
+```python sync
+# Navigate and wait until network is idle
+page.goto("https://example.com", wait_until="networkidle")
+```
+
### Wait for element
-In lazy-loaded pages, it can be useful to wait until an element is visible with [`method: Page.waitForSelector`]. Alternatively, page interactions like [`method: Page.click`] auto-wait for elements.
+
+In lazy-loaded pages, it can be useful to wait until an element is visible with [`method: Page.waitForSelector`].
+Alternatively, page interactions like [`method: Page.click`] auto-wait for elements.
```js
// Navigate and wait for element
@@ -55,6 +86,28 @@ await page.goto('https://example.com');
await page.click('text=Example Domain');
```
+```python async
+# Navigate and wait for element
+await page.goto("https://example.com")
+await page.wait_for_selector("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")
+```
+
+```python sync
+# Navigate and wait for element
+page.goto("https://example.com")
+page.wait_for_selector("text=example domain")
+
+# Navigate and click element
+# Click will auto-wait for the element
+page.goto("https://example.com")
+page.click("text=example domain")
+```
+
#### API reference
- [`method: Page.goto`]
- [`method: Page.reload`]
@@ -62,10 +115,13 @@ await page.click('text=Example Domain');
- [`method: Page.goForward`]
## Scenarios initiated by page interaction
-In the scenarios below, `page.click` initiates a navigation and then waits for the navigation to complete.
+
+In the scenarios below, [`method: 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.
+
+By default, [`method: 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
@@ -74,7 +130,24 @@ await page.click('text=Login');
await page.fill('#username', 'John Doe');
```
+```python async
+# 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")
+```
+
+```python sync
+# Click will auto-wait for navigation to complete
+page.click("text=Login")
+
+# Fill will auto-wait for element on navigated page
+page.fill("#username", "John Doe")
+```
+
### Custom wait
+
`page.click` can be combined with [`method: Page.waitForLoadState`] to wait for a loading event.
```js
@@ -82,51 +155,125 @@ await page.click('button'); // Click triggers navigation
await page.waitForLoadState('networkidle'); // This resolves after 'networkidle'
```
+```python async
+await page.click("button"); # Click triggers navigation
+await page.wait_for_load_state("networkidle"); # This waits for the "networkidle"
+```
+
+```python sync
+page.click("button"); # Click triggers navigation
+page.wait_for_load_state("networkidle"); # This waits for the "networkidle"
+```
+
### Wait for element
-In lazy-loaded pages, it can be useful to wait until an element is visible with [`method: Page.waitForSelector`]. Alternatively, page interactions like [`method: Page.click`] auto-wait for elements.
+
+In lazy-loaded pages, it can be useful to wait until an element is visible with [`method: Page.waitForSelector`].
+Alternatively, page interactions like [`method: Page.click`] auto-wait for elements.
```js
// Click triggers navigation
await page.click('text=Login');
- // Click will auto-wait for the element
+// 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
+// Fill will auto-wait for element
await page.fill('#username', 'John Doe');
```
+```python async
+# Click triggers navigation
+await page.click("text=Login")
+# Click will auto-wait for the element
+await page.wait_for_selector("#username", "John Doe")
+
+# Click triggers navigation
+await page.click("text=Login")
+# Fill will auto-wait for element
+await page.fill("#username", "John Doe")
+```
+
+```python sync
+# Click triggers navigation
+page.click("text=Login")
+# Click will auto-wait for the element
+page.wait_for_selector("#username", "John Doe")
+
+# Click triggers navigation
+page.click("text=Login")
+# Fill will auto-wait for element
+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 [`method: Page.waitForNavigation`]. For example:
+
+Clicking an element could trigger asychronous 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
+// Note that Promise.all prevents a race condition
+// between clicking and waiting for a navigation.
await Promise.all([
- page.click('a'), // Triggers a navigation after a timeout
page.waitForNavigation(), // Waits for the next navigation
+ page.click('a'), // Triggers a navigation after a timeout
]);
```
-The `Promise.all` pattern prevents a race condition between `page.click` and `page.waitForNavigation` when navigation happens quickly.
+```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.click("a")
+```
+
+```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.click("a")
+```
### 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:
+
+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
+// Note that Promise.all prevents a race condition
+// between clicking and waiting for a navigation.
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.
+```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.click("a")
+```
+
+```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.click("a")
+```
### Loading a popup
-When popup is opened, explicitly calling [`method: Page.waitForLoadState`] ensures that popup is loaded to the desired state.
+
+When popup is opened, explicitly calling [`method: Page.waitForLoadState`] ensures that popup is loaded to the desired
+state.
```js
const [ popup ] = await Promise.all([
@@ -136,6 +283,20 @@ const [ popup ] = await Promise.all([
await popup.waitForLoadState('load');
```
+```python async
+async with page.expect_popup() as popup_info:
+ await page.click('a[target="_blank"]') # Opens popup
+popup = await popup_info.value
+await popup.wait_for_load_state("load")
+```
+
+```python sync
+with page.expect_popup() as popup_info:
+ page.click('a[target="_blank"]') # Opens popup
+popup = popup_info.value
+popup.wait_for_load_state("load")
+```
+
#### API reference
- [`method: Page.click`]
- [`method: Page.waitForLoadState`]
@@ -144,7 +305,9 @@ await popup.waitForLoadState('load');
- [`method: Page.waitForFunction`]
## Advanced patterns
-For pages that have complicated loading patterns, [`method: Page.waitForFunction`] is a powerful and extensible approach to define a custom wait criteria.
+
+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');
@@ -153,5 +316,20 @@ await page.waitForFunction(() => window.amILoadedYet());
await 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
+# FIXME
+page.goto("http://example.com")
+page.wait_for_function("() => window.amILoadedYet()")
+# Ready to take a screenshot, according to the page itself.
+page.screenshot()
+```
+
#### API reference
-- [`method: Page.waitForFunction`]
+- [`method: Page.waitForFunction`]
\ No newline at end of file
diff --git a/docs/src/network.md b/docs/src/network.md
index be0d3b1790..6f6a82d22c 100644
--- a/docs/src/network.md
+++ b/docs/src/network.md
@@ -3,8 +3,8 @@ id: network
title: "Network"
---
-Playwright provides APIs to **monitor** and **modify** network traffic, both HTTP and HTTPS.
-Any requests that page does, including [XHRs](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest) and
+Playwright provides APIs to **monitor** and **modify** network traffic, both HTTP and HTTPS. Any requests that page
+does, including [XHRs](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest) and
[fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) requests, can be tracked, modified and handled.
@@ -24,8 +24,23 @@ const page = await context.newPage();
await page.goto('https://example.com');
```
-#### API reference
+```python async
+context = await browser.new_context(
+ http_credentials={"username": "bill", "password": "pa55w0rd"}
+)
+page = await context.new_page()
+await page.goto("https://example.com")
+```
+```python sync
+context = browser.new_context(
+ http_credentials={"username": "bill", "password": "pa55w0rd"}
+)
+page = context.new_page()
+page.goto("https://example.com")
+```
+
+#### API reference
- [`method: Browser.newContext`]
@@ -35,12 +50,32 @@ await page.goto('https://example.com');
```js
const [ download ] = await Promise.all([
page.waitForEvent('download'), // <-- start waiting for the download
- page.click('button#delayed-download') // <-- perform the action that directly or indirectly initiates it.
+ page.click('button#delayed-download') // <-- perform the action that directly or indirectly initiates it
]);
const path = await download.path();
```
-For every attachment downloaded by the page, [`event: Page.download`] event is emitted. If you create a browser context with the [`option: acceptDownloads`] set, all these attachments are going to be downloaded into a temporary folder. You can obtain the download url, file system path and payload stream using the [Download] object from the event.
+```python async
+# Start waiting for the download
+async with page.expect_download() as download_info:
+ # Perform the action that directly or indirectly initiates it
+ await page.click("button#delayed-download")
+download = await download_info.value
+path = await download.path()
+```
+
+```python sync
+# Start waiting for the download
+with page.expect_download() as download_info:
+ # Perform the action that directly or indirectly initiates it
+ page.click("button#delayed-download")
+download = download_info.value
+path = download.path()
+```
+
+For every attachment downloaded by the page, [`event: Page.download`] event is emitted. If you create a browser context
+with the [`option: acceptDownloads`] set, all these attachments are going to be downloaded into a temporary folder. You
+can obtain the download url, file system path and payload stream using the [Download] object from the event.
#### Variations
@@ -50,10 +85,20 @@ If you have no idea what initiates the download, you can still handle the event:
page.on('download', download => download.path().then(console.log));
```
-Note that handling the event forks the control flow and makes script harder to follow. Your scenario might end while you are downloading a file since your main control flow is not awaiting for this operation to resolve.
+```python async
+async def handle_download(download):
+ print(await download.path())
+page.on("download", handle_download)
+```
+
+```python sync
+page.on("download", lambda download: print(download.path()))
+```
+
+Note that handling the event forks the control flow and makes script harder to follow. Your scenario might end while you
+are downloading a file since your main control flow is not awaiting for this operation to resolve.
#### API reference
-
- [Download]
- [`event: Page.download`]
- [`method: Page.waitForEvent`]
@@ -82,6 +127,43 @@ const { chromium, webkit, firefox } = require('playwright');
})();
```
+```python async
+import asyncio
+from playwright.async_api import async_playwright
+
+async def run(playwright):
+ chromium = playwright.chromium
+ browser = await chromium.launch()
+ page = await browser.new_page()
+ # Subscribe to "request" and "response" events.
+ page.on("request", lambda request: print(">>", request.method, request.url))
+ page.on("response", lambda response: print("<<", response.status, response.url))
+ await page.goto("https://example.com")
+ await browser.close()
+
+async def main():
+ async with async_playwright() as playwright:
+ await run(playwright)
+asyncio.run(main())
+```
+
+```python sync
+from playwright.sync_api import sync_playwright
+
+def run(playwright):
+ chromium = playwright.chromium
+ browser = chromium.launch()
+ page = browser.new_page()
+ # Subscribe to "request" and "response" events.
+ page.on("request", lambda request: print(">>", request.method, request.url))
+ page.on("response", lambda response: print("<<", response.status, response.url))
+ page.goto("https://example.com")
+ browser.close()
+
+with sync_playwright() as playwright:
+ run(playwright)
+```
+
Or wait for a network response after the button click:
```js
@@ -92,6 +174,20 @@ const [response] = await Promise.all([
]);
```
+```python async
+# Use a glob url pattern
+async with page.expect_response("**/api/fetch_data") as response_info:
+ await page.click("button#update")
+response = await response_info.value
+```
+
+```python sync
+# Use a glob url pattern
+with page.expect_response("**/api/fetch_data") as response_info:
+ page.click("button#update")
+response = response_info.value
+```
+
#### Variations
```js
@@ -108,8 +204,31 @@ const [response] = await Promise.all([
]);
```
-#### API reference
+```python async
+# Use a regular expresison
+async with page.expect_response(re.compile(r"\.jpeg$")) as response_info:
+ await page.click("button#update")
+response = await response_info.value
+# Use a predicate taking a response object
+async with page.expect_response(lambda response: token in response.url) as response_info:
+ await page.click("button#update")
+response = await response_info.value
+```
+
+```python sync
+# Use a regular expresison
+with page.expect_response(re.compile(r"\.jpeg$")) as response_info:
+ page.click("button#update")
+response = response_info.value
+
+# Use a predicate taking a response object
+with page.expect_response(lambda response: token in response.url) as response_info:
+ page.click("button#update")
+response = response_info.value
+```
+
+#### API reference
- [Request]
- [Response]
- [`event: Page.request`]
@@ -129,6 +248,20 @@ await page.route('**/api/fetch_data', route => route.fulfill({
await page.goto('https://example.com');
```
+```python async
+await page.route(
+ "**/api/fetch_data",
+ lambda route: route.fulfill(status=200, body=test_data))
+await page.goto("https://example.com")
+```
+
+```python sync
+page.route(
+ "**/api/fetch_data",
+ lambda route: route.fulfill(status=200, body=test_data))
+page.goto("https://example.com")
+```
+
You can mock API endpoints via handling the network quests in your Playwright script.
#### Variations
@@ -144,8 +277,25 @@ await browserContext.route('**/api/login', route => route.fulfill({
await page.goto('https://example.com');
```
-#### API reference
+```python async
+# Set up route on the entire browser context.
+# It will apply to popup windows and opened links.
+await context.route(
+ "**/api/login",
+ lambda route: route.fulfill(status=200, body="accept"))
+await page.goto("https://example.com")
+```
+```python sync
+# Set up route on the entire browser context.
+# It will apply to popup windows and opened links.
+context.route(
+ "**/api/login",
+ lambda route: route.fulfill(status=200, body="accept"))
+page.goto("https://example.com")
+```
+
+#### API reference
- [`method: BrowserContext.route`]
- [`method: BrowserContext.unroute`]
- [`method: Page.route`]
@@ -168,6 +318,30 @@ await page.route('**/*', route => {
await page.route('**/*', route => route.continue({method: 'POST'}));
```
+```python async
+# Delete header
+async def handle_route(route):
+ headers = route.request.headers
+ del headers["x-secret"]
+ route.continue_(headers=headers)
+await page.route("**/*", handle_route)
+
+# Continue requests as POST.
+await page.route("**/*", lambda route: route.continue_(method="POST"))
+```
+
+```python sync
+# Delete header
+def handle_route(route):
+ headers = route.request.headers
+ del headers["x-secret"]
+ route.continue_(headers=headers)
+page.route("**/*", handle_route)
+
+# Continue requests as POST.
+page.route("**/*", lambda route: route.continue_(method="POST"))
+```
+
You can continue requests with modifications. Example above removes an HTTP header from the outgoing requests.
## Abort requests
@@ -182,10 +356,23 @@ await page.route('**/*', route => {
});
```
-#### API reference
+```python async
+await page.route("**/*.{png,jpg,jpeg}", lambda route: route.abort())
+# Abort based on the request type
+await page.route("**/*", lambda route: route.abort() if route.request.resource_type == "image" else route.continue_())
+```
+
+```python sync
+page.route("**/*.{png,jpg,jpeg}", lambda route: route.abort())
+
+# Abort based on the request type
+page.route("**/*", lambda route: route.abort() if route.request.resource_type == "image" else route.continue_())
+```
+
+#### API reference
- [`method: Page.route`]
- [`method: BrowserContext.route`]
- [`method: Route.abort`]
-
+
\ No newline at end of file
diff --git a/docs/src/pom.md b/docs/src/pom.md
index ce294f60ac..2707e5f738 100644
--- a/docs/src/pom.md
+++ b/docs/src/pom.md
@@ -3,23 +3,23 @@ id: pom
title: "Page Object Models"
---
-Large test suites can be structured to optimize ease of authoring and maintenance.
-Page object models are one such approach to structure your test suite.
+Large test suites can be structured to optimize ease of authoring and maintenance. Page object models are one such
+approach to structure your test suite.
## Introduction
-A page object represents a part of your web application. An e-commerce web
-application might have a home page, a listings page and a checkout page. Each of
-them can be represented by page object models.
-Page objects **simplify authoring**. They create a higher-level API which suits
-your application.
+A page object represents a part of your web application. An e-commerce web application might have a home page, a
+listings page and a checkout page. Each of them can be represented by page object models.
-Page objects **simplify maintenance**. They capture element selectors in one place
-and create reusable code to avoid repetition.
+Page objects **simplify authoring**. They create a higher-level API which suits your application.
+
+Page objects **simplify maintenance**. They capture element selectors in one place and create reusable code to avoid
+repetition.
## Implementation
+
Page object models wrap over a Playwright [Page].
```js
@@ -33,12 +33,40 @@ class SearchPage {
}
async search(text) {
await this.page.fill('[aria-label="Enter your search term"]', text);
- await this.page.keyboard.press('Enter');
+ await this.page.press('[aria-label="Enter your search term"]', 'Enter');
}
}
module.exports = { SearchPage };
```
+```python async
+# models/search.py
+class SearchPage:
+ def __init__(self, page):
+ self.page = page
+
+ async def navigate(self):
+ await self.page.goto("https://bing.com")
+
+ async def search(self, text):
+ await self.page.fill('[aria-label="Enter your search term"]', text)
+ await self.page.press('[aria-label="Enter your search term"]', "Enter")
+```
+
+```python sync
+# models/search.py
+class SearchPage:
+ def __init__(self, page):
+ self.page = page
+
+ def navigate(self):
+ self.page.goto("https://bing.com")
+
+ def search(self, text):
+ self.page.fill('[aria-label="Enter your search term"]', text)
+ self.page.press('[aria-label="Enter your search term"]', "Enter")
+```
+
Page objects can then be used inside a test.
```js
@@ -52,5 +80,27 @@ await searchPage.navigate();
await searchPage.search('search query');
```
+```python async
+# test_search.py
+from models.search import SearchPage
+
+# in the test
+page = await browser.new_page()
+search_page = SearchPage(page)
+await search_page.navigate()
+await search_page.search("search query")
+```
+
+```python sync
+# test_search.py
+from models.search import SearchPage
+
+# in the test
+page = browser.new_page()
+search_page = SearchPage(page)
+search_page.navigate()
+search_page.search("search query")
+```
+
### API reference
-- [Page]
+- [Page]
\ No newline at end of file
diff --git a/docs/src/selectors.md b/docs/src/selectors.md
index e9b93ad467..68f5a79251 100644
--- a/docs/src/selectors.md
+++ b/docs/src/selectors.md
@@ -59,6 +59,64 @@ const handle = await page.$('div >> ../span');
const handle = await divHandle.$('css=span');
```
+```python async
+# queries 'div' css selector
+handle = await page.query_selector('css=div')
+
+# queries '//html/body/div' xpath selector
+handle = await page.query_selector('xpath=//html/body/div')
+
+# queries '"foo"' text selector
+handle = await page.query_selector('text="foo"')
+
+# queries 'span' css selector inside the result of '//html/body/div' xpath selector
+handle = await page.query_selector('xpath=//html/body/div >> css=span')
+
+# converted to 'css=div'
+handle = await page.query_selector('div')
+
+# converted to 'xpath=//html/body/div'
+handle = await page.query_selector('//html/body/div')
+
+# converted to 'text="foo"'
+handle = await page.query_selector('"foo"')
+
+# queries '../span' xpath selector starting with the result of 'div' css selector
+handle = await page.query_selector('div >> ../span')
+
+# queries 'span' css selector inside the div handle
+handle = await div_handle.query_selector('css=span')
+```
+
+```python sync
+# queries 'div' css selector
+handle = page.query_selector('css=div')
+
+# queries '//html/body/div' xpath selector
+handle = page.query_selector('xpath=//html/body/div')
+
+# queries '"foo"' text selector
+handle = page.query_selector('text="foo"')
+
+# queries 'span' css selector inside the result of '//html/body/div' xpath selector
+handle = page.query_selector('xpath=//html/body/div >> css=span')
+
+# converted to 'css=div'
+handle = page.query_selector('div')
+
+# converted to 'xpath=//html/body/div'
+handle = page.query_selector('//html/body/div')
+
+# converted to 'text="foo"'
+handle = page.query_selector('"foo"')
+
+# queries '../span' xpath selector starting with the result of 'div' css selector
+handle = page.query_selector('div >> ../span')
+
+# queries 'span' css selector inside the div handle
+handle = div_handle.query_selector('css=span')
+```
+
## Syntax
Selectors are defined by selector engine name and selector body, `engine=body`.
@@ -127,6 +185,40 @@ await page.click('[aria-label="Close"]'); // short-form
await page.click('css=nav >> text=Login');
```
+```python async
+# queries "Login" text selector
+await page.click('text="Login"')
+await page.click('"Login"') # short-form
+
+# queries "Search GitHub" placeholder attribute
+await page.fill('css=[placeholder="Search GitHub"]')
+await page.fill('[placeholder="Search GitHub"]') # short-form
+
+# queries "Close" accessibility label
+await page.click('css=[aria-label="Close"]')
+await page.click('[aria-label="Close"]') # short-form
+
+# combine role and text queries
+await page.click('css=nav >> text=Login')
+```
+
+```python sync
+# queries "Login" text selector
+page.click('text="Login"')
+page.click('"Login"') # short-form
+
+# queries "Search GitHub" placeholder attribute
+page.fill('css=[placeholder="Search GitHub"]')
+page.fill('[placeholder="Search GitHub"]') # short-form
+
+# queries "Close" accessibility label
+page.click('css=[aria-label="Close"]')
+page.click('[aria-label="Close"]') # short-form
+
+# combine role and text queries
+page.click('css=nav >> text=Login')
+```
+
### Define explicit contract
When user-facing attributes change frequently, it is recommended to use explicit test ids, like `data-test-id`. These `data-*` attributes are supported by the [css] and [id selectors][id].
@@ -144,6 +236,24 @@ await page.click('[data-test-id=directions]'); // short-form
await page.click('data-test-id=directions');
```
+```python async
+# queries data-test-id attribute with css
+await page.click('css=[data-test-id=directions]')
+await page.click('[data-test-id=directions]') # short-form
+
+# queries data-test-id with id
+await page.click('data-test-id=directions')
+```
+
+```python sync
+# queries data-test-id attribute with css
+page.click('css=[data-test-id=directions]')
+page.click('[data-test-id=directions]') # short-form
+
+# queries data-test-id with id
+page.click('data-test-id=directions')
+```
+
### Avoid selectors tied to implementation
[xpath] and [css] can be tied to the DOM structure or implementation. These selectors can break when the DOM structure changes.
@@ -153,6 +263,18 @@ await page.click('#tsf > div:nth-child(2) > div.A8SBwf > div.RNNXgb > div > div.
await page.click('//*[@id="tsf"]/div[2]/div[1]/div[1]/div/div[2]/input');
```
+```python async
+# avoid long css or xpath chains
+await page.click('#tsf > div:nth-child(2) > div.A8SBwf > div.RNNXgb > div > div.a4bIc > input')
+await page.click('//*[@id="tsf"]/div[2]/div[1]/div[1]/div/div[2]/input')
+```
+
+```python sync
+# avoid long css or xpath chains
+page.click('#tsf > div:nth-child(2) > div.A8SBwf > div.RNNXgb > div > div.a4bIc > input')
+page.click('//*[@id="tsf"]/div[2]/div[1]/div[1]/div/div[2]/input')
+```
+
## CSS selector engine
`css` is a default engine - any malformed selector not starting with `//` nor starting and ending with a quote is assumed to be a css selector. For example, Playwright converts `'span > button'` to `'css=span > button'`.
@@ -212,12 +334,24 @@ Consider a page with two buttons, first invisible and second visible.
```js
await page.click('button');
```
+ ```python async
+ await page.click("button")
+ ```
+ ```python sync
+ page.click("button")
+ ```
* This will find a second button, because it is visible, and then click it.
```js
await page.click('button:visible');
```
+ ```python async
+ await page.click("button:visible")
+ ```
+ ```python sync
+ page.click("button:visible")
+ ```
Use `:visible` with caution, because it has two major drawbacks:
* When elements change their visibility dynamically, `:visible` will give upredictable results based on the timing.
@@ -238,6 +372,16 @@ The `:text` pseudo-class matches elements that have a text node child with speci
await page.click('button:text("Sign in")');
```
+```python async
+# Click a button with text "Sign in".
+await page.click('button:text("Sign in")')
+```
+
+```python sync
+# Click a button with text "Sign in".
+page.click('button:text("Sign in")')
+```
+
### CSS extension: has
The `:has()` pseudo-class is an [experimental CSS pseudo-class](https://developer.mozilla.org/en-US/docs/Web/CSS/:has) that is supported by Playwright.
@@ -247,6 +391,16 @@ The `:has()` pseudo-class is an [experimental CSS pseudo-class](https://develope
await page.textContent('article:has(div.promo)');
```
+```python async
+# Returns text content of an element that has a
inside.
+await page.textContent("article:has(div.promo)")
+```
+
+```python sync
+# Returns text content of an element that has a
inside.
+page.textContent("article:has(div.promo)")
+```
+
### CSS extension: is
The `:is()` pseudo-class is an [experimental CSS pseudo-class](https://developer.mozilla.org/en-US/docs/Web/CSS/:is) that is supported by Playwright.
@@ -256,6 +410,16 @@ The `:is()` pseudo-class is an [experimental CSS pseudo-class](https://developer
await page.click('button:is(:text("Log in"), :text("Sign in"))');
```
+```python async
+# Clicks a