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")
```
```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`]

View File

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

View File

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