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")
|
||||
```
|
||||
|
||||
```csharp
|
||||
// Navigate the page
|
||||
await page.GotoAsync("https://example.com");
|
||||
```
|
||||
|
||||
|
||||
### Custom wait
|
||||
|
||||
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")
|
||||
```
|
||||
|
||||
```csharp
|
||||
// Navigate and wait until network is idle
|
||||
await page.GotoAsync("https://example.com", waitUntil: WaitUntilState.NetworkIdle);
|
||||
```
|
||||
|
||||
### Wait for element
|
||||
|
||||
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")
|
||||
```
|
||||
|
||||
```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
|
||||
- [`method: Page.goto`]
|
||||
- [`method: Page.reload`]
|
||||
@ -177,6 +199,14 @@ page.click("text=Login")
|
||||
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
|
||||
|
||||
`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"
|
||||
```
|
||||
|
||||
```csharp
|
||||
await page.ClickAsync("button"); // Click triggers navigation
|
||||
await page.WaitForLoadStateAsync(LoadState.NetworkIdle); // This resolves after "networkidle"
|
||||
```
|
||||
|
||||
### Wait for element
|
||||
|
||||
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")
|
||||
```
|
||||
|
||||
```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
|
||||
|
||||
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")
|
||||
```
|
||||
|
||||
```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
|
||||
|
||||
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")
|
||||
```
|
||||
|
||||
```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
|
||||
|
||||
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")
|
||||
```
|
||||
|
||||
```csharp
|
||||
var popup = await TaskUtils.WhenAll(
|
||||
page.WaitForPopupAsync(),
|
||||
page.ClickAsync("a[target='_blank']") // Opens popup
|
||||
);
|
||||
await popup.WaitForLoadStateAsync(LoadState.Load);
|
||||
```
|
||||
|
||||
### API reference
|
||||
- [`method: Page.click`]
|
||||
- [`method: Page.waitForLoadState`]
|
||||
@ -408,5 +481,12 @@ page.wait_for_function("() => window.amILoadedYet()")
|
||||
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
|
||||
- [`method: Page.waitForFunction`]
|
||||
@ -47,6 +47,19 @@ page = context.new_page()
|
||||
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
|
||||
- [`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
|
||||
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:
|
||||
@ -123,6 +146,13 @@ browser = chromium.launch(proxy={"server": "per-context"})
|
||||
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
|
||||
|
||||
You can monitor all the requests and responses:
|
||||
@ -200,6 +230,24 @@ with sync_playwright() as 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:
|
||||
|
||||
```js
|
||||
@ -231,6 +279,14 @@ with page.expect_response("**/api/fetch_data") as response_info:
|
||||
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
|
||||
|
||||
```js
|
||||
@ -283,6 +339,20 @@ with page.expect_response(lambda response: token in response.url) as response_in
|
||||
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
|
||||
- [Request]
|
||||
- [Response]
|
||||
@ -364,6 +434,13 @@ context.route(
|
||||
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
|
||||
- [`method: BrowserContext.route`]
|
||||
- [`method: BrowserContext.unroute`]
|
||||
@ -423,6 +500,18 @@ page.route("**/*", handle_route)
|
||||
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.
|
||||
|
||||
## 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_())
|
||||
```
|
||||
|
||||
```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
|
||||
- [`method: Page.route`]
|
||||
- [`method: BrowserContext.route`]
|
||||
@ -504,6 +605,15 @@ def on_web_socket(ws):
|
||||
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
|
||||
- [WebSocket]
|
||||
- [`event: Page.webSocket`]
|
||||
|
||||
@ -24,6 +24,9 @@ methods accept [`param: selector`] as their first argument.
|
||||
```python sync
|
||||
page.click("text=Log in")
|
||||
```
|
||||
```csharp
|
||||
await page.ClickAsync("text=Log in");
|
||||
```
|
||||
Learn more about [text selector][text].
|
||||
- CSS selector
|
||||
```js
|
||||
@ -42,6 +45,10 @@ methods accept [`param: selector`] as their first argument.
|
||||
page.click("button")
|
||||
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].
|
||||
- Select by attribute, with css selector
|
||||
```js
|
||||
@ -60,6 +67,10 @@ methods accept [`param: selector`] as their first argument.
|
||||
page.click("[data-test=login-button]")
|
||||
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].
|
||||
- Combine css and text selectors
|
||||
```js
|
||||
@ -78,6 +89,10 @@ methods accept [`param: selector`] as their first argument.
|
||||
page.click("article:has-text('Playwright')")
|
||||
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].
|
||||
- Element that contains another, with css selector
|
||||
```js
|
||||
@ -92,6 +107,9 @@ methods accept [`param: selector`] as their first argument.
|
||||
```python sync
|
||||
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).
|
||||
- Selecting based on layout, with css selector
|
||||
```js
|
||||
@ -106,6 +124,9 @@ methods accept [`param: selector`] as their first argument.
|
||||
```python sync
|
||||
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).
|
||||
- Only visible elements, with css selector
|
||||
```js
|
||||
@ -120,6 +141,9 @@ methods accept [`param: selector`] as their first argument.
|
||||
```python sync
|
||||
page.click(".login-button:visible")
|
||||
```
|
||||
```csharp
|
||||
await page.ClickAsync(".login-button:visible");
|
||||
```
|
||||
Learn more about [`:visible` pseudo-class](#selecting-visible-elements).
|
||||
- Pick n-th match
|
||||
```js
|
||||
@ -134,6 +158,9 @@ methods accept [`param: selector`] as their first argument.
|
||||
```python sync
|
||||
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).
|
||||
- XPath selector
|
||||
```js
|
||||
@ -148,6 +175,9 @@ methods accept [`param: selector`] as their first argument.
|
||||
```python sync
|
||||
page.click("xpath=//button")
|
||||
```
|
||||
```csharp
|
||||
await page.ClickAsync("xpath=//button");
|
||||
```
|
||||
Learn more about [XPath selector][xpath].
|
||||
|
||||
## Text selector
|
||||
@ -166,6 +196,9 @@ await page.click("text=Log in")
|
||||
```python sync
|
||||
page.click("text=Log in")
|
||||
```
|
||||
```csharp
|
||||
await page.ClickAsync("text=Log in");
|
||||
```
|
||||
|
||||
Text selector has a few variations:
|
||||
|
||||
@ -183,6 +216,9 @@ Text selector has a few variations:
|
||||
```python sync
|
||||
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"`.
|
||||
|
||||
@ -200,6 +236,9 @@ Text selector has a few variations:
|
||||
```python sync
|
||||
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.
|
||||
|
||||
@ -215,6 +254,9 @@ Text selector has a few variations:
|
||||
```python sync
|
||||
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>`.
|
||||
|
||||
@ -230,6 +272,9 @@ Text selector has a few variations:
|
||||
```python sync
|
||||
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>`.
|
||||
|
||||
@ -261,6 +306,13 @@ Text selector has a few variations:
|
||||
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.
|
||||
|
||||
```js
|
||||
@ -275,6 +327,9 @@ Text selector has a few variations:
|
||||
```python sync
|
||||
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.
|
||||
|
||||
@ -310,6 +365,10 @@ await page.click("button")
|
||||
page.click("button")
|
||||
```
|
||||
|
||||
```csharp
|
||||
await page.ClickAsync("button");
|
||||
```
|
||||
|
||||
## Selecting visible elements
|
||||
|
||||
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")
|
||||
```
|
||||
|
||||
```csharp
|
||||
await page.ClickAsync("button");
|
||||
```
|
||||
|
||||
* This will find a second button, because it is visible, and then click it.
|
||||
|
||||
```js
|
||||
@ -361,6 +424,9 @@ Consider a page with two buttons, first invisible and second visible.
|
||||
```python sync
|
||||
page.click("button:visible")
|
||||
```
|
||||
```csharp
|
||||
await page.ClickAsync("button:visible");
|
||||
```
|
||||
|
||||
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.
|
||||
@ -389,6 +455,10 @@ await 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
|
||||
|
||||
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"))')
|
||||
```
|
||||
|
||||
```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
|
||||
|
||||
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)")
|
||||
```
|
||||
|
||||
```csharp
|
||||
await page.ClickAsync(":light(.article > .header)");
|
||||
```
|
||||
|
||||
More advanced Shadow DOM use cases:
|
||||
|
||||
```html
|
||||
@ -523,6 +602,14 @@ page.fill('input:right-of(:text("Username"))', 'value')
|
||||
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
|
||||
`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')
|
||||
```
|
||||
|
||||
```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
|
||||
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)"
|
||||
```
|
||||
|
||||
```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`].
|
||||
|
||||
```js
|
||||
@ -639,6 +739,11 @@ await 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
|
||||
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')
|
||||
```
|
||||
|
||||
```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
|
||||
|
||||
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')
|
||||
```
|
||||
|
||||
```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
|
||||
|
||||
[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')
|
||||
```
|
||||
|
||||
```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
|
||||
[css]: #css-selector
|
||||
[xpath]: #xpath-selectors
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user