docs(dotnet): examples for navigation.md, network.md, selectors.md (#6593)

This commit is contained in:
Pavel Feldman 2021-05-14 23:22:30 -07:00 committed by GitHub
parent 7bbb91f265
commit c01c5dbb55
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 327 additions and 0 deletions

View File

@ -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`]

View File

@ -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`]

View File

@ -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