mirror of
https://github.com/microsoft/playwright.git
synced 2025-06-26 21:40:17 +00:00
docs(dotnet): examples for navigation.md, network.md, selectors.md (#6593)
This commit is contained in:
parent
7bbb91f265
commit
c01c5dbb55
@ -56,6 +56,12 @@ await page.goto("https://example.com")
|
|||||||
page.goto("https://example.com")
|
page.goto("https://example.com")
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// Navigate the page
|
||||||
|
await page.GotoAsync("https://example.com");
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
### Custom wait
|
### Custom wait
|
||||||
|
|
||||||
Override the default behavior to wait until a specific event, like `networkidle`.
|
Override the default behavior to wait until a specific event, like `networkidle`.
|
||||||
@ -81,6 +87,11 @@ await page.goto("https://example.com", wait_until="networkidle")
|
|||||||
page.goto("https://example.com", wait_until="networkidle")
|
page.goto("https://example.com", wait_until="networkidle")
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// Navigate and wait until network is idle
|
||||||
|
await page.GotoAsync("https://example.com", waitUntil: WaitUntilState.NetworkIdle);
|
||||||
|
```
|
||||||
|
|
||||||
### Wait for element
|
### Wait for element
|
||||||
|
|
||||||
In lazy-loaded pages, it can be useful to wait until an element is visible with [`method: Page.waitForSelector`].
|
In lazy-loaded pages, it can be useful to wait until an element is visible with [`method: Page.waitForSelector`].
|
||||||
@ -130,6 +141,17 @@ page.goto("https://example.com")
|
|||||||
page.click("text=example domain")
|
page.click("text=example domain")
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// Navigate and wait for element
|
||||||
|
await page.GotoAsync("https://example.com");
|
||||||
|
await page.WaitForSelectorAsync("text=Example Domain");
|
||||||
|
|
||||||
|
// Navigate and click element
|
||||||
|
// Click will auto-wait for the element
|
||||||
|
await page.GotoAsync("https://example.com");
|
||||||
|
await page.ClickAsync("text=Example Domain");
|
||||||
|
```
|
||||||
|
|
||||||
### API reference
|
### API reference
|
||||||
- [`method: Page.goto`]
|
- [`method: Page.goto`]
|
||||||
- [`method: Page.reload`]
|
- [`method: Page.reload`]
|
||||||
@ -177,6 +199,14 @@ page.click("text=Login")
|
|||||||
page.fill("#username", "John Doe")
|
page.fill("#username", "John Doe")
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// Click will auto-wait for navigation to complete
|
||||||
|
await page.ClickAsync("text=Login");
|
||||||
|
|
||||||
|
// Fill will auto-wait for element on navigated page
|
||||||
|
await page.FillAsync("#username", "John Doe");
|
||||||
|
```
|
||||||
|
|
||||||
### Custom wait
|
### Custom wait
|
||||||
|
|
||||||
`page.click` can be combined with [`method: Page.waitForLoadState`] to wait for a loading event.
|
`page.click` can be combined with [`method: Page.waitForLoadState`] to wait for a loading event.
|
||||||
@ -201,6 +231,11 @@ page.click("button"); # Click triggers navigation
|
|||||||
page.wait_for_load_state("networkidle"); # This waits for the "networkidle"
|
page.wait_for_load_state("networkidle"); # This waits for the "networkidle"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
await page.ClickAsync("button"); // Click triggers navigation
|
||||||
|
await page.WaitForLoadStateAsync(LoadState.NetworkIdle); // This resolves after "networkidle"
|
||||||
|
```
|
||||||
|
|
||||||
### Wait for element
|
### Wait for element
|
||||||
|
|
||||||
In lazy-loaded pages, it can be useful to wait until an element is visible with [`method: Page.waitForSelector`].
|
In lazy-loaded pages, it can be useful to wait until an element is visible with [`method: Page.waitForSelector`].
|
||||||
@ -254,6 +289,18 @@ page.click("text=Login")
|
|||||||
page.fill("#username", "John Doe")
|
page.fill("#username", "John Doe")
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// Click will auto-wait for the element and trigger navigation
|
||||||
|
await page.ClickAsync("text=Login");
|
||||||
|
// Wait for the element
|
||||||
|
await page.WaitForSelectorAsync("#username");
|
||||||
|
|
||||||
|
// Click triggers navigation
|
||||||
|
await page.ClickAsync("text=Login");
|
||||||
|
// Fill will auto-wait for element
|
||||||
|
await page.FillAsync("#username", "John Doe");
|
||||||
|
```
|
||||||
|
|
||||||
### Asynchronous navigation
|
### Asynchronous navigation
|
||||||
|
|
||||||
Clicking an element could trigger asynchronous processing before initiating the navigation. In these cases, it is
|
Clicking an element could trigger asynchronous processing before initiating the navigation. In these cases, it is
|
||||||
@ -294,6 +341,15 @@ with page.expect_navigation():
|
|||||||
page.click("a")
|
page.click("a")
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// Using waitForNavigation with a callback prevents a race condition
|
||||||
|
// between clicking and waiting for a navigation.
|
||||||
|
await TaskUtils.WhenAll(
|
||||||
|
page.WaitForNavigationAsync(), // Waits for the next navigation
|
||||||
|
page.ClickAsync("div.delayed-navigation"); // Triggers a navigation after a timeout
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
### Multiple navigations
|
### Multiple navigations
|
||||||
|
|
||||||
Clicking an element could trigger multiple navigations. In these cases, it is recommended to explicitly
|
Clicking an element could trigger multiple navigations. In these cases, it is recommended to explicitly
|
||||||
@ -334,6 +390,15 @@ with page.expect_navigation(url="**/login"):
|
|||||||
page.click("a")
|
page.click("a")
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// Running action in the callback of waitForNavigation prevents a race
|
||||||
|
// condition between clicking and waiting for a navigation.
|
||||||
|
await TaskUtils.WhenAll(
|
||||||
|
page.WaitForNavigationAsync("**/login"), // Waits for the next navigation
|
||||||
|
page.ClickAsync("a") // Triggers a navigation with a script redirect
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
### Loading a popup
|
### Loading a popup
|
||||||
|
|
||||||
When popup is opened, explicitly calling [`method: Page.waitForLoadState`] ensures that popup is loaded to the desired
|
When popup is opened, explicitly calling [`method: Page.waitForLoadState`] ensures that popup is loaded to the desired
|
||||||
@ -368,6 +433,14 @@ popup = popup_info.value
|
|||||||
popup.wait_for_load_state("load")
|
popup.wait_for_load_state("load")
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
var popup = await TaskUtils.WhenAll(
|
||||||
|
page.WaitForPopupAsync(),
|
||||||
|
page.ClickAsync("a[target='_blank']") // Opens popup
|
||||||
|
);
|
||||||
|
await popup.WaitForLoadStateAsync(LoadState.Load);
|
||||||
|
```
|
||||||
|
|
||||||
### API reference
|
### API reference
|
||||||
- [`method: Page.click`]
|
- [`method: Page.click`]
|
||||||
- [`method: Page.waitForLoadState`]
|
- [`method: Page.waitForLoadState`]
|
||||||
@ -408,5 +481,12 @@ page.wait_for_function("() => window.amILoadedYet()")
|
|||||||
page.screenshot()
|
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();
|
||||||
|
```
|
||||||
|
|
||||||
### API reference
|
### API reference
|
||||||
- [`method: Page.waitForFunction`]
|
- [`method: Page.waitForFunction`]
|
||||||
@ -47,6 +47,19 @@ page = context.new_page()
|
|||||||
page.goto("https://example.com")
|
page.goto("https://example.com")
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
using var context = await Browser.NewContextAsync(new BrowserContextOptions
|
||||||
|
{
|
||||||
|
HttpCredentials = new HttpCredentials
|
||||||
|
{
|
||||||
|
Username = "bill",
|
||||||
|
Password = "pa55w0rd"
|
||||||
|
},
|
||||||
|
});
|
||||||
|
var page = await context.NewPageAsync();
|
||||||
|
await page.GotoAsync("https://example.com");
|
||||||
|
```
|
||||||
|
|
||||||
### API reference
|
### API reference
|
||||||
- [`method: Browser.newContext`]
|
- [`method: Browser.newContext`]
|
||||||
|
|
||||||
@ -93,6 +106,16 @@ browser = chromium.launch(proxy={
|
|||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
var proxy = new Proxy
|
||||||
|
{
|
||||||
|
Server = "http://myproxy.com:3128",
|
||||||
|
Username = "user",
|
||||||
|
Password = "pwd"
|
||||||
|
};
|
||||||
|
await using var browser = await BrowserType.LaunchAsync(proxy: proxy);
|
||||||
|
```
|
||||||
|
|
||||||
When specifying proxy for each context individually, you need to give Playwright
|
When specifying proxy for each context individually, you need to give Playwright
|
||||||
a hint that proxy will be set. This is done via passing a non-empty proxy server
|
a hint that proxy will be set. This is done via passing a non-empty proxy server
|
||||||
to the browser itself. Here is an example of a context-specific proxy:
|
to the browser itself. Here is an example of a context-specific proxy:
|
||||||
@ -123,6 +146,13 @@ browser = chromium.launch(proxy={"server": "per-context"})
|
|||||||
context = browser.new_context(proxy={"server": "http://myproxy.com:3128"})
|
context = browser.new_context(proxy={"server": "http://myproxy.com:3128"})
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
var proxy = new Proxy { Server = "per-context" };
|
||||||
|
await using var browser = await BrowserType.LaunchAsync(proxy: proxy);
|
||||||
|
using var context = await Browser.NewContextAsync(proxy: new Proxy { Server = "http://myproxy.com:3128" });
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
## Network events
|
## Network events
|
||||||
|
|
||||||
You can monitor all the requests and responses:
|
You can monitor all the requests and responses:
|
||||||
@ -200,6 +230,24 @@ with sync_playwright() as playwright:
|
|||||||
run(playwright)
|
run(playwright)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
using Microsoft.Playwright;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
class Example
|
||||||
|
{
|
||||||
|
public async void Main()
|
||||||
|
{
|
||||||
|
using var playwright = await Playwright.CreateAsync();
|
||||||
|
await using var browser = await playwright.Chromium.LaunchAsync();
|
||||||
|
var page = await browser.NewPageAsync();
|
||||||
|
page.Request += (_, request) => Console.WriteLine(">> " + request.Method + " " + request.Url);
|
||||||
|
page.Response += (_, response) => Console.WriteLine("<<" + response.Status + " " + response.Url);
|
||||||
|
await page.GotoAsync("https://example.com");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
Or wait for a network response after the button click:
|
Or wait for a network response after the button click:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
@ -231,6 +279,14 @@ with page.expect_response("**/api/fetch_data") as response_info:
|
|||||||
response = response_info.value
|
response = response_info.value
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// Use a glob URL pattern
|
||||||
|
var response = await TaskUtils.WhenAll(
|
||||||
|
page.WaitForResponseAsync("**/api/fetch_data"),
|
||||||
|
page.ClickAsync("button#update")
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
#### Variations
|
#### Variations
|
||||||
|
|
||||||
```js
|
```js
|
||||||
@ -283,6 +339,20 @@ with page.expect_response(lambda response: token in response.url) as response_in
|
|||||||
response = response_info.value
|
response = response_info.value
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// Use a regular expression
|
||||||
|
var response = await TaskUtils.WhenAll(
|
||||||
|
page.WaitForResponseAsync(new Regex("\\.jpeg$")),
|
||||||
|
page.ClickAsync("button#update")
|
||||||
|
);
|
||||||
|
|
||||||
|
// Use a predicate taking a Response object
|
||||||
|
var response = await TaskUtils.WhenAll(
|
||||||
|
page.WaitForResponseAsync(r => r.Url.Contains(token)),
|
||||||
|
page.ClickAsync("button#update")
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
### API reference
|
### API reference
|
||||||
- [Request]
|
- [Request]
|
||||||
- [Response]
|
- [Response]
|
||||||
@ -364,6 +434,13 @@ context.route(
|
|||||||
page.goto("https://example.com")
|
page.goto("https://example.com")
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
await page.RouteAsync("**/api/fetch_data", async route => {
|
||||||
|
await route.FulfillAsync(status: 200, body: testData);
|
||||||
|
});
|
||||||
|
await page.GotoAsync("https://example.com");
|
||||||
|
```
|
||||||
|
|
||||||
### API reference
|
### API reference
|
||||||
- [`method: BrowserContext.route`]
|
- [`method: BrowserContext.route`]
|
||||||
- [`method: BrowserContext.unroute`]
|
- [`method: BrowserContext.unroute`]
|
||||||
@ -423,6 +500,18 @@ page.route("**/*", handle_route)
|
|||||||
page.route("**/*", lambda route: route.continue_(method="POST"))
|
page.route("**/*", lambda route: route.continue_(method="POST"))
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// Delete header
|
||||||
|
await page.RouteAsync("**/*", async route => {
|
||||||
|
var headers = new Dictionary<string, string>(route.Request.Headers.ToDictionary(x => x.Key, x => x.Value));
|
||||||
|
headers.Remove("X-Secret");
|
||||||
|
await route.ResumeAsync(headers: headers);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Continue requests as POST.
|
||||||
|
await page.RouteAsync("**/*", async route => await route.ResumeAsync(method: "POST"));
|
||||||
|
```
|
||||||
|
|
||||||
You can continue requests with modifications. Example above removes an HTTP header from the outgoing requests.
|
You can continue requests with modifications. Example above removes an HTTP header from the outgoing requests.
|
||||||
|
|
||||||
## Abort requests
|
## Abort requests
|
||||||
@ -463,6 +552,18 @@ page.route("**/*.{png,jpg,jpeg}", lambda route: route.abort())
|
|||||||
page.route("**/*", lambda route: route.abort() if route.request.resource_type == "image" else route.continue_())
|
page.route("**/*", lambda route: route.abort() if route.request.resource_type == "image" else route.continue_())
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
await page.RouteAsync("**/*.{png,jpg,jpeg}", route => route.AbortAsync());
|
||||||
|
|
||||||
|
// Abort based on the request type
|
||||||
|
await page.RouteAsync("**/*", async route => {
|
||||||
|
if ("image".Equals(route.Request.ResourceType))
|
||||||
|
await route.AbortAsync();
|
||||||
|
else
|
||||||
|
await route.ResumeAsync();
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
### API reference
|
### API reference
|
||||||
- [`method: Page.route`]
|
- [`method: Page.route`]
|
||||||
- [`method: BrowserContext.route`]
|
- [`method: BrowserContext.route`]
|
||||||
@ -504,6 +605,15 @@ def on_web_socket(ws):
|
|||||||
page.on("websocket", on_web_socket)
|
page.on("websocket", on_web_socket)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
page.WebSocket += (_, ws) => {
|
||||||
|
Console.WriteLine("WebSocket opened: " + ws.Url);
|
||||||
|
ws.FrameSent += (_, f) => Console.WriteLine(f.Text);
|
||||||
|
ws.FrameReceived += (_, f) => Console.WriteLine(f.Text);
|
||||||
|
ws.Close += (_, ws1) => Console.WriteLine("WebSocket closed");
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
### API reference
|
### API reference
|
||||||
- [WebSocket]
|
- [WebSocket]
|
||||||
- [`event: Page.webSocket`]
|
- [`event: Page.webSocket`]
|
||||||
|
|||||||
@ -24,6 +24,9 @@ methods accept [`param: selector`] as their first argument.
|
|||||||
```python sync
|
```python sync
|
||||||
page.click("text=Log in")
|
page.click("text=Log in")
|
||||||
```
|
```
|
||||||
|
```csharp
|
||||||
|
await page.ClickAsync("text=Log in");
|
||||||
|
```
|
||||||
Learn more about [text selector][text].
|
Learn more about [text selector][text].
|
||||||
- CSS selector
|
- CSS selector
|
||||||
```js
|
```js
|
||||||
@ -42,6 +45,10 @@ methods accept [`param: selector`] as their first argument.
|
|||||||
page.click("button")
|
page.click("button")
|
||||||
page.click("#nav-bar .contact-us-item")
|
page.click("#nav-bar .contact-us-item")
|
||||||
```
|
```
|
||||||
|
```csharp
|
||||||
|
await page.ClickAsync("button");
|
||||||
|
await page.ClickAsync("#nav-bar .contact-us-item");
|
||||||
|
```
|
||||||
Learn more about [css selector][css].
|
Learn more about [css selector][css].
|
||||||
- Select by attribute, with css selector
|
- Select by attribute, with css selector
|
||||||
```js
|
```js
|
||||||
@ -60,6 +67,10 @@ methods accept [`param: selector`] as their first argument.
|
|||||||
page.click("[data-test=login-button]")
|
page.click("[data-test=login-button]")
|
||||||
page.click("[aria-label='Sign in']")
|
page.click("[aria-label='Sign in']")
|
||||||
```
|
```
|
||||||
|
```csharp
|
||||||
|
await page.ClickAsync("[data-test=login-button]");
|
||||||
|
await page.ClickAsync("[aria-label='Sign in']");
|
||||||
|
```
|
||||||
Learn more about [css selector][css].
|
Learn more about [css selector][css].
|
||||||
- Combine css and text selectors
|
- Combine css and text selectors
|
||||||
```js
|
```js
|
||||||
@ -78,6 +89,10 @@ methods accept [`param: selector`] as their first argument.
|
|||||||
page.click("article:has-text('Playwright')")
|
page.click("article:has-text('Playwright')")
|
||||||
page.click("#nav-bar :text('Contact us')")
|
page.click("#nav-bar :text('Contact us')")
|
||||||
```
|
```
|
||||||
|
```csharp
|
||||||
|
await page.ClickAsync("article:has-text(\"Playwright\")");
|
||||||
|
await page.ClickAsync("#nav-bar :text(\"Contact us\")");
|
||||||
|
```
|
||||||
Learn more about [`:has-text()` and `:text()` pseudo classes][text].
|
Learn more about [`:has-text()` and `:text()` pseudo classes][text].
|
||||||
- Element that contains another, with css selector
|
- Element that contains another, with css selector
|
||||||
```js
|
```js
|
||||||
@ -92,6 +107,9 @@ methods accept [`param: selector`] as their first argument.
|
|||||||
```python sync
|
```python sync
|
||||||
page.click(".item-description:has(.item-promo-banner)")
|
page.click(".item-description:has(.item-promo-banner)")
|
||||||
```
|
```
|
||||||
|
```csharp
|
||||||
|
await page.ClickAsync(".item-description:has(.item-promo-banner)");
|
||||||
|
```
|
||||||
Learn more about [`:has()` pseudo class](#selecting-elements-that-contain-other-elements).
|
Learn more about [`:has()` pseudo class](#selecting-elements-that-contain-other-elements).
|
||||||
- Selecting based on layout, with css selector
|
- Selecting based on layout, with css selector
|
||||||
```js
|
```js
|
||||||
@ -106,6 +124,9 @@ methods accept [`param: selector`] as their first argument.
|
|||||||
```python sync
|
```python sync
|
||||||
page.click("input:right-of(:text('Username'))")
|
page.click("input:right-of(:text('Username'))")
|
||||||
```
|
```
|
||||||
|
```csharp
|
||||||
|
await page.ClickAsync("input:right-of(:text(\"Username\"))");
|
||||||
|
```
|
||||||
Learn more about [layout selectors](#selecting-elements-based-on-layout).
|
Learn more about [layout selectors](#selecting-elements-based-on-layout).
|
||||||
- Only visible elements, with css selector
|
- Only visible elements, with css selector
|
||||||
```js
|
```js
|
||||||
@ -120,6 +141,9 @@ methods accept [`param: selector`] as their first argument.
|
|||||||
```python sync
|
```python sync
|
||||||
page.click(".login-button:visible")
|
page.click(".login-button:visible")
|
||||||
```
|
```
|
||||||
|
```csharp
|
||||||
|
await page.ClickAsync(".login-button:visible");
|
||||||
|
```
|
||||||
Learn more about [`:visible` pseudo-class](#selecting-visible-elements).
|
Learn more about [`:visible` pseudo-class](#selecting-visible-elements).
|
||||||
- Pick n-th match
|
- Pick n-th match
|
||||||
```js
|
```js
|
||||||
@ -134,6 +158,9 @@ methods accept [`param: selector`] as their first argument.
|
|||||||
```python sync
|
```python sync
|
||||||
page.click(":nth-match(:text('Buy'), 3)"
|
page.click(":nth-match(:text('Buy'), 3)"
|
||||||
```
|
```
|
||||||
|
```csharp
|
||||||
|
await page.ClickAsync(":nth-match(:text('Buy'), 3)");
|
||||||
|
```
|
||||||
Learn more about [`:nth-match()` pseudo-class](#pick-n-th-match-from-the-query-result).
|
Learn more about [`:nth-match()` pseudo-class](#pick-n-th-match-from-the-query-result).
|
||||||
- XPath selector
|
- XPath selector
|
||||||
```js
|
```js
|
||||||
@ -148,6 +175,9 @@ methods accept [`param: selector`] as their first argument.
|
|||||||
```python sync
|
```python sync
|
||||||
page.click("xpath=//button")
|
page.click("xpath=//button")
|
||||||
```
|
```
|
||||||
|
```csharp
|
||||||
|
await page.ClickAsync("xpath=//button");
|
||||||
|
```
|
||||||
Learn more about [XPath selector][xpath].
|
Learn more about [XPath selector][xpath].
|
||||||
|
|
||||||
## Text selector
|
## Text selector
|
||||||
@ -166,6 +196,9 @@ await page.click("text=Log in")
|
|||||||
```python sync
|
```python sync
|
||||||
page.click("text=Log in")
|
page.click("text=Log in")
|
||||||
```
|
```
|
||||||
|
```csharp
|
||||||
|
await page.ClickAsync("text=Log in");
|
||||||
|
```
|
||||||
|
|
||||||
Text selector has a few variations:
|
Text selector has a few variations:
|
||||||
|
|
||||||
@ -183,6 +216,9 @@ Text selector has a few variations:
|
|||||||
```python sync
|
```python sync
|
||||||
page.click("text=Log in")
|
page.click("text=Log in")
|
||||||
```
|
```
|
||||||
|
```csharp
|
||||||
|
await page.ClickAsync("text=Log in");
|
||||||
|
```
|
||||||
|
|
||||||
- `text="Log in"` - text body can be escaped with single or double quotes to search for a text node with exact content. For example, `text="Log"` does not match `<button>Log in</button>` because `<button>` contains a single text node `"Log in"` that is not equal to `"Log"`. However, `text="Log"` matches `<button>Log<span>in</span></button>`, because `<button>` contains a text node `"Log"`.
|
- `text="Log in"` - text body can be escaped with single or double quotes to search for a text node with exact content. For example, `text="Log"` does not match `<button>Log in</button>` because `<button>` contains a single text node `"Log in"` that is not equal to `"Log"`. However, `text="Log"` matches `<button>Log<span>in</span></button>`, because `<button>` contains a text node `"Log"`.
|
||||||
|
|
||||||
@ -200,6 +236,9 @@ Text selector has a few variations:
|
|||||||
```python sync
|
```python sync
|
||||||
page.click("text='Log in'")
|
page.click("text='Log in'")
|
||||||
```
|
```
|
||||||
|
```csharp
|
||||||
|
await page.ClickAsync("text='Log in'");
|
||||||
|
```
|
||||||
|
|
||||||
- `"Log in"` - selector starting and ending with a quote (either `"` or `'`) is assumed to be a text selector. For example, `"Log in"` is converted to `text="Log in"` internally.
|
- `"Log in"` - selector starting and ending with a quote (either `"` or `'`) is assumed to be a text selector. For example, `"Log in"` is converted to `text="Log in"` internally.
|
||||||
|
|
||||||
@ -215,6 +254,9 @@ Text selector has a few variations:
|
|||||||
```python sync
|
```python sync
|
||||||
page.click("'Log in'")
|
page.click("'Log in'")
|
||||||
```
|
```
|
||||||
|
```csharp
|
||||||
|
await page.ClickAsync("'Log in'");
|
||||||
|
```
|
||||||
|
|
||||||
- `/Log\s*in/i` - body can be a [JavaScript-like regex](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp) wrapped in `/` symbols. For example, `text=/Log\s*in/i` matches `<button>Login</button>` and `<button>log IN</button>`.
|
- `/Log\s*in/i` - body can be a [JavaScript-like regex](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp) wrapped in `/` symbols. For example, `text=/Log\s*in/i` matches `<button>Login</button>` and `<button>log IN</button>`.
|
||||||
|
|
||||||
@ -230,6 +272,9 @@ Text selector has a few variations:
|
|||||||
```python sync
|
```python sync
|
||||||
page.click("text=/Log\s*in/i")
|
page.click("text=/Log\s*in/i")
|
||||||
```
|
```
|
||||||
|
```csharp
|
||||||
|
await page.ClickAsync("text=/Log\\s*in/i");
|
||||||
|
```
|
||||||
|
|
||||||
- `article:has-text("Playwright")` - the `:has-text()` pseudo-class can be used inside a [css] selector. It matches any element containing specified text somewhere inside, possibly in a child or a descendant element. For example, `article:has-text("Playwright")` matches `<article><div>Playwright</div></article>`.
|
- `article:has-text("Playwright")` - the `:has-text()` pseudo-class can be used inside a [css] selector. It matches any element containing specified text somewhere inside, possibly in a child or a descendant element. For example, `article:has-text("Playwright")` matches `<article><div>Playwright</div></article>`.
|
||||||
|
|
||||||
@ -261,6 +306,13 @@ Text selector has a few variations:
|
|||||||
page.click('article:has-text("All products")')
|
page.click('article:has-text("All products")')
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// Wrong, will match many elements including <body>
|
||||||
|
await page.ClickAsync(":has-text(\"Playwright\")");
|
||||||
|
// Correct, only matches the <article> element
|
||||||
|
await page.ClickAsync("article:has-text(\"Playwright\")");
|
||||||
|
```
|
||||||
|
|
||||||
- `#nav-bar :text("Home")` - the `:text()` pseudo-class can be used inside a [css] selector. It matches the smallest element containing specified text. This example is equivalent to `text=Home`, but inside the `#nav-bar` element.
|
- `#nav-bar :text("Home")` - the `:text()` pseudo-class can be used inside a [css] selector. It matches the smallest element containing specified text. This example is equivalent to `text=Home`, but inside the `#nav-bar` element.
|
||||||
|
|
||||||
```js
|
```js
|
||||||
@ -275,6 +327,9 @@ Text selector has a few variations:
|
|||||||
```python sync
|
```python sync
|
||||||
page.click("#nav-bar :text('Home')")
|
page.click("#nav-bar :text('Home')")
|
||||||
```
|
```
|
||||||
|
```csharp
|
||||||
|
await page.ClickAsync("#nav-bar :text('Home')");
|
||||||
|
```
|
||||||
|
|
||||||
- `#nav-bar :text-is("Home")` - the `:text-is()` pseudo-class can be used inside a [css] selector, for strict text node match. This example is equivalent to `text="Home"` (note quotes), but inside the `#nav-bar` element.
|
- `#nav-bar :text-is("Home")` - the `:text-is()` pseudo-class can be used inside a [css] selector, for strict text node match. This example is equivalent to `text="Home"` (note quotes), but inside the `#nav-bar` element.
|
||||||
|
|
||||||
@ -310,6 +365,10 @@ await page.click("button")
|
|||||||
page.click("button")
|
page.click("button")
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
await page.ClickAsync("button");
|
||||||
|
```
|
||||||
|
|
||||||
## Selecting visible elements
|
## Selecting visible elements
|
||||||
|
|
||||||
The `:visible` pseudo-class in CSS selectors matches the elements that are
|
The `:visible` pseudo-class in CSS selectors matches the elements that are
|
||||||
@ -347,6 +406,10 @@ Consider a page with two buttons, first invisible and second visible.
|
|||||||
page.click("button")
|
page.click("button")
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
await page.ClickAsync("button");
|
||||||
|
```
|
||||||
|
|
||||||
* This will find a second button, because it is visible, and then click it.
|
* This will find a second button, because it is visible, and then click it.
|
||||||
|
|
||||||
```js
|
```js
|
||||||
@ -361,6 +424,9 @@ Consider a page with two buttons, first invisible and second visible.
|
|||||||
```python sync
|
```python sync
|
||||||
page.click("button:visible")
|
page.click("button:visible")
|
||||||
```
|
```
|
||||||
|
```csharp
|
||||||
|
await page.ClickAsync("button:visible");
|
||||||
|
```
|
||||||
|
|
||||||
Use `:visible` with caution, because it has two major drawbacks:
|
Use `:visible` with caution, because it has two major drawbacks:
|
||||||
* When elements change their visibility dynamically, `:visible` will give unpredictable results based on the timing.
|
* When elements change their visibility dynamically, `:visible` will give unpredictable results based on the timing.
|
||||||
@ -389,6 +455,10 @@ await page.textContent("article:has(div.promo)")
|
|||||||
page.textContent("article:has(div.promo)")
|
page.textContent("article:has(div.promo)")
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
await page.TextContentAsync("article:has(div.promo)");
|
||||||
|
```
|
||||||
|
|
||||||
## Selecting elements matching one of the conditions
|
## Selecting elements matching one of the conditions
|
||||||
|
|
||||||
The `:is()` pseudo-class is an [experimental CSS pseudo-class](https://developer.mozilla.org/en-US/docs/Web/CSS/:is).
|
The `:is()` pseudo-class is an [experimental CSS pseudo-class](https://developer.mozilla.org/en-US/docs/Web/CSS/:is).
|
||||||
@ -416,6 +486,11 @@ await page.click(':is(button:has-text("Log in"), button:has-text("Sign in"))')
|
|||||||
page.click(':is(button:has-text("Log in"), button:has-text("Sign in"))')
|
page.click(':is(button:has-text("Log in"), button:has-text("Sign in"))')
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// Clicks a <button> that has either a "Log in" or "Sign in" text.
|
||||||
|
await page.ClickAsync(":is(button:has-text(\"Log in\"), button:has-text(\"Sign in\"))");
|
||||||
|
```
|
||||||
|
|
||||||
## Selecting elements in Shadow DOM
|
## Selecting elements in Shadow DOM
|
||||||
|
|
||||||
Our `css` and `text` engines pierce the [Shadow DOM](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_shadow_DOM) by default:
|
Our `css` and `text` engines pierce the [Shadow DOM](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_shadow_DOM) by default:
|
||||||
@ -445,6 +520,10 @@ await page.click(":light(.article > .header)")
|
|||||||
page.click(":light(.article > .header)")
|
page.click(":light(.article > .header)")
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
await page.ClickAsync(":light(.article > .header)");
|
||||||
|
```
|
||||||
|
|
||||||
More advanced Shadow DOM use cases:
|
More advanced Shadow DOM use cases:
|
||||||
|
|
||||||
```html
|
```html
|
||||||
@ -523,6 +602,14 @@ page.fill('input:right-of(:text("Username"))', 'value')
|
|||||||
page.click('button:near(.promo-card)')
|
page.click('button:near(.promo-card)')
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// Fill an input to the right of "Username".
|
||||||
|
await page.FillAsync("input:right-of(:text(\"Username\"))", "value");
|
||||||
|
|
||||||
|
// Click a button near the promo card.
|
||||||
|
await page.ClickAsync("button:near(.promo-card)");
|
||||||
|
```
|
||||||
|
|
||||||
All layout selectors support optional maximum pixel distance as the last argument. For example
|
All layout selectors support optional maximum pixel distance as the last argument. For example
|
||||||
`button:near(:text("Username"), 120)` matches a button that is at most 120 pixels away from the element with the text "Username".
|
`button:near(:text("Username"), 120)` matches a button that is at most 120 pixels away from the element with the text "Username".
|
||||||
|
|
||||||
@ -580,6 +667,14 @@ page.fill('id=username', 'value')
|
|||||||
page.click('data-test-id=submit')
|
page.click('data-test-id=submit')
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// Fill an input with the id "username"
|
||||||
|
await page.FillAsync("id=username", "value");
|
||||||
|
|
||||||
|
// Click an element with data-test-id "submit"
|
||||||
|
await page.ClickAsync("data-test-id=submit");
|
||||||
|
```
|
||||||
|
|
||||||
:::note
|
:::note
|
||||||
Attribute selectors pierce shadow DOM. To opt-out from this behavior, use `:light` suffix after attribute, for example `page.click('data-test-id:light=submit')
|
Attribute selectors pierce shadow DOM. To opt-out from this behavior, use `:light` suffix after attribute, for example `page.click('data-test-id:light=submit')
|
||||||
:::
|
:::
|
||||||
@ -617,6 +712,11 @@ await page.click(":nth-match(:text('Buy'), 3)"
|
|||||||
page.click(":nth-match(:text('Buy'), 3)"
|
page.click(":nth-match(:text('Buy'), 3)"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// Click the third "Buy" button
|
||||||
|
await page.ClickAsync(":nth-match(:text('Buy'), 3)");
|
||||||
|
```
|
||||||
|
|
||||||
`:nth-match()` is also useful to wait until a specified number of elements appear, using [`method: Page.waitForSelector`].
|
`:nth-match()` is also useful to wait until a specified number of elements appear, using [`method: Page.waitForSelector`].
|
||||||
|
|
||||||
```js
|
```js
|
||||||
@ -639,6 +739,11 @@ await page.wait_for_selector(":nth-match(:text('Buy'), 3)")
|
|||||||
page.wait_for_selector(":nth-match(:text('Buy'), 3)")
|
page.wait_for_selector(":nth-match(:text('Buy'), 3)")
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// Wait until all three buttons are visible
|
||||||
|
await page.WaitForSelectorAsync(":nth-match(:text('Buy'), 3)");
|
||||||
|
```
|
||||||
|
|
||||||
:::note
|
:::note
|
||||||
Unlike [`:nth-child()`](https://developer.mozilla.org/en-US/docs/Web/CSS/:nth-child), elements do not have to be siblings, they could be anywhere on the page. In the snippet above, all three buttons match `:text("Buy")` selector, and `:nth-match()` selects the third button.
|
Unlike [`:nth-child()`](https://developer.mozilla.org/en-US/docs/Web/CSS/:nth-child), elements do not have to be siblings, they could be anywhere on the page. In the snippet above, all three buttons match `:text("Buy")` selector, and `:nth-match()` selects the third button.
|
||||||
:::
|
:::
|
||||||
@ -749,6 +854,23 @@ page.click('[aria-label="Close"]') # short-form
|
|||||||
page.click('css=nav >> text=Login')
|
page.click('css=nav >> text=Login')
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// queries "Login" text selector
|
||||||
|
await page.ClickAsync("text=\"Login\"");
|
||||||
|
await page.ClickAsync("\"Login\""); // short-form
|
||||||
|
|
||||||
|
// queries "Search GitHub" placeholder attribute
|
||||||
|
await page.FillAsync("css=[placeholder='Search GitHub']", "query");
|
||||||
|
await page.FillAsync("[placeholder='Search GitHub']", "query"); // short-form
|
||||||
|
|
||||||
|
// queries "Close" accessibility label
|
||||||
|
await page.ClickAsync("css=[aria-label='Close']");
|
||||||
|
await page.ClickAsync("[aria-label='Close']"); // short-form
|
||||||
|
|
||||||
|
// combine role and text queries
|
||||||
|
await page.ClickAsync("css=nav >> text=Login");
|
||||||
|
```
|
||||||
|
|
||||||
### Define explicit contract
|
### 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].
|
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].
|
||||||
@ -793,6 +915,15 @@ page.click('[data-test-id=directions]') # short-form
|
|||||||
page.click('data-test-id=directions')
|
page.click('data-test-id=directions')
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// queries data-test-id attribute with css
|
||||||
|
await page.ClickAsync("css=[data-test-id=directions]");
|
||||||
|
await page.ClickAsync("[data-test-id=directions]"); // short-form
|
||||||
|
|
||||||
|
// queries data-test-id with id
|
||||||
|
await page.ClickAsync("data-test-id=directions");
|
||||||
|
```
|
||||||
|
|
||||||
### Avoid selectors tied to implementation
|
### Avoid selectors tied to implementation
|
||||||
|
|
||||||
[xpath] and [css] can be tied to the DOM structure or implementation. These selectors can break when
|
[xpath] and [css] can be tied to the DOM structure or implementation. These selectors can break when
|
||||||
@ -822,6 +953,12 @@ page.click('#tsf > div:nth-child(2) > div.A8SBwf > div.RNNXgb > div > div.a4bIc
|
|||||||
page.click('//*[@id="tsf"]/div[2]/div[1]/div[1]/div/div[2]/input')
|
page.click('//*[@id="tsf"]/div[2]/div[1]/div[1]/div/div[2]/input')
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// avoid long css or xpath chains
|
||||||
|
await page.ClickAsync("#tsf > div:nth-child(2) > div.A8SBwf > div.RNNXgb > div > div.a4bIc > input");
|
||||||
|
await page.ClickAsync("//*[@id='tsf']/div[2]/div[1]/div[1]/div/div[2]/input");
|
||||||
|
```
|
||||||
|
|
||||||
[text]: #text-selector
|
[text]: #text-selector
|
||||||
[css]: #css-selector
|
[css]: #css-selector
|
||||||
[xpath]: #xpath-selectors
|
[xpath]: #xpath-selectors
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user