diff --git a/docs/src/navigations.md b/docs/src/navigations.md index c6de39fc8a..86d9f77ccd 100644 --- a/docs/src/navigations.md +++ b/docs/src/navigations.md @@ -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`] \ No newline at end of file diff --git a/docs/src/network.md b/docs/src/network.md index a0f1a1fd6f..1eeddab2aa 100644 --- a/docs/src/network.md +++ b/docs/src/network.md @@ -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(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`] diff --git a/docs/src/selectors.md b/docs/src/selectors.md index 13de228e6f..bfa8f6cc5b 100644 --- a/docs/src/selectors.md +++ b/docs/src/selectors.md @@ -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 `` because ``, because `` and ``. @@ -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 `
Playwright
`. @@ -261,6 +306,13 @@ Text selector has a few variations: page.click('article:has-text("All products")') ``` + ```csharp + // Wrong, will match many elements including + await page.ClickAsync(":has-text(\"Playwright\")"); + // Correct, only matches the
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