diff --git a/README.md b/README.md index 861b0feac9..4aa6f2a529 100644 --- a/README.md +++ b/README.md @@ -3,9 +3,9 @@ | | Linux | macOS | Windows | | :--- | :---: | :---: | :---: | -| Chromium 137.0.7151.27 | ✅ | ✅ | ✅ | -| WebKit 18.4 | ✅ | ✅ | ✅ | -| Firefox 137.0 | ✅ | ✅ | ✅ | +| Chromium 138.0.7204.4 | ✅ | ✅ | ✅ | +| WebKit 18.5 | ✅ | ✅ | ✅ | +| Firefox 139.0 | ✅ | ✅ | ✅ | Playwright for .NET is the official language port of [Playwright](https://playwright.dev), the library to automate [Chromium](https://www.chromium.org/Home), [Firefox](https://www.mozilla.org/en-US/firefox/new/) and [WebKit](https://webkit.org/) with a single API. Playwright is built to enable cross-browser web automation that is **ever-green**, **capable**, **reliable** and **fast**. diff --git a/src/Common/Version.props b/src/Common/Version.props index 0fe86ed89d..72f0206031 100644 --- a/src/Common/Version.props +++ b/src/Common/Version.props @@ -2,7 +2,7 @@ 1.52.0 $(AssemblyVersion) - 1.53.0-alpha-2025-05-21 + 1.53.0-beta-1749131401000 $(AssemblyVersion) $(AssemblyVersion) true diff --git a/src/Playwright.TestingHarnessTest/package-lock.json b/src/Playwright.TestingHarnessTest/package-lock.json index 7d6bac6a78..6502372aeb 100644 --- a/src/Playwright.TestingHarnessTest/package-lock.json +++ b/src/Playwright.TestingHarnessTest/package-lock.json @@ -7,18 +7,18 @@ "": { "name": "playwright.testingharnesstest", "devDependencies": { - "@playwright/test": "1.53.0-alpha-2025-05-21", + "@playwright/test": "1.53.0-beta-1749131401000", "@types/node": "^22.12.0", "fast-xml-parser": "^4.5.0" } }, "node_modules/@playwright/test": { - "version": "1.53.0-alpha-2025-05-21", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.53.0-alpha-2025-05-21.tgz", - "integrity": "sha512-Op3hh/3tIRnT63Iy6GmD6+vDZRyjVCQTM3mxEN269hjlvMKXZ+XwgXoY8V7vH5WK75xtCXd/kwSMBvP5R8E41A==", + "version": "1.53.0-beta-1749131401000", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.53.0-beta-1749131401000.tgz", + "integrity": "sha512-uW+vwUqhzjiMjQkYx2nFOh9f/gV+GT1GY27+w/PPjKWUqlsAm4gkOqrZ4cgOU4fSNc2fMPB+O0E5PAKEgz+khQ==", "dev": true, "dependencies": { - "playwright": "1.53.0-alpha-2025-05-21" + "playwright": "1.53.0-beta-1749131401000" }, "bin": { "playwright": "cli.js" @@ -75,12 +75,12 @@ } }, "node_modules/playwright": { - "version": "1.53.0-alpha-2025-05-21", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.53.0-alpha-2025-05-21.tgz", - "integrity": "sha512-zplHUa9ALvRygqhSBQbPtebMAnnj3+Xx0B7hiNM7aqW37tPQLfcMHIkl+TvIIjJRmZjqlRcEqykuZQawisN5yQ==", + "version": "1.53.0-beta-1749131401000", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.53.0-beta-1749131401000.tgz", + "integrity": "sha512-5xd1YI6w7x25GOFFojn8Fn9DUb4sv9q5JMySLuiqpZAQ2ap5LbyPpCfr8JquPmaLgRVOehU+BcP1PJtLfbTlOg==", "dev": true, "dependencies": { - "playwright-core": "1.53.0-alpha-2025-05-21" + "playwright-core": "1.53.0-beta-1749131401000" }, "bin": { "playwright": "cli.js" @@ -93,9 +93,9 @@ } }, "node_modules/playwright-core": { - "version": "1.53.0-alpha-2025-05-21", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.53.0-alpha-2025-05-21.tgz", - "integrity": "sha512-W087CBxyE0T1OUHAg2/ZpYyBPm1IOisHQPkGFl1vhT8CgZjW7kWdTR6zyHzY9wwlX0OW+kQXYnp3zPddIaH5KA==", + "version": "1.53.0-beta-1749131401000", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.53.0-beta-1749131401000.tgz", + "integrity": "sha512-HSooiOSSZ9wMVRyihzzSHG2MqLmiZrSFQ77pRuutTnjipM9lIqPleiVD7e6Kxsl+oukxHAgCEplzqr5Vh+yvFQ==", "dev": true, "bin": { "playwright-core": "cli.js" @@ -120,12 +120,12 @@ }, "dependencies": { "@playwright/test": { - "version": "1.53.0-alpha-2025-05-21", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.53.0-alpha-2025-05-21.tgz", - "integrity": "sha512-Op3hh/3tIRnT63Iy6GmD6+vDZRyjVCQTM3mxEN269hjlvMKXZ+XwgXoY8V7vH5WK75xtCXd/kwSMBvP5R8E41A==", + "version": "1.53.0-beta-1749131401000", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.53.0-beta-1749131401000.tgz", + "integrity": "sha512-uW+vwUqhzjiMjQkYx2nFOh9f/gV+GT1GY27+w/PPjKWUqlsAm4gkOqrZ4cgOU4fSNc2fMPB+O0E5PAKEgz+khQ==", "dev": true, "requires": { - "playwright": "1.53.0-alpha-2025-05-21" + "playwright": "1.53.0-beta-1749131401000" } }, "@types/node": { @@ -154,19 +154,19 @@ "optional": true }, "playwright": { - "version": "1.53.0-alpha-2025-05-21", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.53.0-alpha-2025-05-21.tgz", - "integrity": "sha512-zplHUa9ALvRygqhSBQbPtebMAnnj3+Xx0B7hiNM7aqW37tPQLfcMHIkl+TvIIjJRmZjqlRcEqykuZQawisN5yQ==", + "version": "1.53.0-beta-1749131401000", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.53.0-beta-1749131401000.tgz", + "integrity": "sha512-5xd1YI6w7x25GOFFojn8Fn9DUb4sv9q5JMySLuiqpZAQ2ap5LbyPpCfr8JquPmaLgRVOehU+BcP1PJtLfbTlOg==", "dev": true, "requires": { "fsevents": "2.3.2", - "playwright-core": "1.53.0-alpha-2025-05-21" + "playwright-core": "1.53.0-beta-1749131401000" } }, "playwright-core": { - "version": "1.53.0-alpha-2025-05-21", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.53.0-alpha-2025-05-21.tgz", - "integrity": "sha512-W087CBxyE0T1OUHAg2/ZpYyBPm1IOisHQPkGFl1vhT8CgZjW7kWdTR6zyHzY9wwlX0OW+kQXYnp3zPddIaH5KA==", + "version": "1.53.0-beta-1749131401000", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.53.0-beta-1749131401000.tgz", + "integrity": "sha512-HSooiOSSZ9wMVRyihzzSHG2MqLmiZrSFQ77pRuutTnjipM9lIqPleiVD7e6Kxsl+oukxHAgCEplzqr5Vh+yvFQ==", "dev": true }, "strnum": { diff --git a/src/Playwright.TestingHarnessTest/package.json b/src/Playwright.TestingHarnessTest/package.json index 58cef19681..f908d53840 100644 --- a/src/Playwright.TestingHarnessTest/package.json +++ b/src/Playwright.TestingHarnessTest/package.json @@ -2,7 +2,7 @@ "name": "playwright.testingharnesstest", "private": true, "devDependencies": { - "@playwright/test": "1.53.0-alpha-2025-05-21", + "@playwright/test": "1.53.0-beta-1749131401000", "@types/node": "^22.12.0", "fast-xml-parser": "^4.5.0" } diff --git a/src/Playwright.TestingHarnessTest/tests/mstest/basic.spec.ts b/src/Playwright.TestingHarnessTest/tests/mstest/basic.spec.ts index 34ea4e7f67..c1d8e1ae6b 100644 --- a/src/Playwright.TestingHarnessTest/tests/mstest/basic.spec.ts +++ b/src/Playwright.TestingHarnessTest/tests/mstest/basic.spec.ts @@ -36,7 +36,7 @@ test('should be able to forward DEBUG=pw:api env var', async ({ runTest }) => { namespace Playwright.TestingHarnessTest.MSTest; - [TestClass] + [TestClass] public class : PageTest { [TestMethod] @@ -81,7 +81,7 @@ test('should be able to set the browser via the runsettings file', async ({ runT namespace Playwright.TestingHarnessTest.MSTest; - [TestClass] + [TestClass] public class : PageTest { [TestMethod] @@ -120,7 +120,7 @@ test('should prioritize browser from env over the runsettings file', async ({ ru namespace Playwright.TestingHarnessTest.MSTest; - [TestClass] + [TestClass] public class : PageTest { [TestMethod] @@ -161,7 +161,7 @@ test('should be able to make the browser headed via the env', async ({ runTest } namespace Playwright.TestingHarnessTest.MSTest; - [TestClass] + [TestClass] public class : PageTest { [TestMethod] @@ -192,7 +192,7 @@ test('should be able to parse BrowserName and LaunchOptions.Headless from runset namespace Playwright.TestingHarnessTest.MSTest; - [TestClass] + [TestClass] public class : PageTest { [TestMethod] @@ -233,7 +233,7 @@ test('should be able to parse LaunchOptions.Proxy from runsettings', async ({ ru namespace Playwright.TestingHarnessTest.MSTest; - [TestClass] + [TestClass] public class : PageTest { [TestMethod] @@ -281,7 +281,7 @@ test('should be able to parse LaunchOptions.Args from runsettings', async ({ run namespace Playwright.TestingHarnessTest.MSTest; - [TestClass] + [TestClass] public class : PageTest { [TestMethod] @@ -316,10 +316,10 @@ test('should be able to override context options', async ({ runTest }) => { using Microsoft.Playwright; using Microsoft.Playwright.MSTest; using Microsoft.VisualStudio.TestTools.UnitTesting; - + namespace Playwright.TestingHarnessTest.MSTest; - [TestClass] + [TestClass] public class : PageTest { [TestMethod] @@ -371,7 +371,7 @@ test('should be able to override launch options', async ({ runTest }) => { namespace Playwright.TestingHarnessTest.MSTest; - [TestClass] + [TestClass] public class : PageTest { [TestMethod] @@ -409,7 +409,7 @@ test.describe('Expect() timeout', () => { namespace Playwright.TestingHarnessTest.MSTest; - [TestClass] + [TestClass] public class : PageTest { [TestMethod] @@ -424,7 +424,7 @@ test.describe('Expect() timeout', () => { expect(result.passed).toBe(0); expect(result.failed).toBe(1); expect(result.total).toBe(1); - expect(result.rawStdout).toContain("LocatorAssertions.ToHaveTextAsync with timeout 5000ms") + expect(result.rawStdout).toContain("Expect \"ToHaveTextAsync\" with timeout 5000ms") }); test('should be able to override it via each Expect() call', async ({ runTest }) => { @@ -434,10 +434,10 @@ test.describe('Expect() timeout', () => { using System.Threading.Tasks; using Microsoft.Playwright.MSTest; using Microsoft.VisualStudio.TestTools.UnitTesting; - + namespace Playwright.TestingHarnessTest.MSTest; - - [TestClass] + + [TestClass] public class : PageTest { [TestMethod] @@ -452,7 +452,7 @@ test.describe('Expect() timeout', () => { expect(result.passed).toBe(0); expect(result.failed).toBe(1); expect(result.total).toBe(1); - expect(result.rawStdout).toContain("LocatorAssertions.ToHaveTextAsync with timeout 100ms") + expect(result.rawStdout).toContain("Expect \"ToHaveTextAsync\" with timeout 100ms") }); test('should be able to override it via the global settings', async ({ runTest }) => { const result = await runTest({ @@ -464,7 +464,7 @@ test.describe('Expect() timeout', () => { namespace Playwright.TestingHarnessTest.MSTest; - [TestClass] + [TestClass] public class : PageTest { [TestMethod] @@ -487,7 +487,7 @@ test.describe('Expect() timeout', () => { expect(result.passed).toBe(0); expect(result.failed).toBe(1); expect(result.total).toBe(1); - expect(result.rawStdout).toContain("LocatorAssertions.ToHaveTextAsync with timeout 123ms") + expect(result.rawStdout).toContain("Expect \"ToHaveTextAsync\" with timeout 123ms") }); }); @@ -501,7 +501,7 @@ test.describe('ConnectOptions', () => { namespace Playwright.TestingHarnessTest.MSTest; - [TestClass] + [TestClass] public class : PageTest { [TestMethod] diff --git a/src/Playwright.TestingHarnessTest/tests/nunit/basic.spec.ts b/src/Playwright.TestingHarnessTest/tests/nunit/basic.spec.ts index 495e064322..995ce49b3f 100644 --- a/src/Playwright.TestingHarnessTest/tests/nunit/basic.spec.ts +++ b/src/Playwright.TestingHarnessTest/tests/nunit/basic.spec.ts @@ -312,7 +312,7 @@ test('should be able to override context options', async ({ runTest }) => { using Microsoft.Playwright; using Microsoft.Playwright.NUnit; using NUnit.Framework; - + namespace Playwright.TestingHarnessTest.NUnit; public class : PageTest @@ -402,7 +402,7 @@ test.describe('Expect() timeout', () => { using Microsoft.Playwright; using Microsoft.Playwright.NUnit; using NUnit.Framework; - + namespace Playwright.TestingHarnessTest.NUnit; public class : PageTest @@ -419,7 +419,7 @@ test.describe('Expect() timeout', () => { expect(result.passed).toBe(0); expect(result.failed).toBe(1); expect(result.total).toBe(1); - expect(result.rawStdout).toContain("LocatorAssertions.ToHaveTextAsync with timeout 5000ms") + expect(result.rawStdout).toContain("Expect \"ToHaveTextAsync\" with timeout 5000ms") }); test('should be able to override it via each Expect() call', async ({ runTest }) => { @@ -431,7 +431,7 @@ test.describe('Expect() timeout', () => { using Microsoft.Playwright; using Microsoft.Playwright.NUnit; using NUnit.Framework; - + namespace Playwright.TestingHarnessTest.NUnit; public class : PageTest @@ -448,7 +448,7 @@ test.describe('Expect() timeout', () => { expect(result.passed).toBe(0); expect(result.failed).toBe(1); expect(result.total).toBe(1); - expect(result.rawStdout).toContain("LocatorAssertions.ToHaveTextAsync with timeout 100ms") + expect(result.rawStdout).toContain("Expect \"ToHaveTextAsync\" with timeout 100ms") }); test('should be able to override it via the global config', async ({ runTest }) => { const result = await runTest({ @@ -459,7 +459,7 @@ test.describe('Expect() timeout', () => { using Microsoft.Playwright; using Microsoft.Playwright.NUnit; using NUnit.Framework; - + namespace Playwright.TestingHarnessTest.NUnit; public class : PageTest @@ -484,7 +484,7 @@ test.describe('Expect() timeout', () => { expect(result.passed).toBe(0); expect(result.failed).toBe(1); expect(result.total).toBe(1); - expect(result.rawStdout).toContain("LocatorAssertions.ToHaveTextAsync with timeout 123ms") + expect(result.rawStdout).toContain("Expect \"ToHaveTextAsync\" with timeout 123ms") }); }); diff --git a/src/Playwright.TestingHarnessTest/tests/xunit/basic.spec.ts b/src/Playwright.TestingHarnessTest/tests/xunit/basic.spec.ts index a2a0903aad..e6c603c973 100644 --- a/src/Playwright.TestingHarnessTest/tests/xunit/basic.spec.ts +++ b/src/Playwright.TestingHarnessTest/tests/xunit/basic.spec.ts @@ -358,7 +358,7 @@ test('should be able to override context options', async ({ runTest }) => { using Microsoft.Playwright; using Microsoft.Playwright.Xunit; using Xunit; - + namespace Playwright.TestingHarnessTest.Xunit; public class : PageTest @@ -448,7 +448,7 @@ test.describe('Expect() timeout', () => { using Microsoft.Playwright; using Microsoft.Playwright.Xunit; using Xunit; - + namespace Playwright.TestingHarnessTest.Xunit; public class : PageTest @@ -465,7 +465,7 @@ test.describe('Expect() timeout', () => { expect(result.passed).toBe(0); expect(result.failed).toBe(1); expect(result.total).toBe(1); - expect(result.rawStdout).toContain("LocatorAssertions.ToHaveTextAsync with timeout 5000ms") + expect(result.rawStdout).toContain("Expect \"ToHaveTextAsync\" with timeout 5000ms") }); test('should be able to override it via each Expect() call', async ({ runTest }) => { @@ -477,7 +477,7 @@ test.describe('Expect() timeout', () => { using Microsoft.Playwright; using Microsoft.Playwright.Xunit; using Xunit; - + namespace Playwright.TestingHarnessTest.Xunit; public class : PageTest @@ -494,7 +494,7 @@ test.describe('Expect() timeout', () => { expect(result.passed).toBe(0); expect(result.failed).toBe(1); expect(result.total).toBe(1); - expect(result.rawStdout).toContain("LocatorAssertions.ToHaveTextAsync with timeout 100ms") + expect(result.rawStdout).toContain("Expect \"ToHaveTextAsync\" with timeout 100ms") }); test('should be able to override it via the global config', async ({ runTest }) => { @@ -506,7 +506,7 @@ test.describe('Expect() timeout', () => { using Microsoft.Playwright; using Microsoft.Playwright.Xunit; using Xunit; - + namespace Playwright.TestingHarnessTest.Xunit; public class : PageTest @@ -531,7 +531,7 @@ test.describe('Expect() timeout', () => { expect(result.passed).toBe(0); expect(result.failed).toBe(1); expect(result.total).toBe(1); - expect(result.rawStdout).toContain("LocatorAssertions.ToHaveTextAsync with timeout 123ms") + expect(result.rawStdout).toContain("Expect \"ToHaveTextAsync\" with timeout 123ms") }); }); diff --git a/src/Playwright.Tests/Assertions/LocatorAssertionsTests.cs b/src/Playwright.Tests/Assertions/LocatorAssertionsTests.cs index de62c35373..4683a54e44 100644 --- a/src/Playwright.Tests/Assertions/LocatorAssertionsTests.cs +++ b/src/Playwright.Tests/Assertions/LocatorAssertionsTests.cs @@ -40,11 +40,11 @@ public async Task ShouldSupportToBeChecked() var exception = await PlaywrightAssert.ThrowsAsync(() => Expect(Page.Locator("input")).ToBeCheckedAsync(new() { Checked = false, Timeout = 300 })); StringAssert.Contains("Locator expected not to be checked", exception.Message); - StringAssert.Contains("LocatorAssertions.ToBeCheckedAsync with timeout 300ms", exception.Message); + StringAssert.Contains("Expect \"ToBeCheckedAsync\" with timeout 300ms", exception.Message); exception = await PlaywrightAssert.ThrowsAsync(() => Expect(Page.Locator("input")).Not.ToBeCheckedAsync(new() { Timeout = 300 })); StringAssert.Contains("Locator expected not to be checked", exception.Message); - StringAssert.Contains("LocatorAssertions.ToBeCheckedAsync with timeout 300ms", exception.Message); + StringAssert.Contains("Expect \"ToBeCheckedAsync\" with timeout 300ms", exception.Message); } [PlaywrightTest("tests/page/expect-boolean.spec.ts", "with indeterminate:true")] @@ -70,7 +70,7 @@ public async Task FailWithIndeterminateTrue() await Page.SetContentAsync(""); var locator = Page.Locator("input"); var exception = await PlaywrightAssert.ThrowsAsync(() => Expect(locator).ToBeCheckedAsync(new() { Indeterminate = true, Timeout = 1000 })); - StringAssert.Contains("LocatorAssertions.ToBeCheckedAsync with timeout 1000ms", exception.Message); + StringAssert.Contains("Expect \"ToBeCheckedAsync\" with timeout 1000ms", exception.Message); } [PlaywrightTest("playwright-test/playwright.expect.spec.ts", "should be able to set default timeout")] @@ -81,7 +81,7 @@ public async Task ShouldBeAbleToSetDefaultTimeout() SetDefaultExpectTimeout(1111); var exception = await PlaywrightAssert.ThrowsAsync(() => Expect(Page.Locator("input")).Not.ToBeCheckedAsync()); StringAssert.Contains("Locator expected not to be checked", exception.Message); - StringAssert.Contains("LocatorAssertions.ToBeCheckedAsync with timeout 1111ms", exception.Message); + StringAssert.Contains("Expect \"ToBeCheckedAsync\" with timeout 1111ms", exception.Message); } finally { @@ -278,7 +278,7 @@ public async Task ShouldSupportToBeEnabled() StringAssert.Contains("locator resolved to ", exception.Message); // extra checks StringAssert.Contains("Locator expected to be enabled", exception.Message); - StringAssert.Contains("LocatorAssertions.ToBeEnabledAsync with timeout 1000ms", exception.Message); + StringAssert.Contains("Expect \"ToBeEnabledAsync\" with timeout 1000ms", exception.Message); } { // eventually @@ -402,14 +402,14 @@ public async Task ShouldSupportToBeVisibleToBeHiddenFail() var locator = Page.Locator("button"); var exception = await PlaywrightAssert.ThrowsAsync(() => Expect(locator).ToBeVisibleAsync(new() { Timeout = 500 })); StringAssert.Contains("Locator expected to be visible", exception.Message); - StringAssert.Contains("LocatorAssertions.ToBeVisibleAsync with timeout 500ms", exception.Message); + StringAssert.Contains("Expect \"ToBeVisibleAsync\" with timeout 500ms", exception.Message); } { await Page.SetContentAsync(""); var locator = Page.Locator("input"); var exception = await PlaywrightAssert.ThrowsAsync(() => Expect(locator).Not.ToBeVisibleAsync(new() { Timeout = 500 })); StringAssert.Contains("Locator expected not to be visible", exception.Message); - StringAssert.Contains("LocatorAssertions.ToBeVisibleAsync with timeout 500ms", exception.Message); + StringAssert.Contains("Expect \"ToBeVisibleAsync\" with timeout 500ms", exception.Message); } } @@ -438,7 +438,7 @@ public async Task ShouldSupportToContainText() var exeption = await PlaywrightAssert.ThrowsAsync(() => Expect(Page.Locator("#node")).ToContainTextAsync(new Regex("ex2"), new() { Timeout = 100 })); StringAssert.Contains("Locator expected text matching regex 'ex2'", exeption.Message); StringAssert.Contains("But was: 'Text content'", exeption.Message); - StringAssert.Contains("LocatorAssertions.ToContainTextAsync with timeout 100ms", exeption.Message); + StringAssert.Contains("Expect \"ToContainTextAsync\" with timeout 100ms", exeption.Message); } { await Page.SetContentAsync("
Text \ncontent 
"); @@ -472,7 +472,7 @@ public async Task ShouldSupportToContainText() var exeption = await PlaywrightAssert.ThrowsAsync(() => Expect(locator).ToHaveTextAsync("Text", new() { Timeout = 100 })); StringAssert.Contains("Locator expected to have text 'Text'", exeption.Message); StringAssert.Contains("But was: 'Text content'", exeption.Message); - StringAssert.Contains("LocatorAssertions.ToHaveTextAsync with timeout 100ms", exeption.Message); + StringAssert.Contains("Expect \"ToHaveTextAsync\" with timeout 100ms", exeption.Message); } } @@ -543,7 +543,7 @@ public async Task ShouldSupportToHaveClass() var exception = await PlaywrightAssert.ThrowsAsync(() => Expect(locator).ToHaveClassAsync("kektus", new() { Timeout = 300 })); StringAssert.Contains("Locator expected to have class 'kektus'", exception.Message); StringAssert.Contains("But was: 'foo bar baz'", exception.Message); - StringAssert.Contains("LocatorAssertions.ToHaveClassAsync with timeout 300ms", exception.Message); + StringAssert.Contains("Expect \"ToHaveClassAsync\" with timeout 300ms", exception.Message); } { await Page.SetContentAsync("
"); @@ -567,7 +567,7 @@ public async Task ShouldSupportToContainClass() var exception = await PlaywrightAssert.ThrowsAsync(() => Expect(locator).ToContainClassAsync("does-not-exist", new() { Timeout = 300 })); StringAssert.Contains("Locator expected to contain class names 'does-not-exist'", exception.Message); StringAssert.Contains("But was: 'foo bar baz'", exception.Message); - StringAssert.Contains("LocatorAssertions.ToContainClassAsync with timeout 300ms", exception.Message); + StringAssert.Contains("Expect \"ToContainClassAsync\" with timeout 300ms", exception.Message); } { await Page.SetContentAsync("
"); diff --git a/src/Playwright.Tests/Assertions/PageAssertionsTests.cs b/src/Playwright.Tests/Assertions/PageAssertionsTests.cs index b9af896a6f..14b0497d1e 100644 --- a/src/Playwright.Tests/Assertions/PageAssertionsTests.cs +++ b/src/Playwright.Tests/Assertions/PageAssertionsTests.cs @@ -38,7 +38,7 @@ public async Task ShouldSupportToHaveTitleAsync() var exception = await PlaywrightAssert.ThrowsAsync(() => Expect(Page).ToHaveTitleAsync("Hello", new() { Timeout = 100 })); StringAssert.Contains("Page title expected to be 'Hello'", exception.Message); StringAssert.Contains("But was: 'Bye'", exception.Message); - StringAssert.Contains("PageAssertions.ToHaveTitleAsync with timeout 100ms", exception.Message); + StringAssert.Contains("Expect \"ToHaveTitleAsync\" with timeout 100ms", exception.Message); await Page.SetContentAsync("Foo Bar Kek"); await Expect(Page).ToHaveTitleAsync(new Regex("^Foo .* Kek$")); @@ -60,7 +60,7 @@ public async Task ShouldSupportToHaveURLAsync() var exception = await PlaywrightAssert.ThrowsAsync(() => Expect(Page).ToHaveURLAsync("wrong", new() { Timeout = 1000 })); StringAssert.Contains("Page URL expected to be 'wrong'", exception.Message); StringAssert.Contains("But was: 'data:text/html,
B
'", exception.Message); - StringAssert.Contains("PageAssertions.ToHaveURLAsync with timeout 1000ms", exception.Message); + StringAssert.Contains("Expect \"ToHaveURLAsync\" with timeout 1000ms", exception.Message); // Fail with Regex await Page.GotoAsync(Server.EmptyPage); diff --git a/src/Playwright.Tests/DefaultBrowsercontext2Tests.cs b/src/Playwright.Tests/DefaultBrowsercontext2Tests.cs index 2a4e107cdf..84d0e5be4a 100644 --- a/src/Playwright.Tests/DefaultBrowsercontext2Tests.cs +++ b/src/Playwright.Tests/DefaultBrowsercontext2Tests.cs @@ -310,6 +310,22 @@ public async Task ShouldRespectSelectors() tmp.Dispose(); } + [PlaywrightTest("defaultbrowsercontext-2.spec.ts", "exposes browser")] + public async Task ExposesBrowser() + { + var (tmp, context, page) = await LaunchAsync(); + var browser = context.Browser; + Assert.NotNull(browser); + var page2 = await browser.NewPageAsync(); + await page2.GotoAsync("data:text/html,Title"); + Assert.AreEqual("Title", await page2.TitleAsync()); + await browser.CloseAsync(); + Assert.AreEqual(0, context.Pages.Count); + // Next line should not throw. + await context.CloseAsync(); + tmp.Dispose(); + } + private async Task<(TempDirectory tmp, IBrowserContext context, IPage page)> LaunchAsync(BrowserTypeLaunchPersistentContextOptions options = null) { var tmp = new TempDirectory(); diff --git a/src/Playwright.Tests/PageAriaSnapshotTests.cs b/src/Playwright.Tests/PageAriaSnapshotTests.cs index 07562cb549..c945a0d11b 100644 --- a/src/Playwright.Tests/PageAriaSnapshotTests.cs +++ b/src/Playwright.Tests/PageAriaSnapshotTests.cs @@ -128,7 +128,7 @@ await Expect(Page.Locator("body")).ToMatchAriaSnapshotAsync(@" - listitem: ""Three"" ", new() { Timeout = 300 }); }); - StringAssert.Contains("LocatorAssertions.ToMatchAriaSnapshotAsync with timeout 300ms", exception.Message); + StringAssert.Contains("Expect \"ToMatchAriaSnapshotAsync\" with timeout 300ms", exception.Message); StringAssert.Contains("- unexpected value", exception.Message); } diff --git a/src/Playwright.Tests/PdfTests.cs b/src/Playwright.Tests/PdfTests.cs index d930541cf2..52cebed590 100644 --- a/src/Playwright.Tests/PdfTests.cs +++ b/src/Playwright.Tests/PdfTests.cs @@ -57,11 +57,6 @@ public async Task ShouldBeAbleToSaveFileWithoutPassingOptions() Assert.True(result.Length > 0); } - [PlaywrightTest("pdf.spec.ts", "should only have pdf in chromium")] - [Skip(SkipAttribute.Targets.Chromium)] - public Task ShouldOnlyHavePdfInChromium() - => PlaywrightAssert.ThrowsAsync(() => Page.PdfAsync()); - [PlaywrightTest("pdf.spec.ts", "should be able to generate outline")] [Skip(SkipAttribute.Targets.Firefox, SkipAttribute.Targets.Webkit)] public async Task ShouldBeAbleToGenerateOutline() diff --git a/src/Playwright.Tests/TracingTests.cs b/src/Playwright.Tests/TracingTests.cs index edb2e653d7..7adc1c428a 100644 --- a/src/Playwright.Tests/TracingTests.cs +++ b/src/Playwright.Tests/TracingTests.cs @@ -62,17 +62,17 @@ await Context.Tracing.StartAsync(new() Assert.AreEqual("context-options", events[0].Type); - string[] actualActionApiNames = GetActions(events); - string[] expectedActionApiNames = new string[] { "BrowserContext.NewPageAsync", "Page.GotoAsync", "Page.SetContentAsync", "Page.ClickAsync", "Mouse.MoveAsync", "Mouse.DblClickAsync", "Keyboard.InsertTextAsync", "Page.WaitForTimeoutAsync", "Page.CloseAsync" }; - Assert.AreEqual(expectedActionApiNames, actualActionApiNames); - - Assert.GreaterOrEqual(events.Where(e => e?.Title == "Page.GotoAsync").Count(), 1); - Assert.GreaterOrEqual(events.Where(e => e?.Title == "Page.SetContentAsync").Count(), 1); - Assert.GreaterOrEqual(events.Where(e => e?.Title == "Page.ClickAsync").Count(), 1); - Assert.GreaterOrEqual(events.Where(e => e?.Title == "Mouse.MoveAsync").Count(), 1); - Assert.GreaterOrEqual(events.Where(e => e?.Title == "Mouse.DblClickAsync").Count(), 1); - Assert.GreaterOrEqual(events.Where(e => e?.Title == "Keyboard.InsertTextAsync").Count(), 1); - Assert.GreaterOrEqual(events.Where(e => e?.Title == "Page.CloseAsync").Count(), 1); + string[] actualActionTitles = GetActions(events); + string[] expectedActionTitles = new string[] { "BrowserContext.newPage", "Frame.goto", "Frame.setContent", "Frame.click", "Page.mouseMove", "Double click", "Page.keyboardInsertText", "Frame.waitForTimeout", "Page.close" }; + Assert.AreEqual(expectedActionTitles, actualActionTitles); + + Assert.GreaterOrEqual(events.Where(e => e?.RenderedTitle() == "Frame.goto").Count(), 1); + Assert.GreaterOrEqual(events.Where(e => e?.RenderedTitle() == "Frame.setContent").Count(), 1); + Assert.GreaterOrEqual(events.Where(e => e?.RenderedTitle() == "Frame.click").Count(), 1); + Assert.GreaterOrEqual(events.Where(e => e?.RenderedTitle() == "Page.mouseMove").Count(), 1); + Assert.GreaterOrEqual(events.Where(e => e?.RenderedTitle() == "Double click").Count(), 1); + Assert.GreaterOrEqual(events.Where(e => e?.RenderedTitle() == "Page.keyboardInsertText").Count(), 1); + Assert.GreaterOrEqual(events.Where(e => e?.RenderedTitle() == "Page.close").Count(), 1); Assert.GreaterOrEqual(events.Where(x => x.Type == "frame-snapshot").Count(), 1); Assert.GreaterOrEqual(events.Where(x => x.Type == "screencast-frame").Count(), 1); @@ -101,21 +101,21 @@ public async Task ShouldCollectTwoTraces() { var (events, resources) = ParseTrace(trace1Path); Assert.AreEqual("context-options", events[0].Type); - Assert.GreaterOrEqual(events.Where(x => x?.Title == "Page.GotoAsync").Count(), 1); - Assert.GreaterOrEqual(events.Where(x => x?.Title == "Page.SetContentAsync").Count(), 1); - Assert.GreaterOrEqual(events.Where(x => x?.Title == "Page.ClickAsync").Count(), 1); - Assert.AreEqual(0, events.Where(x => x?.Title == "Page.CloseAsync").Count()); - Assert.AreEqual(0, events.Where(x => x?.Title == "Page.DblClickAsync").Count()); + Assert.GreaterOrEqual(events.Where(x => x?.RenderedTitle() == "Frame.goto").Count(), 1); + Assert.GreaterOrEqual(events.Where(x => x?.RenderedTitle() == "Frame.setContent").Count(), 1); + Assert.GreaterOrEqual(events.Where(x => x?.RenderedTitle() == "Frame.click").Count(), 1); + Assert.AreEqual(0, events.Where(x => x?.RenderedTitle() == "Page.close").Count()); + Assert.AreEqual(0, events.Where(x => x?.RenderedTitle() == "Frame.dblclick").Count()); } { var (events, resources) = ParseTrace(trace2Path); Assert.AreEqual("context-options", events[0].Type); - Assert.AreEqual(0, events.Where(x => x?.Title == "Page.GottoAsync").Count()); - Assert.AreEqual(0, events.Where(x => x?.Title == "Page.SetContentAsync").Count()); - Assert.AreEqual(0, events.Where(x => x?.Title == "Page.ClickAsync").Count()); - Assert.GreaterOrEqual(events.Where(x => x?.Title == "Page.CloseAsync").Count(), 1); - Assert.GreaterOrEqual(events.Where(x => x?.Title == "Page.DblClickAsync").Count(), 1); + Assert.AreEqual(0, events.Where(x => x?.RenderedTitle() == "Page.GottoAsync").Count()); + Assert.AreEqual(0, events.Where(x => x?.RenderedTitle() == "Frame.setContent").Count()); + Assert.AreEqual(0, events.Where(x => x?.RenderedTitle() == "Frame.click").Count()); + Assert.GreaterOrEqual(events.Where(x => x?.RenderedTitle() == "Page.close").Count(), 1); + Assert.GreaterOrEqual(events.Where(x => x?.RenderedTitle() == "Frame.dblclick").Count(), 1); } } @@ -153,13 +153,13 @@ await Context.Tracing.StartAsync(new() { var (events, resources) = ParseTrace(traceFile1); Assert.AreEqual("context-options", events[0].Type); - string[] actualActionApiNames = GetActions(events); - string[] expectedActionApiNames = new string[] { - "Page.SetContentAsync", - "Page.ClickAsync", - "Page.ClickAsync" - }; - Assert.AreEqual(expectedActionApiNames, actualActionApiNames); + string[] actualActionTitles = GetActions(events); + string[] expectedActionTitles = new string[] { + "Frame.setContent", + "Frame.click", + "Frame.click" + }; + Assert.AreEqual(expectedActionTitles, actualActionTitles); Assert.GreaterOrEqual(events.Where(x => x.Type == "frame-snapshot").Count(), 1); Assert.GreaterOrEqual(events.Where(x => x.Type == "resource-snapshot").Count(), 1); @@ -167,11 +167,11 @@ await Context.Tracing.StartAsync(new() { var (events, resources) = ParseTrace(traceFile2); Assert.AreEqual("context-options", events[0].Type); - string[] actualActionApiNames = GetActions(events); - string[] expectedActionApiNames = new string[] { - "Page.HoverAsync" - }; - Assert.AreEqual(expectedActionApiNames, actualActionApiNames); + string[] actualActionTitles = GetActions(events); + string[] expectedActionTitles = new string[] { + "Frame.hover" + }; + Assert.AreEqual(expectedActionTitles, actualActionTitles); Assert.GreaterOrEqual(events.Where(x => x.Type == "frame-snapshot").Count(), 1); Assert.GreaterOrEqual(events.Where(x => x.Type == "resource-snapshot").Count(), 1); @@ -223,7 +223,7 @@ await Context.Tracing.StartAsync(new() } [PlaywrightTest()] - public async Task ShouldSendDotNetApiNames() + public async Task ShouldSendDotNetTitles() { await Context.Tracing.StartAsync(new() { @@ -249,19 +249,18 @@ await Context.Tracing.StartAsync(new() var (events, resources) = ParseTrace(tracePath); CollectionAssert.IsNotEmpty(events); - string[] actualActionApiNames = GetActions(events); - string[] expectedActionApiNames = new string[] { - "BrowserContext.NewPageAsync", - "Page.GotoAsync", - "Page.SetContentAsync", - "BrowserContext.RunAndWaitForPageAsync", - "Page.ClickAsync", - "Page.EvaluateAsync", - "Page.RouteAsync", - "Page.GotoAsync", - "Page.GotoAsync" + string[] actualActionTitles = GetActions(events); + string[] expectedActionTitles = new string[] { + "BrowserContext.newPage", + "Frame.goto", + "Frame.setContent", + "BrowserContext.waitForEventInfo", + "Frame.click", + "Frame.evaluateExpression", + "Frame.goto", + "Frame.goto" }; - Assert.AreEqual(expectedActionApiNames, actualActionApiNames); + Assert.AreEqual(expectedActionTitles, actualActionTitles); } [PlaywrightTest()] @@ -281,13 +280,13 @@ public async Task ShouldDisplayWaitForLoadStateEvenIfDidNotWaitForIt() var (events, resources) = ParseTrace(tracePath); CollectionAssert.IsNotEmpty(events); - string[] actualActionApiNames = GetActions(events); - string[] expectedActionApiNames = new string[] { - "Page.GotoAsync", - "Page.WaitForLoadStateAsync", - "Page.WaitForLoadStateAsync" + string[] actualActionTitles = GetActions(events); + string[] expectedActionTitles = new string[] { + "Frame.goto", + "Page.waitForEventInfo", + "Page.waitForEventInfo" }; - Assert.AreEqual(expectedActionApiNames, actualActionApiNames); + Assert.AreEqual(expectedActionTitles, actualActionTitles); } [PlaywrightTest("tracing.spec.ts", "should respect tracesDir and name")] @@ -322,13 +321,13 @@ string[] ResourceNames(Dictionary resources) { var (events, resources) = ParseTrace(Path.Combine(tracesDir.Path, "trace1.zip")); - Assert.AreEqual(new[] { "Page.GotoAsync" }, GetActions(events)); + Assert.AreEqual(new[] { "Frame.goto" }, GetActions(events)); Assert.AreEqual(new[] { "resources/XXX.css", "resources/XXX.html", "trace.network", "trace.stacks", "trace.trace" }, ResourceNames(resources)); } { var (events, resources) = ParseTrace(Path.Combine(tracesDir.Path, "trace2.zip")); - Assert.AreEqual(new[] { "Page.GotoAsync" }, GetActions(events)); + Assert.AreEqual(new[] { "Frame.goto" }, GetActions(events)); Assert.AreEqual(new[] { "resources/XXX.css", "resources/XXX.html", "resources/XXX.html", "trace.network", "trace.stacks", "trace.trace" }, ResourceNames(resources)); } } @@ -357,13 +356,13 @@ public async Task ShouldShowTracingGroupInActionList() var actions = GetActions(events); Assert.AreEqual(new[] { - "BrowserContext.NewPageAsync", + "BrowserContext.newPage", "outer group", - "Page.GotoAsync", + "Frame.goto", "inner group 1", - "Locator.ClickAsync", + "Frame.click", "inner group 2", - "LocatorAssertions.ToBeVisibleAsync" + "Expect \"ToBeVisibleAsync\"" }, actions); } @@ -416,10 +415,20 @@ private class TraceEventEntry { public string Type { get; set; } public string Title { get; set; } - public string ApiName { get; set; } public TraceEventError Error { get; set; } public double StartTime { get; set; } public string CallID { get; set; } + public string Class { get; set; } + public string Method { get; set; } + + public string RenderedTitle() + { + if (string.IsNullOrEmpty(Title)) + { + return $"{Class}.{Method}"; + } + return Title; + } } private class TraceEventError @@ -429,5 +438,5 @@ private class TraceEventError public string Message { get; set; } } - string[] GetActions(IReadOnlyList events) => events.Where(action => action.Type == "action").OrderBy(action => action.StartTime).Select(action => action.Title).ToArray(); + string[] GetActions(IReadOnlyList events) => events.Where(action => action.Type == "action").OrderBy(action => action.StartTime).Select(action => action.RenderedTitle()).ToArray(); } diff --git a/src/Playwright/API/Generated/ILocator.cs b/src/Playwright/API/Generated/ILocator.cs index c403a7aa0b..32a415875c 100644 --- a/src/Playwright/API/Generated/ILocator.cs +++ b/src/Playwright/API/Generated/ILocator.cs @@ -346,6 +346,11 @@ public partial interface ILocator /// Describes the locator, description is used in the trace viewer and reports. Returns /// the locator pointing to the same element. /// + /// **Usage** + /// + /// var button = Page.GetByTestId("btn-sub").Describe("Subscribe button");
+ /// await button.ClickAsync(); + ///
/// /// Locator description. ILocator Describe(string description); diff --git a/src/Playwright/API/Generated/IWebSocketRoute.cs b/src/Playwright/API/Generated/IWebSocketRoute.cs index 139b132fbe..293a8cf951 100644 --- a/src/Playwright/API/Generated/IWebSocketRoute.cs +++ b/src/Playwright/API/Generated/IWebSocketRoute.cs @@ -37,7 +37,7 @@ namespace Microsoft.Playwright; /// **Mocking** /// /// By default, the routed WebSocket will not connect to the server. This way, you can -/// mock entire communcation over the WebSocket. Here is an example that responds to +/// mock entire communication over the WebSocket. Here is an example that responds to /// a "request" with a "response". /// /// diff --git a/src/Playwright/Core/AssertionsBase.cs b/src/Playwright/Core/AssertionsBase.cs index d3a6fbb981..d53e1ef9dc 100644 --- a/src/Playwright/Core/AssertionsBase.cs +++ b/src/Playwright/Core/AssertionsBase.cs @@ -52,20 +52,20 @@ public AssertionsBase(ILocator actual, bool isNot) protected Locator ActualLocator { get; } - protected async Task ExpectImplAsync(string expression, ExpectedTextValue textValue, object expected, string message, FrameExpectOptions options) + protected async Task ExpectImplAsync(string expression, ExpectedTextValue textValue, object expected, string message, string title, FrameExpectOptions options) { - await ExpectImplAsync(expression, new ExpectedTextValue[] { textValue }, expected, message, options).ConfigureAwait(false); + await ExpectImplAsync(expression, new ExpectedTextValue[] { textValue }, expected, message, title, options).ConfigureAwait(false); } - protected async Task ExpectImplAsync(string expression, ExpectedTextValue[]? expectedText, object? expected, string message, FrameExpectOptions options) + protected async Task ExpectImplAsync(string expression, ExpectedTextValue[]? expectedText, object? expected, string message, string title, FrameExpectOptions options) { options ??= new(); options.ExpectedText = expectedText; options.IsNot = IsNot; - await ExpectImplAsync(expression, options, expected, message).ConfigureAwait(false); + await ExpectImplAsync(expression, options, expected, message, title).ConfigureAwait(false); } - protected async Task ExpectImplAsync(string expression, FrameExpectOptions expectOptions, object? expected, string message) + protected async Task ExpectImplAsync(string expression, FrameExpectOptions expectOptions, object? expected, string message, string title) { if (expectOptions.Timeout == null) { @@ -75,7 +75,7 @@ protected async Task ExpectImplAsync(string expression, FrameExpectOptions expec { message = message.Replace("expected to", "expected not to"); } - var result = await ActualLocator.ExpectAsync(expression, expectOptions).ConfigureAwait(false); + var result = await ActualLocator.ExpectAsync(expression, expectOptions, title).ConfigureAwait(false); if (result.Matches == IsNot) { var actual = result.Received; diff --git a/src/Playwright/Core/Browser.cs b/src/Playwright/Core/Browser.cs index 93949634ee..f12a4b19ae 100644 --- a/src/Playwright/Core/Browser.cs +++ b/src/Playwright/Core/Browser.cs @@ -28,7 +28,6 @@ using System.Linq; using System.Runtime.CompilerServices; using System.Text.Json; -using System.Text.RegularExpressions; using System.Threading.Tasks; using Microsoft.Playwright.Helpers; using Microsoft.Playwright.Transport; @@ -41,6 +40,7 @@ internal class Browser : ChannelOwner, IBrowser private readonly BrowserInitializer _initializer; private readonly TaskCompletionSource _closedTcs = new(); internal readonly List _contexts = new(); + internal string? _tracesDir = null; internal BrowserType _browserType = null!; internal string? _closeReason; @@ -66,6 +66,9 @@ internal override void OnMessage(string method, JsonElement serverParams) { switch (method) { + case "context": + DidCreateContext(serverParams.GetProperty("context").ToObject(_connection.DefaultJsonSerializerOptions)!); + break; case "close": DidClose(); break; @@ -123,19 +126,13 @@ public async Task NewContextAsync(BrowserNewContextOptions? opt ["forcedColors"] = options.ForcedColors == ForcedColors.Null ? "no-override" : options.ForcedColors, ["contrast"] = options.Contrast == Contrast.Null ? "no-override" : options.Contrast, ["extraHTTPHeaders"] = options.ExtraHTTPHeaders?.Select(kv => new HeaderEntry { Name = kv.Key, Value = kv.Value }).ToArray(), - ["recordHar"] = PrepareHarOptions( - recordHarContent: options.RecordHarContent, - recordHarMode: options.RecordHarMode, - recordHarPath: options.RecordHarPath, - recordHarOmitContent: options.RecordHarOmitContent, - recordHarUrlFilter: options.RecordHarUrlFilter, - recordHarUrlFilterString: options.RecordHarUrlFilterString, - recordHarUrlFilterRegex: options.RecordHarUrlFilterRegex), ["recordVideo"] = GetVideoArgs(options.RecordVideoDir, options.RecordVideoSize), ["timezoneId"] = options.TimezoneId, ["userAgent"] = options.UserAgent, ["baseURL"] = options.BaseURL, ["clientCertificates"] = ToClientCertificatesProtocol(options.ClientCertificates), + ["selectorEngines"] = _browserType.Playwright._selectors._selectorEngines, + ["testIdAttributeName"] = _browserType.Playwright._selectors._testIdAttributeName, }; if (options.AcceptDownloads.HasValue) @@ -170,8 +167,7 @@ public async Task NewContextAsync(BrowserNewContextOptions? opt } var context = await SendMessageToServerAsync("newContext", args).ConfigureAwait(false); - - _browserType.DidCreateContext(context, options, null); + await context.InitializeHarFromOptionsAsync(options).ConfigureAwait(false); return context; } @@ -221,16 +217,17 @@ public async Task NewPageAsync(BrowserNewPageOptions? options = default) ClientCertificates = options.ClientCertificates, }; - var context = (BrowserContext)await NewContextAsync(contextOptions).ConfigureAwait(false); - - return await WrapApiCallAsync(async () => + return await WrapApiCallAsync( + async () => { + var context = (BrowserContext)await NewContextAsync(contextOptions).ConfigureAwait(false); var page = (Page)await context.NewPageAsync().ConfigureAwait(false); page.OwnedContext = context; - context.Options = contextOptions; context.OwnerPage = page; return page; - }).ConfigureAwait(false); + }, + false, + "Create page").ConfigureAwait(false); } [MethodImpl(MethodImplOptions.NoInlining)] @@ -261,6 +258,32 @@ public async Task NewPageAsync(BrowserNewPageOptions? options = default) return recordVideoArgs; } + internal void ConnectToBrowserType(BrowserType browserType, string? tracesDir) + { + // Note: when using connect(), `browserType` is different from `this.parent`. + // This is why browser type is not wired up in the constructor, and instead this separate method is called later on. + _browserType = browserType; + _tracesDir = tracesDir; + foreach (var context in _contexts) + { + context._tracing._tracesDir = this._tracesDir; + browserType.Playwright._selectors._contextsForSelectors.Add(context); + } + } + + private void DidCreateContext(BrowserContext context) + { + context._browser = this; + _contexts.Add(context); + // Note: when connecting to a browser, initial contexts arrive before `_browserType` is set, + // and will be configured later in `ConnectToBrowserType`. + if (_browserType != null) + { + context._tracing._tracesDir = _tracesDir; + _browserType.Playwright._selectors._contextsForSelectors.Add(context); + } + } + internal void DidClose() { IsConnected = false; @@ -273,54 +296,6 @@ public async Task NewBrowserCDPSessionAsync() => await SendMessageToServerAsync( "newBrowserCDPSession").ConfigureAwait(false); - internal static Dictionary? PrepareHarOptions( - HarContentPolicy? recordHarContent, - HarMode? recordHarMode, - string? recordHarPath, - bool? recordHarOmitContent, - string? recordHarUrlFilter, - string? recordHarUrlFilterString, - Regex? recordHarUrlFilterRegex) - { - if (string.IsNullOrEmpty(recordHarPath)) - { - return null; - } - var recordHarArgs = new Dictionary(); - recordHarArgs["path"] = recordHarPath; - if (recordHarContent.HasValue) - { - recordHarArgs["content"] = recordHarContent; - } - else if (recordHarOmitContent == true) - { - recordHarArgs["content"] = HarContentPolicy.Omit; - } - if (!string.IsNullOrEmpty(recordHarUrlFilter)) - { - recordHarArgs["urlGlob"] = recordHarUrlFilter; - } - else if (!string.IsNullOrEmpty(recordHarUrlFilterString)) - { - recordHarArgs["urlGlob"] = recordHarUrlFilterString; - } - else if (recordHarUrlFilterRegex != null) - { - recordHarArgs["urlRegexSource"] = recordHarUrlFilterRegex.ToString(); - recordHarArgs["urlRegexFlags"] = recordHarUrlFilterRegex.Options.GetInlineFlags(); - } - if (recordHarMode.HasValue) - { - recordHarArgs["mode"] = recordHarMode; - } - - if (recordHarArgs.Keys.Count > 0) - { - return recordHarArgs; - } - return null; - } - internal static Dictionary[]? ToClientCertificatesProtocol(IEnumerable? clientCertificates) { if (clientCertificates == null) diff --git a/src/Playwright/Core/BrowserContext.cs b/src/Playwright/Core/BrowserContext.cs index 92683f3f62..50436cd842 100644 --- a/src/Playwright/Core/BrowserContext.cs +++ b/src/Playwright/Core/BrowserContext.cs @@ -43,7 +43,7 @@ internal class BrowserContext : ChannelOwner, IBrowserContext private readonly TaskCompletionSource _closeTcs = new(); private readonly Dictionary _bindings = new(); private readonly BrowserContextInitializer _initializer; - private readonly Tracing _tracing; + internal readonly Tracing _tracing; private readonly Clock _clock; internal readonly HashSet _backgroundPages = new(); internal readonly APIRequestContext _request; @@ -52,7 +52,8 @@ internal class BrowserContext : ChannelOwner, IBrowserContext private readonly List _webSocketRoutes = new(); private List _routes = new(); internal readonly List _pages = new(); - private readonly Browser? _browser; + // Browser is null for browser contexts created outside of normal browser, e.g. android or electron. + internal Browser? _browser; private readonly List _harRouters = new(); private string? _closeReason; @@ -60,9 +61,6 @@ internal class BrowserContext : ChannelOwner, IBrowserContext internal BrowserContext(ChannelOwner parent, string guid, BrowserContextInitializer initializer) : base(parent, guid) { - _browser = parent as Browser; - _browser?._contexts.Add(this); - _tracing = initializer.Tracing; _clock = new Clock(this); _request = initializer.RequestContext; @@ -138,11 +136,9 @@ public event EventHandler RequestFailed internal Page? OwnerPage { get; set; } - internal bool IsChromium => _initializer.IsChromium; - - internal BrowserNewContextOptions Options { get; set; } = new(); + internal Options Options => _initializer.Options; - internal bool CloseWasCalled { get; private set; } + internal bool ClosingOrClosed { get; private set; } public IAPIRequestContext APIRequest => _request; @@ -267,6 +263,24 @@ internal void OnDialog(Dialog dialog) } } + internal async Task InitializeHarFromOptionsAsync(BrowserNewContextOptions options) + { + if (options.RecordHarPath.IsNullOrEmpty()) + { + return; + } + var defaultPolicy = options.RecordHarPath.EndsWith(".zip") ? HarContentPolicy.Attach : HarContentPolicy.Embed; + var contentPolicy = options.RecordHarContent ?? (options.RecordHarOmitContent == true ? HarContentPolicy.Omit : defaultPolicy); + var routeFromHAROptions = new BrowserContextRouteFromHAROptions() + { + Url = options.RecordHarUrlFilter, + UrlString = options.RecordHarUrlFilter, + UrlRegex = options.RecordHarUrlFilterRegex, + UpdateMode = options.RecordHarMode ?? HarMode.Full, + }; + await RecordIntoHarAsync(options.RecordHarPath, null, routeFromHAROptions, contentPolicy).ConfigureAwait(false); + } + [MethodImpl(MethodImplOptions.NoInlining)] public Task AddCookiesAsync(IEnumerable cookies) => SendMessageToServerAsync( "addCookies", @@ -316,18 +330,13 @@ public async Task ClearCookiesAsync(BrowserContextClearCookiesOptions? options = [MethodImpl(MethodImplOptions.NoInlining)] public async Task CloseAsync(BrowserContextCloseOptions? options = default) { - if (CloseWasCalled) + if (ClosingOrClosed) { return; } _closeReason = options?.Reason; - CloseWasCalled = true; - await WrapApiCallAsync( - async () => - { - await _request.DisposeAsync(options?.Reason).ConfigureAwait(false); - }, - true).ConfigureAwait(false); + ClosingOrClosed = true; + await _request.DisposeAsync(options?.Reason).ConfigureAwait(false); await WrapApiCallAsync( async () => { @@ -362,16 +371,6 @@ await WrapApiCallAsync( await _closeTcs.Task.ConfigureAwait(false); } - internal void SetOptions(BrowserNewContextOptions contextOptions, string? tracesDir) - { - Options = contextOptions; - if (!string.IsNullOrEmpty(Options?.RecordHarPath) && Options != null) - { - _harRecorders.Add(string.Empty, new(Options.RecordHarPath!, Options.RecordHarContent)); - } - _tracing._tracesDir = tracesDir; - } - [MethodImpl(MethodImplOptions.NoInlining)] public Task> CookiesAsync() => CookiesAsync(Array.Empty()); @@ -613,7 +612,7 @@ internal async Task InnerWaitForEventAsync(PlaywrightEvent playwrightEv var result = waiter.WaitForEventAsync(this, playwrightEvent.Name, predicate); if (action != null) { - await WrapApiBoundaryAsync(() => waiter.CancelWaitOnExceptionAsync(result, action)).ConfigureAwait(false); + await waiter.CancelWaitOnExceptionAsync(result, action).ConfigureAwait(false); } return await result.ConfigureAwait(false); @@ -662,7 +661,7 @@ internal async Task OnRouteAsync(Route route) foreach (var routeHandler in routeHandlers) { // If the page or the context was closed we stall all requests right away. - if (page?.CloseWasCalled == true || CloseWasCalled) + if (page?.CloseWasCalled == true || ClosingOrClosed) { return; } @@ -681,7 +680,7 @@ internal async Task OnRouteAsync(Route route) var handled = await routeHandler.HandleAsync(route).ConfigureAwait(false); if (_routes.Count == 0) { - await UpdateInterceptionAsync().ConfigureAwait(false); + UpdateInterceptionAsync().IgnoreException(); } if (handled) { @@ -783,9 +782,11 @@ await SendMessageToServerAsync( internal void OnClose() { + ClosingOrClosed = true; if (Browser != null) { ((Browser)Browser)._contexts.Remove(this); + ((Browser)Browser)._browserType.Playwright._selectors._contextsForSelectors.Remove(this); } DisposeHarRouters(); @@ -855,13 +856,29 @@ await SendMessageToServerAsync( } } - internal async Task RecordIntoHarAsync(string har, Page? page, BrowserContextRouteFromHAROptions options) + internal async Task RecordIntoHarAsync(string har, Page? page, BrowserContextRouteFromHAROptions options, HarContentPolicy? contentPolicy) { - var contentPolicy = RouteFromHarUpdateContentPolicyToHarContentPolicy(options?.UpdateContent); + contentPolicy = contentPolicy ?? RouteFromHarUpdateContentPolicyToHarContentPolicy(options?.UpdateContent); + var urlGlob = options?.Url ?? options?.UrlString; + + var recordHarArgs = new Dictionary(); + recordHarArgs["zip"] = har.EndsWith(".zip"); + recordHarArgs["content"] = contentPolicy ?? HarContentPolicy.Attach; + if (!string.IsNullOrEmpty(urlGlob)) + { + recordHarArgs["urlGlob"] = urlGlob; + } + if (options?.UrlRegex != null) + { + recordHarArgs["urlRegexSource"] = options?.UrlRegex.ToString(); + recordHarArgs["urlRegexFlags"] = options?.UrlRegex.Options.GetInlineFlags(); + } + recordHarArgs["mode"] = options?.UpdateMode ?? HarMode.Minimal; + var harId = (await SendMessageToServerAsync("harStart", new Dictionary { { "page", page }, - { "options", Core.Browser.PrepareHarOptions(contentPolicy ?? HarContentPolicy.Attach, options?.UpdateMode ?? HarMode.Minimal, har, null, options?.Url, options?.UrlString, options?.UrlRegex) }, + { "options", recordHarArgs }, }).ConfigureAwait(false)).Value.GetProperty("harId").ToString(); _harRecorders.Add(harId, new(har, contentPolicy ?? HarContentPolicy.Attach)); } @@ -871,7 +888,7 @@ public async Task RouteFromHARAsync(string har, BrowserContextRouteFromHAROption { if (options?.Update == true) { - await RecordIntoHarAsync(har, null, options).ConfigureAwait(false); + await RecordIntoHarAsync(har, null, options, null).ConfigureAwait(false); return; } var harRouter = await HarRouter.CreateAsync(_connection.LocalUtils!, har, options?.NotFound ?? HarNotFound.Abort, new() diff --git a/src/Playwright/Core/BrowserType.cs b/src/Playwright/Core/BrowserType.cs index 61b97373dd..dac2432641 100644 --- a/src/Playwright/Core/BrowserType.cs +++ b/src/Playwright/Core/BrowserType.cs @@ -78,7 +78,7 @@ public async Task LaunchAsync(BrowserTypeLaunchOptions? options = defa { "slowMo", options.SlowMo }, { "timeout", TimeoutSettings.LaunchTimeout(options.Timeout) }, }).ConfigureAwait(false); - DidLaunchBrowser(browser); + browser.ConnectToBrowserType(this, options.TracesDir); return browser; } @@ -132,15 +132,9 @@ public async Task LaunchPersistentContextAsync(string userDataD ["ignoreDefaultArgs"] = options.IgnoreDefaultArgs, ["ignoreAllDefaultArgs"] = options.IgnoreAllDefaultArgs, ["baseURL"] = options.BaseURL, - ["recordHar"] = Browser.PrepareHarOptions( - recordHarContent: options.RecordHarContent, - recordHarMode: options.RecordHarMode, - recordHarPath: options.RecordHarPath, - recordHarOmitContent: options.RecordHarOmitContent, - recordHarUrlFilter: options.RecordHarUrlFilter, - recordHarUrlFilterString: options.RecordHarUrlFilterString, - recordHarUrlFilterRegex: options.RecordHarUrlFilterRegex), ["clientCertificates"] = Browser.ToClientCertificatesProtocol(options.ClientCertificates), + ["selectorEngines"] = Playwright._selectors._selectorEngines, + ["testIdAttributeName"] = Playwright._selectors._testIdAttributeName, }; if (options.AcceptDownloads.HasValue) @@ -157,13 +151,20 @@ public async Task LaunchPersistentContextAsync(string userDataD channelArgs.Add("viewport", options.ViewportSize); } - var context = await SendMessageToServerAsync("launchPersistentContext", channelArgs).ConfigureAwait(false); - - DidCreateContext( - context, - ClassUtils.Clone(options), - options?.TracesDir); - + JsonElement result = await SendMessageToServerAsync("launchPersistentContext", channelArgs).ConfigureAwait(false); + var browser = result.GetProperty("browser").ToObject(_connection.DefaultJsonSerializerOptions)!; + browser.ConnectToBrowserType(this, options.TracesDir); + var context = result.GetProperty("context").ToObject(_connection.DefaultJsonSerializerOptions)!; + await context.InitializeHarFromOptionsAsync(new() + { + RecordHarContent = options.RecordHarContent, + RecordHarMode = options.RecordHarMode, + RecordHarOmitContent = options.RecordHarOmitContent, + RecordHarPath = options.RecordHarPath, + RecordHarUrlFilter = options.RecordHarUrlFilter, + RecordHarUrlFilterRegex = options.RecordHarUrlFilterRegex, + RecordHarUrlFilterString = options.RecordHarUrlFilterString, + }).ConfigureAwait(false); return context; } @@ -247,11 +248,11 @@ async Task CreateBrowserAsync() ClosePipe(); throw new ArgumentException("Malformed endpoint. Did you use launchServer method?"); } - playwright.SetSelectors(this.Playwright._selectors); + playwright._selectors = this.Playwright._selectors; browser = playwright.PreLaunchedBrowser; browser.ShouldCloseConnectionOnClose = true; browser.Disconnected += (_, _) => ClosePipe(); - DidLaunchBrowser(browser); + browser.ConnectToBrowserType(this, null); return playwright.PreLaunchedBrowser; } var task = CreateBrowserAsync(); @@ -274,22 +275,7 @@ public async Task ConnectOverCDPAsync(string endpointURL, BrowserTypeC { "timeout", TimeoutSettings.LaunchTimeout(options.Timeout) }, }).ConfigureAwait(false); Browser browser = result.GetProperty("browser").ToObject(_connection.DefaultJsonSerializerOptions); - DidLaunchBrowser(browser); - if (result.TryGetProperty("defaultContext", out JsonElement defaultContextValue)) - { - var defaultContext = defaultContextValue.ToObject(_connection.DefaultJsonSerializerOptions); - DidCreateContext(defaultContext, new(), null); - } + browser.ConnectToBrowserType(this, null); return browser; } - - internal void DidLaunchBrowser(Browser browser) - { - browser._browserType = this; - } - - internal void DidCreateContext(BrowserContext context, BrowserNewContextOptions contextOptions, string? tracesDir) - { - context.SetOptions(contextOptions, tracesDir); - } } diff --git a/src/Playwright/Core/Frame.cs b/src/Playwright/Core/Frame.cs index a71e17dfc1..9f402cba03 100644 --- a/src/Playwright/Core/Frame.cs +++ b/src/Playwright/Core/Frame.cs @@ -279,8 +279,7 @@ await waiter.WaitForEventAsync(this, "LoadState", s => if (action != null) { - await WrapApiBoundaryAsync(() => waiter.CancelWaitOnExceptionAsync(result, action)) - .ConfigureAwait(false); + await waiter.CancelWaitOnExceptionAsync(result, action).ConfigureAwait(false); } return await result.ConfigureAwait(false); diff --git a/src/Playwright/Core/LocalUtils.cs b/src/Playwright/Core/LocalUtils.cs index b6505904a8..57f52e1299 100644 --- a/src/Playwright/Core/LocalUtils.cs +++ b/src/Playwright/Core/LocalUtils.cs @@ -36,7 +36,6 @@ internal class LocalUtils : ChannelOwner public LocalUtils(ChannelOwner parent, string guid, LocalUtilsInitializer initializer) : base(parent, guid) { - MarkAsInternalType(); foreach (var entry in initializer.DeviceDescriptors) { _devices[entry.Name] = entry.Descriptor; diff --git a/src/Playwright/Core/Locator.cs b/src/Playwright/Core/Locator.cs index 0df35a2678..5a7f88b3a8 100644 --- a/src/Playwright/Core/Locator.cs +++ b/src/Playwright/Core/Locator.cs @@ -134,7 +134,8 @@ public Locator(Frame parent, string selector, LocatorLocatorOptions? options = n Y = bb.Y, }; }, - options?.Timeout); + options?.Timeout, + "Bounding box"); public Task CheckAsync(LocatorCheckOptions? options = null) => _frame.CheckAsync( @@ -161,19 +162,20 @@ public Task DragToAsync(ILocator target, LocatorDragToOptions? options = null) public Task EvaluateAsync(string expression, object? arg = null, LocatorEvaluateOptions? options = null) => WithElementAsync( (h, _) => h.EvaluateAsync(expression, arg), - options?.Timeout); + options?.Timeout, + "Evaluate"); public Task EvaluateAllAsync(string expression, object? arg = null) => _frame.EvalOnSelectorAllAsync(_selector, expression, arg); public Task EvaluateHandleAsync(string expression, object? arg = null, LocatorEvaluateHandleOptions? options = null) - => WithElementAsync((e, _) => e.EvaluateHandleAsync(expression, arg), options?.Timeout); + => WithElementAsync((e, _) => e.EvaluateHandleAsync(expression, arg), options?.Timeout, "Evaluate"); public Task FillAsync(string value, LocatorFillOptions? options = null) => _frame.FillAsync(_selector, value, ConvertOptions(options)); public Task ClearAsync(LocatorClearOptions? options = null) - => _frame.FillAsync(_selector, string.Empty, ConvertOptions(options)); + => this._frame.WrapApiCallAsync(() => _frame.FillAsync(_selector, string.Empty, ConvertOptions(options)), false, "Clear"); public Task HighlightAsync() => _frame.HighlightAsync(_selector); @@ -291,10 +293,10 @@ public Task PressAsync(string key, LocatorPressOptions? options = null) => _frame.PressAsync(_selector, key, ConvertOptions(options)); public Task ScreenshotAsync(LocatorScreenshotOptions? options = null) - => WithElementAsync((h, timeout) => h.ScreenshotAsync(ConvertOptions(options, new() { Timeout = timeout })), options?.Timeout); + => WithElementAsync((h, timeout) => h.ScreenshotAsync(ConvertOptions(options, new() { Timeout = timeout })), options?.Timeout, "Screenshot"); public Task ScrollIntoViewIfNeededAsync(LocatorScrollIntoViewIfNeededOptions? options = null) - => WithElementAsync((h, timeout) => h.ScrollIntoViewIfNeededAsync(ConvertOptions(options, new() { Timeout = timeout })), options?.Timeout); + => WithElementAsync((h, timeout) => h.ScrollIntoViewIfNeededAsync(ConvertOptions(options, new() { Timeout = timeout })), options?.Timeout, "Scroll into view"); public Task> SelectOptionAsync(string values, LocatorSelectOptionOptions? options = null) => _frame.SelectOptionAsync(_selector, values, ConvertOptions(options)); @@ -315,7 +317,7 @@ public Task> SelectOptionAsync(IEnumerable _frame.SelectOptionAsync(_selector, values, ConvertOptions(options)); public Task SelectTextAsync(LocatorSelectTextOptions? options = null) - => WithElementAsync((h, timeout) => h.SelectTextAsync(ConvertOptions(options, new() { Timeout = timeout })), options?.Timeout); + => WithElementAsync((h, timeout) => h.SelectTextAsync(ConvertOptions(options, new() { Timeout = timeout })), options?.Timeout, "Select text"); public Task SetCheckedAsync(bool @checked, LocatorSetCheckedOptions? options = null) => @checked ? @@ -367,11 +369,11 @@ public Task WaitForAsync(LocatorWaitForOptions? options = null) }); } - internal Task ExpectAsync(string expression, FrameExpectOptions options) - => _frame.ExpectAsync( - _selector, - expression, - options); + internal Task ExpectAsync(string expression, FrameExpectOptions options, string? title) + => this._frame.WrapApiCallAsync( + () => _frame.ExpectAsync(_selector, expression, options), + false, + title); public override string ToString() => "Locator@" + _selector; @@ -397,12 +399,13 @@ private T ConvertOptions(object? source, T? inheritFrom = default) return target; } - private Task WithElementAsync(Func> task, float? timeout) + private Task WithElementAsync(Func> task, float? timeout, string title) { timeout = this._frame.Timeout(timeout); long? deadline = timeout.HasValue ? Stopwatch.GetTimestamp() + (long)(timeout.Value * 1000 * Stopwatch.Frequency / 1000) : null; - return this._frame.WrapApiCallAsync(async () => + return this._frame.WrapApiCallAsync( + async () => { var handle = await _frame.SendMessageToServerAsync("waitForSelector", new Dictionary { @@ -424,10 +427,12 @@ private Task WithElementAsync(Func callback, float? timeout) + private async Task WithElementAsync(Func callback, float? timeout, string title) { await WithElementAsync( async (h, t) => @@ -435,7 +440,8 @@ await WithElementAsync( await callback(h, t).ConfigureAwait(false); return true; }, - timeout).ConfigureAwait(false); + timeout, + title).ConfigureAwait(false); } public ILocator Describe(string description) diff --git a/src/Playwright/Core/LocatorAssertions.cs b/src/Playwright/Core/LocatorAssertions.cs index 3bb7b34df7..6b0a5ee8b4 100644 --- a/src/Playwright/Core/LocatorAssertions.cs +++ b/src/Playwright/Core/LocatorAssertions.cs @@ -41,7 +41,7 @@ public LocatorAssertions(ILocator locator, bool isNot) : base(locator, isNot) public Task ToBeAttachedAsync(LocatorAssertionsToBeAttachedOptions? options = null) { var attached = options == null || options.Attached == null || options.Attached == true; - return ExpectTrueAsync(attached ? "to.be.attached" : "to.be.detached", $"Locator expected {(!attached ? "not " : string.Empty)}to be attached", ConvertToFrameExpectOptions(options)); + return ExpectTrueAsync(attached ? "to.be.attached" : "to.be.detached", $"Locator expected {(!attached ? "not " : string.Empty)}to be attached", "Expect \"ToBeAttachedAsync\"", ConvertToFrameExpectOptions(options)); } public Task ToBeCheckedAsync(LocatorAssertionsToBeCheckedOptions? options = null) @@ -58,30 +58,30 @@ public Task ToBeCheckedAsync(LocatorAssertionsToBeCheckedOptions? options = null var frameExpectedOptions = ConvertToFrameExpectOptions(options) ?? new(); frameExpectedOptions.ExpectedValue = ScriptsHelper.SerializedArgument(expectedValue); var checkedString = options?.Indeterminate == true ? "indeterminate" : "checked"; - return ExpectTrueAsync("to.be.checked", $"Locator expected {(options?.Checked == false ? "not " : string.Empty)}to be {checkedString}", frameExpectedOptions); + return ExpectTrueAsync("to.be.checked", $"Locator expected {(options?.Checked == false ? "not " : string.Empty)}to be {checkedString}", "Expect \"ToBeCheckedAsync\"", frameExpectedOptions); } - public Task ToBeDisabledAsync(LocatorAssertionsToBeDisabledOptions? options = null) => ExpectTrueAsync("to.be.disabled", "Locator expected to be disabled", ConvertToFrameExpectOptions(options)); + public Task ToBeDisabledAsync(LocatorAssertionsToBeDisabledOptions? options = null) => ExpectTrueAsync("to.be.disabled", "Locator expected to be disabled", "Expect \"ToBeDisabledAsync\"", ConvertToFrameExpectOptions(options)); public Task ToBeEditableAsync(LocatorAssertionsToBeEditableOptions? options = null) { var editable = options == null || options.Editable == null || options.Editable == true; var editableString = editable ? "editable" : "read-only"; - return ExpectTrueAsync(editable ? "to.be.editable" : "to.be.readonly", $"Locator expected to be {editableString}", ConvertToFrameExpectOptions(options)); + return ExpectTrueAsync(editable ? "to.be.editable" : "to.be.readonly", $"Locator expected to be {editableString}", "Expect \"ToBeEditableAsync\"", ConvertToFrameExpectOptions(options)); } - public Task ToBeEmptyAsync(LocatorAssertionsToBeEmptyOptions? options = null) => ExpectTrueAsync("to.be.empty", "Locator expected to be empty", ConvertToFrameExpectOptions(options)); + public Task ToBeEmptyAsync(LocatorAssertionsToBeEmptyOptions? options = null) => ExpectTrueAsync("to.be.empty", "Locator expected to be empty", "Expect \"ToBeEmptyAsync\"", ConvertToFrameExpectOptions(options)); public Task ToBeEnabledAsync(LocatorAssertionsToBeEnabledOptions? options = null) { var enabled = options == null || options.Enabled == null || options.Enabled == true; var enabledString = enabled ? "enabled" : "disabled"; - return ExpectTrueAsync(enabled ? "to.be.enabled" : "to.be.disabled", $"Locator expected to be {enabledString}", ConvertToFrameExpectOptions(options)); + return ExpectTrueAsync(enabled ? "to.be.enabled" : "to.be.disabled", $"Locator expected to be {enabledString}", "Expect \"ToBeEnabledAsync\"", ConvertToFrameExpectOptions(options)); } - public Task ToBeFocusedAsync(LocatorAssertionsToBeFocusedOptions? options = null) => ExpectTrueAsync("to.be.focused", "Locator expected to be focused", ConvertToFrameExpectOptions(options)); + public Task ToBeFocusedAsync(LocatorAssertionsToBeFocusedOptions? options = null) => ExpectTrueAsync("to.be.focused", "Locator expected to be focused", "Expect \"ToBeFocusedAsync\"", ConvertToFrameExpectOptions(options)); - public Task ToBeHiddenAsync(LocatorAssertionsToBeHiddenOptions? options = null) => ExpectTrueAsync("to.be.hidden", "Locator expected to be hidden", ConvertToFrameExpectOptions(options)); + public Task ToBeHiddenAsync(LocatorAssertionsToBeHiddenOptions? options = null) => ExpectTrueAsync("to.be.hidden", "Locator expected to be hidden", "Expect \"ToBeHiddenAsync\"", ConvertToFrameExpectOptions(options)); public Task ToBeInViewportAsync(LocatorAssertionsToBeInViewportOptions? options = null) { @@ -90,32 +90,32 @@ public Task ToBeInViewportAsync(LocatorAssertionsToBeInViewportOptions? options { frameExpectOptions.ExpectedNumber = (float)options.Ratio; } - return ExpectTrueAsync("to.be.in.viewport", "Locator expected to be in viewport", frameExpectOptions); + return ExpectTrueAsync("to.be.in.viewport", "Locator expected to be in viewport", "Expect \"ToBeInViewportAsync\"", frameExpectOptions); } public Task ToBeVisibleAsync(LocatorAssertionsToBeVisibleOptions? options = null) { var visible = options == null || options.Visible == null || options.Visible == true; var visibleString = visible ? "visible" : "hidden"; - return ExpectTrueAsync(visible ? "to.be.visible" : "to.be.hidden", $"Locator expected to be {visibleString}", ConvertToFrameExpectOptions(options)); + return ExpectTrueAsync(visible ? "to.be.visible" : "to.be.hidden", $"Locator expected to be {visibleString}", "Expect \"ToBeVisibleAsync\"", ConvertToFrameExpectOptions(options)); } - private Task ExpectTrueAsync(string expression, string message, FrameExpectOptions options) + private Task ExpectTrueAsync(string expression, string message, string title, FrameExpectOptions options) { - return ExpectImplAsync(expression, null as ExpectedTextValue[], null, message, options); + return ExpectImplAsync(expression, null as ExpectedTextValue[], null, message, title, options); } public Task ToContainTextAsync(string expected, LocatorAssertionsToContainTextOptions? options = null) => - ExpectImplAsync("to.have.text", new ExpectedTextValue() { String = expected, MatchSubstring = true, NormalizeWhiteSpace = true, IgnoreCase = options?.IgnoreCase ?? false }, expected, "Locator expected to contain text", ConvertToFrameExpectOptions(options)); + ExpectImplAsync("to.have.text", new ExpectedTextValue() { String = expected, MatchSubstring = true, NormalizeWhiteSpace = true, IgnoreCase = options?.IgnoreCase ?? false }, expected, "Locator expected to contain text", "Expect \"ToContainTextAsync\"", ConvertToFrameExpectOptions(options)); public Task ToContainTextAsync(Regex expected, LocatorAssertionsToContainTextOptions? options = null) => - ExpectImplAsync("to.have.text", ExpectedRegex(expected, new() { MatchSubstring = true, NormalizeWhiteSpace = true, IgnoreCase = options?.IgnoreCase ?? false }), expected, "Locator expected text matching regex", ConvertToFrameExpectOptions(options)); + ExpectImplAsync("to.have.text", ExpectedRegex(expected, new() { MatchSubstring = true, NormalizeWhiteSpace = true, IgnoreCase = options?.IgnoreCase ?? false }), expected, "Locator expected text matching regex", "Expect \"ToContainTextAsync\"", ConvertToFrameExpectOptions(options)); public Task ToContainTextAsync(IEnumerable expected, LocatorAssertionsToContainTextOptions? options = null) => - ExpectImplAsync("to.contain.text.array", expected.Select(text => new ExpectedTextValue() { String = text, MatchSubstring = true, NormalizeWhiteSpace = true, IgnoreCase = options?.IgnoreCase ?? false }).ToArray(), expected, "Locator expected to contain text", ConvertToFrameExpectOptions(options)); + ExpectImplAsync("to.contain.text.array", expected.Select(text => new ExpectedTextValue() { String = text, MatchSubstring = true, NormalizeWhiteSpace = true, IgnoreCase = options?.IgnoreCase ?? false }).ToArray(), expected, "Locator expected to contain text", "Expect \"ToContainTextAsync\"", ConvertToFrameExpectOptions(options)); public Task ToContainTextAsync(IEnumerable expected, LocatorAssertionsToContainTextOptions? options = null) => - ExpectImplAsync("to.contain.text.array", expected.Select(regex => ExpectedRegex(regex, new() { MatchSubstring = true, NormalizeWhiteSpace = true, IgnoreCase = options?.IgnoreCase ?? false })).ToArray(), expected, "Locator expected text matching regex", ConvertToFrameExpectOptions(options)); + ExpectImplAsync("to.contain.text.array", expected.Select(regex => ExpectedRegex(regex, new() { MatchSubstring = true, NormalizeWhiteSpace = true, IgnoreCase = options?.IgnoreCase ?? false })).ToArray(), expected, "Locator expected text matching regex", "Expect \"ToContainTextAsync\"", ConvertToFrameExpectOptions(options)); public Task ToHaveAttributeAsync(string name, string value, LocatorAssertionsToHaveAttributeOptions? options = null) => ToHaveAttributeAsync(name, new() { String = value, IgnoreCase = options?.IgnoreCase }, value, options); @@ -132,32 +132,32 @@ private Task ToHaveAttributeAsync(string name, ExpectedTextValue expectedText, o { message += " matching regex"; } - return ExpectImplAsync("to.have.attribute.value", expectedText, expectedValue, message, commonOptions); + return ExpectImplAsync("to.have.attribute.value", expectedText, expectedValue, message, "Expect \"ToHaveAttributeAsync\"", commonOptions); } public Task ToHaveClassAsync(string expected, LocatorAssertionsToHaveClassOptions? options = null) => - ExpectImplAsync("to.have.class", new ExpectedTextValue() { String = expected }, expected, "Locator expected to have class", ConvertToFrameExpectOptions(options)); + ExpectImplAsync("to.have.class", new ExpectedTextValue() { String = expected }, expected, "Locator expected to have class", "Expect \"ToHaveClassAsync\"", ConvertToFrameExpectOptions(options)); public Task ToHaveClassAsync(Regex expected, LocatorAssertionsToHaveClassOptions? options = null) => - ExpectImplAsync("to.have.class", ExpectedRegex(expected), expected, "Locator expected matching regex", ConvertToFrameExpectOptions(options)); + ExpectImplAsync("to.have.class", ExpectedRegex(expected), expected, "Locator expected matching regex", "Expect \"ToHaveClassAsync\"", ConvertToFrameExpectOptions(options)); public Task ToHaveClassAsync(IEnumerable expected, LocatorAssertionsToHaveClassOptions? options = null) => - ExpectImplAsync("to.have.class.array", expected.Select(text => new ExpectedTextValue() { String = text }).ToArray(), expected, "Locator expected to have class", ConvertToFrameExpectOptions(options)); + ExpectImplAsync("to.have.class.array", expected.Select(text => new ExpectedTextValue() { String = text }).ToArray(), expected, "Locator expected to have class", "Expect \"ToHaveClassAsync\"", ConvertToFrameExpectOptions(options)); public Task ToHaveClassAsync(IEnumerable expected, LocatorAssertionsToHaveClassOptions? options = null) => - ExpectImplAsync("to.have.class.array", expected.Select(regex => ExpectedRegex(regex)).ToArray(), expected, "Locator expected to have class matching regex", ConvertToFrameExpectOptions(options)); + ExpectImplAsync("to.have.class.array", expected.Select(regex => ExpectedRegex(regex)).ToArray(), expected, "Locator expected to have class matching regex", "Expect \"ToHaveClassAsync\"", ConvertToFrameExpectOptions(options)); public Task ToContainClassAsync(string expected, LocatorAssertionsToContainClassOptions? options = null) => - ExpectImplAsync("to.contain.class", new ExpectedTextValue() { String = expected }, expected, "Locator expected to contain class names", ConvertToFrameExpectOptions(options)); + ExpectImplAsync("to.contain.class", new ExpectedTextValue() { String = expected }, expected, "Locator expected to contain class names", "Expect \"ToContainClassAsync\"", ConvertToFrameExpectOptions(options)); public Task ToContainClassAsync(IEnumerable expected, LocatorAssertionsToContainClassOptions? options = null) => - ExpectImplAsync("to.contain.class.array", expected.Select(text => new ExpectedTextValue() { String = text }).ToArray(), expected, "Locator expected to contain class names", ConvertToFrameExpectOptions(options)); + ExpectImplAsync("to.contain.class.array", expected.Select(text => new ExpectedTextValue() { String = text }).ToArray(), expected, "Locator expected to contain class names", "Expect \"ToContainClassAsync\"", ConvertToFrameExpectOptions(options)); public Task ToHaveCountAsync(int count, LocatorAssertionsToHaveCountOptions? options = null) { var commonOptions = ConvertToFrameExpectOptions(options); commonOptions.ExpectedNumber = count; - return ExpectImplAsync("to.have.count", null as ExpectedTextValue[], count, "Locator expected to have count", commonOptions); + return ExpectImplAsync("to.have.count", null as ExpectedTextValue[], count, "Locator expected to have count", "Expect \"ToHaveCountAsync\"", commonOptions); } public Task ToHaveCSSAsync(string name, string value, LocatorAssertionsToHaveCSSOptions? options = null) => @@ -175,72 +175,72 @@ internal Task ToHaveCSSAsync(string name, ExpectedTextValue expectedText, object { message += " matching regex"; } - return ExpectImplAsync("to.have.css", expectedText, expectedValue, message, commonOptions); + return ExpectImplAsync("to.have.css", expectedText, expectedValue, message, "Expect \"ToHaveCSSAsync\"", commonOptions); } public Task ToHaveIdAsync(string id, LocatorAssertionsToHaveIdOptions? options = null) => - ExpectImplAsync("to.have.id", new ExpectedTextValue() { String = id }, id, "Locator expected to have ID", ConvertToFrameExpectOptions(options)); + ExpectImplAsync("to.have.id", new ExpectedTextValue() { String = id }, id, "Locator expected to have ID", "Expect \"ToHaveIdAsync\"", ConvertToFrameExpectOptions(options)); public Task ToHaveIdAsync(Regex id, LocatorAssertionsToHaveIdOptions? options = null) => - ExpectImplAsync("to.have.id", ExpectedRegex(id), id, "Locator expected to have ID", ConvertToFrameExpectOptions(options)); + ExpectImplAsync("to.have.id", ExpectedRegex(id), id, "Locator expected to have ID", "Expect \"ToHaveIdAsync\"", ConvertToFrameExpectOptions(options)); public Task ToHaveJSPropertyAsync(string name, object value, LocatorAssertionsToHaveJSPropertyOptions? options = null) { var commonOptions = ConvertToFrameExpectOptions(options); commonOptions.ExpressionArg = name; commonOptions.ExpectedValue = ScriptsHelper.SerializedArgument(value); - return ExpectImplAsync("to.have.property", null as ExpectedTextValue[], value, $"Locator expected to have JavaScript property '{name}'", commonOptions); + return ExpectImplAsync("to.have.property", null as ExpectedTextValue[], value, $"Locator expected to have JavaScript property '{name}'", "Expect \"ToHaveJSPropertyAsync\"", commonOptions); } public Task ToHaveTextAsync(string expected, LocatorAssertionsToHaveTextOptions? options = null) => - ExpectImplAsync("to.have.text", new ExpectedTextValue() { String = expected, NormalizeWhiteSpace = true, IgnoreCase = options?.IgnoreCase ?? false }, expected, "Locator expected to have text", ConvertToFrameExpectOptions(options)); + ExpectImplAsync("to.have.text", new ExpectedTextValue() { String = expected, NormalizeWhiteSpace = true, IgnoreCase = options?.IgnoreCase ?? false }, expected, "Locator expected to have text", "Expect \"ToHaveTextAsync\"", ConvertToFrameExpectOptions(options)); public Task ToHaveTextAsync(Regex expected, LocatorAssertionsToHaveTextOptions? options = null) => - ExpectImplAsync("to.have.text", ExpectedRegex(expected, new() { NormalizeWhiteSpace = true, IgnoreCase = options?.IgnoreCase ?? false }), expected, "Locator expected to have text matching regex", ConvertToFrameExpectOptions(options)); + ExpectImplAsync("to.have.text", ExpectedRegex(expected, new() { NormalizeWhiteSpace = true, IgnoreCase = options?.IgnoreCase ?? false }), expected, "Locator expected to have text matching regex", "Expect \"ToHaveTextAsync\"", ConvertToFrameExpectOptions(options)); public Task ToHaveTextAsync(IEnumerable expected, LocatorAssertionsToHaveTextOptions? options = null) => - ExpectImplAsync("to.have.text.array", expected.Select(text => new ExpectedTextValue() { String = text, NormalizeWhiteSpace = true, IgnoreCase = options?.IgnoreCase ?? false }).ToArray(), expected, "Locator expected to have text", ConvertToFrameExpectOptions(options)); + ExpectImplAsync("to.have.text.array", expected.Select(text => new ExpectedTextValue() { String = text, NormalizeWhiteSpace = true, IgnoreCase = options?.IgnoreCase ?? false }).ToArray(), expected, "Locator expected to have text", "Expect \"ToHaveTextAsync\"", ConvertToFrameExpectOptions(options)); public Task ToHaveTextAsync(IEnumerable expected, LocatorAssertionsToHaveTextOptions? options = null) => - ExpectImplAsync("to.have.text.array", expected.Select(regex => ExpectedRegex(regex, new() { NormalizeWhiteSpace = true, IgnoreCase = options?.IgnoreCase ?? false })).ToArray(), expected, "Locator expected to have text", ConvertToFrameExpectOptions(options)); + ExpectImplAsync("to.have.text.array", expected.Select(regex => ExpectedRegex(regex, new() { NormalizeWhiteSpace = true, IgnoreCase = options?.IgnoreCase ?? false })).ToArray(), expected, "Locator expected to have text", "Expect \"ToHaveTextAsync\"", ConvertToFrameExpectOptions(options)); public Task ToHaveValueAsync(string value, LocatorAssertionsToHaveValueOptions? options = null) => - ExpectImplAsync("to.have.value", new ExpectedTextValue() { String = value }, value, "Locator expected to have value", ConvertToFrameExpectOptions(options)); + ExpectImplAsync("to.have.value", new ExpectedTextValue() { String = value }, value, "Locator expected to have value", "Expect \"ToHaveValueAsync\"", ConvertToFrameExpectOptions(options)); public Task ToHaveValueAsync(Regex value, LocatorAssertionsToHaveValueOptions? options = null) => - ExpectImplAsync("to.have.value", ExpectedRegex(value), value, "Locator expected to have value matching regex", ConvertToFrameExpectOptions(options)); + ExpectImplAsync("to.have.value", ExpectedRegex(value), value, "Locator expected to have value matching regex", "Expect \"ToHaveValueAsync\"", ConvertToFrameExpectOptions(options)); public Task ToHaveValuesAsync(IEnumerable values, LocatorAssertionsToHaveValuesOptions? options = null) => - ExpectImplAsync("to.have.values", values.Select(text => new ExpectedTextValue() { String = text }).ToArray(), values, "Locator expected to have values", ConvertToFrameExpectOptions(options)); + ExpectImplAsync("to.have.values", values.Select(text => new ExpectedTextValue() { String = text }).ToArray(), values, "Locator expected to have values", "Expect \"ToHaveValuesAsync\"", ConvertToFrameExpectOptions(options)); public Task ToHaveValuesAsync(IEnumerable values, LocatorAssertionsToHaveValuesOptions? options = null) => - ExpectImplAsync("to.have.values", values.Select(regex => ExpectedRegex(regex)).ToArray(), values, "Locator expected to have matching regex", ConvertToFrameExpectOptions(options)); + ExpectImplAsync("to.have.values", values.Select(regex => ExpectedRegex(regex)).ToArray(), values, "Locator expected to have matching regex", "Expect \"ToHaveValuesAsync\"", ConvertToFrameExpectOptions(options)); public Task ToHaveAccessibleDescriptionAsync(string expected, LocatorAssertionsToHaveAccessibleDescriptionOptions? options = null) - => ExpectImplAsync("to.have.accessible.description", new ExpectedTextValue() { String = expected, IgnoreCase = options?.IgnoreCase, NormalizeWhiteSpace = true }, expected, "Locator expected to have accessible description", ConvertToFrameExpectOptions(options)); + => ExpectImplAsync("to.have.accessible.description", new ExpectedTextValue() { String = expected, IgnoreCase = options?.IgnoreCase, NormalizeWhiteSpace = true }, expected, "Locator expected to have accessible description", "Expect \"ToHaveAccessibleDescriptionAsync\"", ConvertToFrameExpectOptions(options)); public Task ToHaveAccessibleDescriptionAsync(Regex expected, LocatorAssertionsToHaveAccessibleDescriptionOptions? options = null) - => ExpectImplAsync("to.have.accessible.description", ExpectedRegex(expected, new() { IgnoreCase = options?.IgnoreCase, NormalizeWhiteSpace = true }), expected, "Locator expected to have accessible description matching regex", ConvertToFrameExpectOptions(options)); + => ExpectImplAsync("to.have.accessible.description", ExpectedRegex(expected, new() { IgnoreCase = options?.IgnoreCase, NormalizeWhiteSpace = true }), expected, "Locator expected to have accessible description matching regex", "Expect \"ToHaveAccessibleDescriptionAsync\"", ConvertToFrameExpectOptions(options)); public Task ToHaveAccessibleErrorMessageAsync(string errorMessage, LocatorAssertionsToHaveAccessibleErrorMessageOptions? options = null) - => ExpectImplAsync("to.have.accessible.error.message", new ExpectedTextValue() { String = errorMessage, IgnoreCase = options?.IgnoreCase }, errorMessage, "Locator expected to have accessible error message", ConvertToFrameExpectOptions(options)); + => ExpectImplAsync("to.have.accessible.error.message", new ExpectedTextValue() { String = errorMessage, IgnoreCase = options?.IgnoreCase }, errorMessage, "Locator expected to have accessible error message", "Expect \"ToHaveAccessibleErrorMessageAsync\"", ConvertToFrameExpectOptions(options)); public Task ToHaveAccessibleErrorMessageAsync(Regex errorMessage, LocatorAssertionsToHaveAccessibleErrorMessageOptions? options = null) - => ExpectImplAsync("to.have.accessible.error.message", ExpectedRegex(errorMessage, new() { IgnoreCase = options?.IgnoreCase }), errorMessage, "Locator expected to have accessible error message matching regex", ConvertToFrameExpectOptions(options)); + => ExpectImplAsync("to.have.accessible.error.message", ExpectedRegex(errorMessage, new() { IgnoreCase = options?.IgnoreCase }), errorMessage, "Locator expected to have accessible error message matching regex", "Expect \"ToHaveAccessibleErrorMessageAsync\"", ConvertToFrameExpectOptions(options)); public Task ToHaveAccessibleNameAsync(string expected, LocatorAssertionsToHaveAccessibleNameOptions? options = null) - => ExpectImplAsync("to.have.accessible.name", new ExpectedTextValue() { String = expected, IgnoreCase = options?.IgnoreCase, NormalizeWhiteSpace = true }, expected, "Locator expected to have accessible name", ConvertToFrameExpectOptions(options)); + => ExpectImplAsync("to.have.accessible.name", new ExpectedTextValue() { String = expected, IgnoreCase = options?.IgnoreCase, NormalizeWhiteSpace = true }, expected, "Locator expected to have accessible name", "Expect \"ToHaveAccessibleNameAsync\"", ConvertToFrameExpectOptions(options)); public Task ToHaveAccessibleNameAsync(Regex expected, LocatorAssertionsToHaveAccessibleNameOptions? options = null) - => ExpectImplAsync("to.have.accessible.name", ExpectedRegex(expected, new() { IgnoreCase = options?.IgnoreCase, NormalizeWhiteSpace = true }), expected, "Locator expected to have accessible name matching regex", ConvertToFrameExpectOptions(options)); + => ExpectImplAsync("to.have.accessible.name", ExpectedRegex(expected, new() { IgnoreCase = options?.IgnoreCase, NormalizeWhiteSpace = true }), expected, "Locator expected to have accessible name matching regex", "Expect \"ToHaveAccessibleNameAsync\"", ConvertToFrameExpectOptions(options)); public Task ToHaveRoleAsync(AriaRole role, LocatorAssertionsToHaveRoleOptions? options = null) - => ExpectImplAsync("to.have.role", new ExpectedTextValue() { String = role.ToString().ToLowerInvariant() }, role, "Locator expected to have role", ConvertToFrameExpectOptions(options)); + => ExpectImplAsync("to.have.role", new ExpectedTextValue() { String = role.ToString().ToLowerInvariant() }, role, "Locator expected to have role", "Expect \"ToHaveRoleAsync\"", ConvertToFrameExpectOptions(options)); public Task ToMatchAriaSnapshotAsync(string expected, LocatorAssertionsToMatchAriaSnapshotOptions? options = null) { var commonOptions = ConvertToFrameExpectOptions(options); commonOptions.ExpectedValue = ScriptsHelper.SerializedArgument(expected); - return ExpectImplAsync("to.match.aria", null as ExpectedTextValue[], expected, "Locator expected to match Aria snapshot", commonOptions); + return ExpectImplAsync("to.match.aria", null as ExpectedTextValue[], expected, "Locator expected to match Aria snapshot", "Expect \"ToMatchAriaSnapshotAsync\"", commonOptions); } } diff --git a/src/Playwright/Core/Mouse.cs b/src/Playwright/Core/Mouse.cs index 272bd98b99..5c6de778a6 100644 --- a/src/Playwright/Core/Mouse.cs +++ b/src/Playwright/Core/Mouse.cs @@ -48,17 +48,25 @@ public Task ClickAsync(float x, float y, MouseClickOptions? options = default) ["clickCount"] = options?.ClickCount, }); - public Task DblClickAsync(float x, float y, MouseDblClickOptions? options = default) - => _page.SendMessageToServerAsync( - "mouseClick", - new Dictionary - { - ["x"] = x, - ["y"] = y, - ["delay"] = options?.Delay, - ["button"] = options?.Button, - ["clickCount"] = 2, - }); + public async Task DblClickAsync(float x, float y, MouseDblClickOptions? options = default) + { + await _page.WrapApiCallAsync( + async () => + { + await _page.SendMessageToServerAsync( + "mouseClick", + new Dictionary + { + ["x"] = x, + ["y"] = y, + ["delay"] = options?.Delay, + ["button"] = options?.Button, + ["clickCount"] = 2, + }).ConfigureAwait(false); + }, + false, + "Double click").ConfigureAwait(false); + } public Task DownAsync(MouseDownOptions? options = default) => _page.SendMessageToServerAsync( diff --git a/src/Playwright/Core/Page.cs b/src/Playwright/Core/Page.cs index 21dca6bfcd..bd56f912bc 100644 --- a/src/Playwright/Core/Page.cs +++ b/src/Playwright/Core/Page.cs @@ -198,7 +198,7 @@ public IVideo? Video { get { - if (Context.Options.RecordVideoDir == null) + if (Context.Options?.RecordVideo?.Dir == null) { return null; } @@ -265,6 +265,10 @@ internal override void OnMessage(string method, JsonElement serverParams) case "video": ForceVideo().ArtifactReady(serverParams.GetProperty("artifact").ToObject(_connection.DefaultJsonSerializerOptions)); break; + case "viewportSizeChanged": + var size = serverParams.GetProperty("viewportSize").ToObject(_connection.DefaultJsonSerializerOptions); + ViewportSize = new() { Width = size.Width, Height = size.Height }; + break; case "worker": var worker = serverParams.GetProperty("worker").ToObject(_connection.DefaultJsonSerializerOptions); _workers.Add(worker); @@ -492,8 +496,7 @@ internal async Task InnerWaitForEventAsync(PlaywrightEvent pageEvent, F var waitForEventTask = waiter.WaitForEventAsync(this, pageEvent.Name, predicate); if (action != null) { - await WrapApiBoundaryAsync(() => waiter.CancelWaitOnExceptionAsync(waitForEventTask, action)) - .ConfigureAwait(false); + await waiter.CancelWaitOnExceptionAsync(waitForEventTask, action).ConfigureAwait(false); } return await waitForEventTask.ConfigureAwait(false); @@ -891,11 +894,6 @@ public Task ExposeFunctionAsync(string name, Func PdfAsync(PagePdfOptions? options = default) { options ??= new(); - if (!Context.IsChromium) - { - throw new NotSupportedException("This browser doesn't support this action."); - } - byte[] result = (await SendMessageToServerAsync("pdf", new Dictionary { ["scale"] = options?.Scale, @@ -1311,7 +1309,7 @@ private async Task OnRouteAsync(Route route) foreach (var routeHandler in _routes.ToArray()) { // If the page was closed we stall all requests right away. - if (CloseWasCalled || Context.CloseWasCalled) + if (CloseWasCalled || Context.ClosingOrClosed) { return; } @@ -1330,7 +1328,7 @@ private async Task OnRouteAsync(Route route) var handled = await routeHandler.HandleAsync(route).ConfigureAwait(false); if (_routes.Count == 0) { - await UpdateInterceptionAsync().ConfigureAwait(false); + UpdateInterceptionAsync().IgnoreException(); } if (handled) { @@ -1411,16 +1409,20 @@ public async Task RouteFromHARAsync(string har, PageRouteFromHAROptions? options { if (options?.Update == true) { - await Context.RecordIntoHarAsync(har, this, new() - { - NotFound = options.NotFound, - Url = options.Url, - UrlString = options.UrlString, - UrlRegex = options.UrlRegex, - Update = options.Update, - UpdateContent = options.UpdateContent, - UpdateMode = options.UpdateMode, - }).ConfigureAwait(false); + await Context.RecordIntoHarAsync( + har, + this, + new() + { + NotFound = options.NotFound, + Url = options.Url, + UrlString = options.UrlString, + UrlRegex = options.UrlRegex, + Update = options.Update, + UpdateContent = options.UpdateContent, + UpdateMode = options.UpdateMode, + }, + null).ConfigureAwait(false); return; } var harRouter = await HarRouter.CreateAsync(_connection.LocalUtils!, har, options?.NotFound ?? HarNotFound.Abort, new() diff --git a/src/Playwright/Core/PageAssertions.cs b/src/Playwright/Core/PageAssertions.cs index 1d9c20bdae..026ea7a426 100644 --- a/src/Playwright/Core/PageAssertions.cs +++ b/src/Playwright/Core/PageAssertions.cs @@ -52,14 +52,14 @@ private static T PassThroughNonNull(T value) } public Task ToHaveTitleAsync(string titleOrRegExp, PageAssertionsToHaveTitleOptions? options = null) => - ExpectImplAsync("to.have.title", new ExpectedTextValue() { String = titleOrRegExp, NormalizeWhiteSpace = true }, titleOrRegExp, "Page title expected to be", ConvertToFrameExpectOptions(options)); + ExpectImplAsync("to.have.title", new ExpectedTextValue() { String = titleOrRegExp, NormalizeWhiteSpace = true }, titleOrRegExp, "Page title expected to be", "Expect \"ToHaveTitleAsync\"", ConvertToFrameExpectOptions(options)); public Task ToHaveTitleAsync(Regex titleOrRegExp, PageAssertionsToHaveTitleOptions? options = null) => - ExpectImplAsync("to.have.title", ExpectedRegex(titleOrRegExp, new() { NormalizeWhiteSpace = true }), titleOrRegExp, "Page title expected to be", ConvertToFrameExpectOptions(options)); + ExpectImplAsync("to.have.title", ExpectedRegex(titleOrRegExp, new() { NormalizeWhiteSpace = true }), titleOrRegExp, "Page title expected to be", "Expect \"ToHaveTitleAsync\"", ConvertToFrameExpectOptions(options)); public Task ToHaveURLAsync(string urlOrRegExp, PageAssertionsToHaveURLOptions? options = null) => - ExpectImplAsync("to.have.url", new ExpectedTextValue() { String = URLMatch.ConstructURLBasedOnBaseURL(_page.Context.Options.BaseURL, urlOrRegExp), IgnoreCase = options?.IgnoreCase }, urlOrRegExp, "Page URL expected to be", ConvertToFrameExpectOptions(options)); + ExpectImplAsync("to.have.url", new ExpectedTextValue() { String = URLMatch.ConstructURLBasedOnBaseURL(_page.Context.Options.BaseURL, urlOrRegExp), IgnoreCase = options?.IgnoreCase }, urlOrRegExp, "Page URL expected to be", "Expect \"ToHaveURLAsync\"", ConvertToFrameExpectOptions(options)); public Task ToHaveURLAsync(Regex urlOrRegExp, PageAssertionsToHaveURLOptions? options = null) => - ExpectImplAsync("to.have.url", ExpectedRegex(urlOrRegExp, new() { IgnoreCase = options?.IgnoreCase }), urlOrRegExp, "Page URL expected to match regex", ConvertToFrameExpectOptions(options)); + ExpectImplAsync("to.have.url", ExpectedRegex(urlOrRegExp, new() { IgnoreCase = options?.IgnoreCase }), urlOrRegExp, "Page URL expected to match regex", "Expect \"ToHaveURLAsync\"", ConvertToFrameExpectOptions(options)); } diff --git a/src/Playwright/Core/PlaywrightImpl.cs b/src/Playwright/Core/PlaywrightImpl.cs index 4baf253b6e..20bd6f7e58 100644 --- a/src/Playwright/Core/PlaywrightImpl.cs +++ b/src/Playwright/Core/PlaywrightImpl.cs @@ -34,7 +34,7 @@ namespace Microsoft.Playwright.Core; internal class PlaywrightImpl : ChannelOwner, IPlaywright { private readonly PlaywrightInitializer _initializer; - internal SelectorsAPI _selectors; + internal Selectors _selectors; private readonly Dictionary _devices = new(StringComparer.InvariantCultureIgnoreCase); @@ -50,10 +50,7 @@ internal PlaywrightImpl(ChannelOwner parent, string guid, PlaywrightInitializer _initializer.Webkit.Playwright = this; APIRequest = new APIRequest(this); - _selectors = new SelectorsAPI(); - var selectorsOwner = this._initializer.Selectors; - _selectors.AddChannel(selectorsOwner); - this._connection.Close += (_, _) => _selectors.RemoveChannel(selectorsOwner); + _selectors = new Selectors(); } ~PlaywrightImpl() => Dispose(false); @@ -88,14 +85,6 @@ public IBrowserType this[string browserType] _ => throw new ArgumentException($"Unknown browser type: {browserType}"), }; - internal void SetSelectors(SelectorsAPI selectors) - { - var selectorsOwner = this._initializer.Selectors; - _selectors.RemoveChannel(selectorsOwner); - _selectors = selectors; - _selectors.AddChannel(selectorsOwner); - } - public void Dispose() { Dispose(true); diff --git a/src/Playwright/Core/Request.cs b/src/Playwright/Core/Request.cs index 86707ee9f9..13fdc1980d 100644 --- a/src/Playwright/Core/Request.cs +++ b/src/Playwright/Core/Request.cs @@ -45,7 +45,6 @@ internal class Request : ChannelOwner, IRequest internal Request(ChannelOwner parent, string guid, RequestInitializer initializer) : base(parent, guid) { - MarkAsInternalType(); _initializer = initializer; RedirectedFrom = _initializer.RedirectedFrom; Timing = new(); @@ -219,8 +218,13 @@ private Task ActualHeadersAsync() private async Task GetRawHeadersTaskAsync() { - var headerList = (await SendMessageToServerAsync("rawRequestHeaders").ConfigureAwait(false)).Value.GetProperty("headers").ToObject>(); - return new(headerList); + return await WrapApiCallAsync( + async () => + { + var headerList = (await SendMessageToServerAsync("rawRequestHeaders").ConfigureAwait(false)).Value.GetProperty("headers").ToObject>(); + return new RawHeaders(headerList); + }, + true).ConfigureAwait(false); } internal void ApplyFallbackOverrides(RouteFallbackOptions? overrides) diff --git a/src/Playwright/Core/Response.cs b/src/Playwright/Core/Response.cs index 01e4cb7126..66a3ca28f2 100644 --- a/src/Playwright/Core/Response.cs +++ b/src/Playwright/Core/Response.cs @@ -43,7 +43,6 @@ internal class Response : ChannelOwner, IResponse internal Response(ChannelOwner parent, string guid, ResponseInitializer initializer) : base(parent, guid) { - MarkAsInternalType(); _initializer = initializer; _initializer.Request.Timing = _initializer.Timing; _finishedTask = new(); diff --git a/src/Playwright/Core/Route.cs b/src/Playwright/Core/Route.cs index 269ab3d0c9..f3c10e8391 100644 --- a/src/Playwright/Core/Route.cs +++ b/src/Playwright/Core/Route.cs @@ -47,7 +47,6 @@ internal class Route : ChannelOwner, IRoute internal Route(ChannelOwner parent, string guid, RouteInitializer initializer) : base(parent, guid) { - MarkAsInternalType(); _initializer = initializer; _request = initializer.Request; } @@ -61,19 +60,20 @@ internal Route(ChannelOwner parent, string guid, RouteInitializer initializer) : [MethodImpl(MethodImplOptions.NoInlining)] public async Task FulfillAsync(RouteFulfillOptions? options = default) { - CheckNotHandled(); - options ??= new RouteFulfillOptions(); - var normalized = await NormalizeFulfillParametersAsync( - options.Status, - options.Headers, - options.ContentType, - options.Body, - options.BodyBytes, - options.Json, - options.Path, - options.Response).ConfigureAwait(false); - await RaceWithTargetCloseAsync(SendMessageToServerAsync("fulfill", normalized)).ConfigureAwait(false); - ReportHandled(true); + await HandleRouteAsync(async () => + { + options ??= new RouteFulfillOptions(); + var normalized = await NormalizeFulfillParametersAsync( + options.Status, + options.Headers, + options.ContentType, + options.Body, + options.BodyBytes, + options.Json, + options.Path, + options.Response).ConfigureAwait(false); + await RaceWithTargetCloseAsync(SendMessageToServerAsync("fulfill", normalized)).ConfigureAwait(false); + }).ConfigureAwait(false); } [MethodImpl(MethodImplOptions.NoInlining)] @@ -93,10 +93,11 @@ await RaceWithTargetCloseAsync(SendMessageToServerAsync( [MethodImpl(MethodImplOptions.NoInlining)] public async Task ContinueAsync(RouteContinueOptions? options = default) { - CheckNotHandled(); - _request.ApplyFallbackOverrides(new RouteFallbackOptions().FromRouteContinueOptions(options)); - await InnerContinueAsync(false /* isFallback */).ConfigureAwait(false); - ReportHandled(true); + await HandleRouteAsync(async () => + { + _request.ApplyFallbackOverrides(new RouteFallbackOptions().FromRouteContinueOptions(options)); + await InnerContinueAsync(false /* isFallback */).ConfigureAwait(false); + }).ConfigureAwait(false); } internal async Task InnerContinueAsync(bool isFallback = false) diff --git a/src/Playwright/Core/Selectors.cs b/src/Playwright/Core/Selectors.cs index e182ed84ce..030d8beda2 100644 --- a/src/Playwright/Core/Selectors.cs +++ b/src/Playwright/Core/Selectors.cs @@ -25,45 +25,45 @@ using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.Playwright.Helpers; -using Microsoft.Playwright.Transport; namespace Microsoft.Playwright.Core; -internal class Selectors : ChannelOwner +internal class Selectors : ISelectors { - internal Selectors(ChannelOwner parent, string guid) : base(parent, guid) - { - } -} - -internal class SelectorsAPI : ISelectors -{ - private readonly HashSet _channels = new(); - private readonly List> _registrations = new(); + internal readonly List _contextsForSelectors = new(); + internal readonly List> _selectorEngines = new(); + internal string? _testIdAttributeName; public async Task RegisterAsync(string name, SelectorsRegisterOptions? options = default) { options ??= new SelectorsRegisterOptions(); var source = ScriptsHelper.EvaluationScript(options.Script, options.Path, false); - var @params = new Dictionary() + var engine = new Dictionary() { ["name"] = name, ["source"] = source, - ["contentScript"] = options.ContentScript, }; - foreach (var channel in _channels) + if (options.ContentScript != null) { - await channel.SendMessageToServerAsync("register", @params).ConfigureAwait(false); + engine["contentScript"] = options.ContentScript; } - _registrations.Add(@params); + foreach (var context in _contextsForSelectors) + { + await context.SendMessageToServerAsync("registerSelectorEngine", new Dictionary + { + ["selectorEngine"] = engine, + }).ConfigureAwait(false); + } + _selectorEngines.Add(engine); } public void SetTestIdAttribute(string attributeName) { Locator.SetTestIdAttribute(attributeName); - foreach (var channel in _channels) + _testIdAttributeName = attributeName; + foreach (var context in _contextsForSelectors) { - channel.SendMessageToServerAsync( + context.SendMessageToServerAsync( "setTestIdAttributeName", new Dictionary { @@ -71,23 +71,4 @@ public void SetTestIdAttribute(string attributeName) }).IgnoreException(); } } - - internal void AddChannel(Selectors channel) - { - _channels.Add(channel); - foreach (var @params in _registrations) - { - // This should not fail except for connection closure, but just in case we catch. - channel.SendMessageToServerAsync("register", @params).IgnoreException(); - channel.SendMessageToServerAsync("setTestIdAttributeName", new Dictionary - { - ["testIdAttributeName"] = Locator.TestIdAttributeName(), - }).IgnoreException(); - } - } - - internal void RemoveChannel(Selectors channel) - { - _channels.Remove(channel); - } } diff --git a/src/Playwright/Core/Tracing.cs b/src/Playwright/Core/Tracing.cs index 472951b1c7..341ab1a07e 100644 --- a/src/Playwright/Core/Tracing.cs +++ b/src/Playwright/Core/Tracing.cs @@ -41,7 +41,6 @@ internal class Tracing : ChannelOwner, ITracing public Tracing(ChannelOwner parent, string guid) : base(parent, guid) { - MarkAsInternalType(); } [MethodImpl(MethodImplOptions.NoInlining)] diff --git a/src/Playwright/Core/WebSocketRoute.cs b/src/Playwright/Core/WebSocketRoute.cs index 30af2f0c9b..fde2f963cb 100644 --- a/src/Playwright/Core/WebSocketRoute.cs +++ b/src/Playwright/Core/WebSocketRoute.cs @@ -48,7 +48,6 @@ internal class WebSocketRoute : ChannelOwner, IWebSocketRoute internal WebSocketRoute(ChannelOwner parent, string guid, WebSocketRouteInitializer initializer) : base(parent, guid) { _initializer = initializer; - MarkAsInternalType(); _server = new ServerWebSocketRoute(this); } diff --git a/src/Playwright/Core/Worker.cs b/src/Playwright/Core/Worker.cs index bec8f645f6..1ff9f71198 100644 --- a/src/Playwright/Core/Worker.cs +++ b/src/Playwright/Core/Worker.cs @@ -108,7 +108,7 @@ public async Task WaitForCloseAsync(Func? action = default, float var result = waiterResult.Task.WithTimeout(Convert.ToInt32(timeout ?? 0)); if (action != null) { - await WrapApiBoundaryAsync(() => waiter.CancelWaitOnExceptionAsync(result, action)).ConfigureAwait(false); + await waiter.CancelWaitOnExceptionAsync(result, action).ConfigureAwait(false); } else { diff --git a/src/Playwright/Transport/ApiZone.cs b/src/Playwright/Transport/ApiZone.cs index 13cf359d00..9edd588ad0 100644 --- a/src/Playwright/Transport/ApiZone.cs +++ b/src/Playwright/Transport/ApiZone.cs @@ -30,7 +30,10 @@ namespace Microsoft.Playwright.Transport; internal class ApiZone { [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - public string? ApiName { get; set; } + public string? Title { get; set; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public bool? Internal { get; set; } public List Frames { get; set; } = null!; } diff --git a/src/Playwright/Transport/ChannelOwner.cs b/src/Playwright/Transport/ChannelOwner.cs index e41608140c..cf9fe7f18f 100644 --- a/src/Playwright/Transport/ChannelOwner.cs +++ b/src/Playwright/Transport/ChannelOwner.cs @@ -24,7 +24,6 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; -using System.Runtime.CompilerServices; using System.Text.Json; using System.Threading.Tasks; using Microsoft.Playwright.Helpers; @@ -36,7 +35,6 @@ internal class ChannelOwner internal readonly Connection _connection; internal bool _wasCollected; - internal bool _isInternalType; internal ChannelOwner(ChannelOwner parent, string guid) : this(parent, parent._connection, guid) { @@ -86,14 +84,9 @@ internal void DisposeOwner(string? reason) Objects.Clear(); } - public Task WrapApiCallAsync(Func> action, bool isInternal = false) => _connection.WrapApiCallAsync(action, isInternal); + public Task WrapApiCallAsync(Func> action, bool isInternal = false, string? title = null) => _connection.WrapApiCallAsync(action, isInternal, title); - public Task WrapApiCallAsync(Func action, bool isInternal = false) => _connection.WrapApiCallAsync(action, isInternal); - - [MethodImpl(MethodImplOptions.NoInlining)] - public Task WrapApiBoundaryAsync(Func action) => _connection.WrapApiBoundaryAsync(action); - - internal void MarkAsInternalType() => _isInternalType = true; + public Task WrapApiCallAsync(Func action, bool isInternal = false, string? title = null) => _connection.WrapApiCallAsync(action, isInternal, title); internal EventHandler? UpdateEventHandler(string eventName, EventHandler? handlers, EventHandler? handler, bool add) { diff --git a/src/Playwright/Transport/Channels/ChannelOwnerType.cs b/src/Playwright/Transport/Channels/ChannelOwnerType.cs index adce04d785..91a3368a5c 100644 --- a/src/Playwright/Transport/Channels/ChannelOwnerType.cs +++ b/src/Playwright/Transport/Channels/ChannelOwnerType.cs @@ -90,9 +90,6 @@ internal enum ChannelOwnerType [EnumMember(Value = "electron")] Electron, - [EnumMember(Value = "selectors")] - Selectors, - [EnumMember(Value = "SocksSupport")] SocksSupport, diff --git a/src/Playwright/Transport/Connection.cs b/src/Playwright/Transport/Connection.cs index 3c4bb21644..652f17765b 100644 --- a/src/Playwright/Transport/Connection.cs +++ b/src/Playwright/Transport/Connection.cs @@ -130,7 +130,7 @@ internal Task SendMessageToServerAsync( ChannelOwner? @object, string method, Dictionary? args = null, - bool keepNulls = false) => WrapApiCallAsync(() => InnerSendMessageToServerAsync(@object, method, args, keepNulls), @object?._isInternalType ?? false); + bool keepNulls = false) => WrapApiCallAsync(() => InnerSendMessageToServerAsync(@object, method, args, keepNulls), false, null); private async Task InnerSendMessageToServerAsync( ChannelOwner? @object, @@ -160,15 +160,15 @@ private async Task InnerSendMessageToServerAsync( .Where(f => f.Value != null) .ToDictionary(f => f.Key, f => f.Value) as Dictionary; } - var (apiName, frames) = (ApiZone.Value[0]!.ApiName, ApiZone.Value[0]!.Frames); + var (title, isInternal, frames) = (ApiZone.Value[0]!.Title, ApiZone.Value[0]!.Internal, ApiZone.Value[0]!.Frames); var metadata = new Dictionary { - ["internal"] = string.IsNullOrEmpty(apiName), + ["internal"] = isInternal, ["wallTime"] = DateTimeOffset.Now.ToUnixTimeMilliseconds(), }; - if (!string.IsNullOrEmpty(apiName)) + if (!string.IsNullOrEmpty(title)) { - metadata["apiName"] = apiName; + metadata["title"] = title; } if (frames.Count > 0) { @@ -383,9 +383,6 @@ internal void Dispatch(PlaywrightServerMessage message) case ChannelOwnerType.WebSocketRoute: result = new WebSocketRoute(parent, guid, initializer?.ToObject(DefaultJsonSerializerOptions)!); break; - case ChannelOwnerType.Selectors: - result = new Selectors(parent, guid); - break; case ChannelOwnerType.SocksSupport: result = new SocksSupport(parent, guid); break; @@ -478,7 +475,7 @@ internal static void TraceMessage(string logLevel, byte[] rawMessage) } [MethodImpl(MethodImplOptions.NoInlining)] - internal async Task WrapApiCallAsync(Func> action, bool isInternal = false) + internal async Task WrapApiCallAsync(Func> action, bool isInternal = false, string? title = null) { EnsureApiZoneExists(); if (ApiZone.Value[0] != null) @@ -487,45 +484,18 @@ internal async Task WrapApiCallAsync(Func> action, bool isInternal } var st = new StackTrace(true); var stack = new List(); - var lastInternalApiName = string.Empty; - var apiName = string.Empty; - var apiBoundaryReached = false; for (int i = 0; i < st.FrameCount; ++i) { var sf = st.GetFrame(i); string fileName = sf.GetFileName(); - if (IsPlaywrightInternalNamespace(sf.GetMethod().ReflectedType?.Namespace)) - { - string methodName = $"{sf?.GetMethod()?.DeclaringType?.Name}.{sf?.GetMethod()?.Name}"; - if (methodName.Contains("WrapApiBoundaryAsync")) - { - apiBoundaryReached = true; - } - var hasCleanMethodName = !methodName.StartsWith("<", StringComparison.InvariantCultureIgnoreCase); - if (hasCleanMethodName) - { - lastInternalApiName = methodName; - } - } - else if (!string.IsNullOrEmpty(fileName)) + if (!IsPlaywrightInternalNamespace(sf.GetMethod().ReflectedType?.Namespace) && !string.IsNullOrEmpty(fileName)) { stack.Add(new() { File = fileName, Line = sf.GetFileLineNumber(), Column = sf.GetFileColumnNumber() }); - if (!string.IsNullOrEmpty(lastInternalApiName) && !apiBoundaryReached) - { - apiName = lastInternalApiName; - } } } - if (string.IsNullOrEmpty(apiName)) - { - apiName = lastInternalApiName; - } try { - if (!string.IsNullOrEmpty(apiName)) - { - ApiZone.Value[0] = new() { ApiName = isInternal ? null : apiName, Frames = stack }; - } + ApiZone.Value[0] = new() { Internal = isInternal, Title = title, Frames = stack }; return await action().ConfigureAwait(false); } finally @@ -534,14 +504,15 @@ internal async Task WrapApiCallAsync(Func> action, bool isInternal } } - internal Task WrapApiCallAsync(Func action, bool isInternal = false) + internal Task WrapApiCallAsync(Func action, bool isInternal = false, string? title = null) => WrapApiCallAsync( async () => { await action().ConfigureAwait(false); return true; }, - isInternal); + isInternal, + title); private static bool IsPlaywrightInternalNamespace(string? namespaceName) { @@ -552,21 +523,6 @@ private static bool IsPlaywrightInternalNamespace(string? namespaceName) namespaceName.StartsWith("Microsoft.Playwright.Helpers", StringComparison.InvariantCultureIgnoreCase)); } - [MethodImpl(MethodImplOptions.NoInlining)] // This method is also a stacktrace marker. - internal async Task WrapApiBoundaryAsync(Func action) - { - EnsureApiZoneExists(); - try - { - ApiZone.Value.Insert(0, null); - await action().ConfigureAwait(false); - } - finally - { - ApiZone.Value.RemoveAt(0); - } - } - private void EnsureApiZoneExists() { if (ApiZone.Value == null) diff --git a/src/Playwright/Transport/Protocol/Generated/AndroidSelectorHasChild.cs b/src/Playwright/Transport/Protocol/Generated/AndroidSelectorHasChild.cs index dcdf2590ae..4cdb4def16 100644 --- a/src/Playwright/Transport/Protocol/Generated/AndroidSelectorHasChild.cs +++ b/src/Playwright/Transport/Protocol/Generated/AndroidSelectorHasChild.cs @@ -28,6 +28,6 @@ namespace Microsoft.Playwright.Transport.Protocol; internal class AndroidSelectorHasChild { - [JsonPropertyName("selector")] - public AndroidSelector Selector { get; set; } = null!; + [JsonPropertyName("androidSelector")] + public AndroidSelector AndroidSelector { get; set; } = null!; } diff --git a/src/Playwright/Transport/Protocol/Generated/AndroidSelectorHasDescendant.cs b/src/Playwright/Transport/Protocol/Generated/AndroidSelectorHasDescendant.cs index 5e7665f56b..b93cbe6b7f 100644 --- a/src/Playwright/Transport/Protocol/Generated/AndroidSelectorHasDescendant.cs +++ b/src/Playwright/Transport/Protocol/Generated/AndroidSelectorHasDescendant.cs @@ -28,8 +28,8 @@ namespace Microsoft.Playwright.Transport.Protocol; internal class AndroidSelectorHasDescendant { - [JsonPropertyName("selector")] - public AndroidSelector Selector { get; set; } = null!; + [JsonPropertyName("androidSelector")] + public AndroidSelector AndroidSelector { get; set; } = null!; [JsonPropertyName("maxDepth")] public int? MaxDepth { get; set; } diff --git a/src/Playwright/Transport/Protocol/Generated/BrowserContextInitializer.cs b/src/Playwright/Transport/Protocol/Generated/BrowserContextInitializer.cs index 3ae3fb888f..f5060b1ec6 100644 --- a/src/Playwright/Transport/Protocol/Generated/BrowserContextInitializer.cs +++ b/src/Playwright/Transport/Protocol/Generated/BrowserContextInitializer.cs @@ -36,4 +36,7 @@ internal class BrowserContextInitializer : EventTargetInitializer [JsonPropertyName("tracing")] public Core.Tracing Tracing { get; set; } = null!; + + [JsonPropertyName("options")] + public Options Options { get; set; } = null!; } diff --git a/src/Playwright/Transport/Protocol/Generated/Metadata.cs b/src/Playwright/Transport/Protocol/Generated/Metadata.cs index ef3275f60b..ea50cfcbc0 100644 --- a/src/Playwright/Transport/Protocol/Generated/Metadata.cs +++ b/src/Playwright/Transport/Protocol/Generated/Metadata.cs @@ -31,8 +31,8 @@ internal class Metadata [JsonPropertyName("location")] public MetadataLocation Location { get; set; } = null!; - [JsonPropertyName("apiName")] - public string ApiName { get; set; } = null!; + [JsonPropertyName("title")] + public string Title { get; set; } = null!; [JsonPropertyName("internal")] public bool? Internal { get; set; } diff --git a/src/Playwright/Transport/Protocol/Generated/Options.cs b/src/Playwright/Transport/Protocol/Generated/Options.cs new file mode 100644 index 0000000000..8ae316c090 --- /dev/null +++ b/src/Playwright/Transport/Protocol/Generated/Options.cs @@ -0,0 +1,118 @@ +/* + * MIT License + * + * Copyright (c) Microsoft Corporation. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and / or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace Microsoft.Playwright.Transport.Protocol; + +internal class Options +{ + [JsonPropertyName("noDefaultViewport")] + public bool? NoDefaultViewport { get; set; } + + [JsonPropertyName("viewport")] + public ViewportSize Viewport { get; set; } = null!; + + [JsonPropertyName("screen")] + public ViewportSize Screen { get; set; } = null!; + + [JsonPropertyName("ignoreHTTPSErrors")] + public bool? IgnoreHTTPSErrors { get; set; } + + [JsonPropertyName("clientCertificates")] + public List ClientCertificates { get; set; } = null!; + + [JsonPropertyName("javaScriptEnabled")] + public bool? JavaScriptEnabled { get; set; } + + [JsonPropertyName("bypassCSP")] + public bool? BypassCSP { get; set; } + + [JsonPropertyName("userAgent")] + public string UserAgent { get; set; } = null!; + + [JsonPropertyName("locale")] + public string Locale { get; set; } = null!; + + [JsonPropertyName("timezoneId")] + public string TimezoneId { get; set; } = null!; + + [JsonPropertyName("geolocation")] + public OptionsGeolocation Geolocation { get; set; } = null!; + + [JsonPropertyName("permissions")] + public List Permissions { get; set; } = null!; + + [JsonPropertyName("extraHTTPHeaders")] + public List ExtraHTTPHeaders { get; set; } = null!; + + [JsonPropertyName("offline")] + public bool? Offline { get; set; } + + [JsonPropertyName("httpCredentials")] + public OptionsHttpCredentials HttpCredentials { get; set; } = null!; + + [JsonPropertyName("deviceScaleFactor")] + public int? DeviceScaleFactor { get; set; } + + [JsonPropertyName("isMobile")] + public bool? IsMobile { get; set; } + + [JsonPropertyName("hasTouch")] + public bool? HasTouch { get; set; } + + [JsonPropertyName("colorScheme")] + public string ColorScheme { get; set; } = null!; + + [JsonPropertyName("reducedMotion")] + public string ReducedMotion { get; set; } = null!; + + [JsonPropertyName("forcedColors")] + public string ForcedColors { get; set; } = null!; + + [JsonPropertyName("acceptDownloads")] + public string AcceptDownloads { get; set; } = null!; + + [JsonPropertyName("contrast")] + public string Contrast { get; set; } = null!; + + [JsonPropertyName("baseURL")] + public string BaseURL { get; set; } = null!; + + [JsonPropertyName("recordVideo")] + public OptionsRecordVideo RecordVideo { get; set; } = null!; + + [JsonPropertyName("strictSelectors")] + public bool? StrictSelectors { get; set; } + + [JsonPropertyName("serviceWorkers")] + public string ServiceWorkers { get; set; } = null!; + + [JsonPropertyName("selectorEngines")] + public List SelectorEngines { get; set; } = null!; + + [JsonPropertyName("testIdAttributeName")] + public string TestIdAttributeName { get; set; } = null!; +} diff --git a/src/Playwright/Transport/Protocol/Generated/OptionsClientCertificates.cs b/src/Playwright/Transport/Protocol/Generated/OptionsClientCertificates.cs new file mode 100644 index 0000000000..1bb3bd4d24 --- /dev/null +++ b/src/Playwright/Transport/Protocol/Generated/OptionsClientCertificates.cs @@ -0,0 +1,45 @@ +/* + * MIT License + * + * Copyright (c) Microsoft Corporation. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and / or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +using System.Text.Json.Serialization; + +namespace Microsoft.Playwright.Transport.Protocol; + +internal class OptionsClientCertificates +{ + [JsonPropertyName("origin")] + public string Origin { get; set; } = null!; + + [JsonPropertyName("cert")] + public byte[] Cert { get; set; } = null!; + + [JsonPropertyName("key")] + public byte[] Key { get; set; } = null!; + + [JsonPropertyName("passphrase")] + public string Passphrase { get; set; } = null!; + + [JsonPropertyName("pfx")] + public byte[] Pfx { get; set; } = null!; +} diff --git a/src/Playwright/Transport/Protocol/Generated/OptionsGeolocation.cs b/src/Playwright/Transport/Protocol/Generated/OptionsGeolocation.cs new file mode 100644 index 0000000000..ceff46f3fd --- /dev/null +++ b/src/Playwright/Transport/Protocol/Generated/OptionsGeolocation.cs @@ -0,0 +1,39 @@ +/* + * MIT License + * + * Copyright (c) Microsoft Corporation. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and / or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +using System.Text.Json.Serialization; + +namespace Microsoft.Playwright.Transport.Protocol; + +internal class OptionsGeolocation +{ + [JsonPropertyName("longitude")] + public int Longitude { get; set; } + + [JsonPropertyName("latitude")] + public int Latitude { get; set; } + + [JsonPropertyName("accuracy")] + public int? Accuracy { get; set; } +} diff --git a/src/Playwright/Transport/Protocol/Generated/OptionsHttpCredentials.cs b/src/Playwright/Transport/Protocol/Generated/OptionsHttpCredentials.cs new file mode 100644 index 0000000000..474ba3a54e --- /dev/null +++ b/src/Playwright/Transport/Protocol/Generated/OptionsHttpCredentials.cs @@ -0,0 +1,42 @@ +/* + * MIT License + * + * Copyright (c) Microsoft Corporation. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and / or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +using System.Text.Json.Serialization; + +namespace Microsoft.Playwright.Transport.Protocol; + +internal class OptionsHttpCredentials +{ + [JsonPropertyName("username")] + public string Username { get; set; } = null!; + + [JsonPropertyName("password")] + public string Password { get; set; } = null!; + + [JsonPropertyName("origin")] + public string Origin { get; set; } = null!; + + [JsonPropertyName("send")] + public string Send { get; set; } = null!; +} diff --git a/src/Playwright/Transport/Protocol/Generated/SelectorsInitializer.cs b/src/Playwright/Transport/Protocol/Generated/OptionsRecordVideo.cs similarity index 83% rename from src/Playwright/Transport/Protocol/Generated/SelectorsInitializer.cs rename to src/Playwright/Transport/Protocol/Generated/OptionsRecordVideo.cs index 60cea996a7..ae1239d2c8 100644 --- a/src/Playwright/Transport/Protocol/Generated/SelectorsInitializer.cs +++ b/src/Playwright/Transport/Protocol/Generated/OptionsRecordVideo.cs @@ -22,8 +22,15 @@ * SOFTWARE. */ +using System.Text.Json.Serialization; + namespace Microsoft.Playwright.Transport.Protocol; -internal class SelectorsInitializer +internal class OptionsRecordVideo { + [JsonPropertyName("dir")] + public string Dir { get; set; } = null!; + + [JsonPropertyName("size")] + public ViewportSize Size { get; set; } = null!; } diff --git a/src/Playwright/Transport/Protocol/Generated/PlaywrightInitializer.cs b/src/Playwright/Transport/Protocol/Generated/PlaywrightInitializer.cs index e053bdede4..a6b510da9a 100644 --- a/src/Playwright/Transport/Protocol/Generated/PlaywrightInitializer.cs +++ b/src/Playwright/Transport/Protocol/Generated/PlaywrightInitializer.cs @@ -46,9 +46,6 @@ internal class PlaywrightInitializer [JsonPropertyName("utils")] public Core.LocalUtils Utils { get; set; } = null!; - [JsonPropertyName("selectors")] - public Core.Selectors Selectors { get; set; } = null!; - [JsonPropertyName("preLaunchedBrowser")] public Core.Browser PreLaunchedBrowser { get; set; } = null!; diff --git a/src/Playwright/Transport/Protocol/Generated/RecordHarOptions.cs b/src/Playwright/Transport/Protocol/Generated/RecordHarOptions.cs index b27c6a52e7..80b4073bcd 100644 --- a/src/Playwright/Transport/Protocol/Generated/RecordHarOptions.cs +++ b/src/Playwright/Transport/Protocol/Generated/RecordHarOptions.cs @@ -28,8 +28,8 @@ namespace Microsoft.Playwright.Transport.Protocol; internal class RecordHarOptions { - [JsonPropertyName("path")] - public string Path { get; set; } = null!; + [JsonPropertyName("zip")] + public bool? Zip { get; set; } [JsonPropertyName("content")] public string Content { get; set; } = null!; diff --git a/src/Playwright/Transport/Protocol/Generated/SelectorEngine.cs b/src/Playwright/Transport/Protocol/Generated/SelectorEngine.cs new file mode 100644 index 0000000000..c6746a60e7 --- /dev/null +++ b/src/Playwright/Transport/Protocol/Generated/SelectorEngine.cs @@ -0,0 +1,39 @@ +/* + * MIT License + * + * Copyright (c) Microsoft Corporation. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and / or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +using System.Text.Json.Serialization; + +namespace Microsoft.Playwright.Transport.Protocol; + +internal class SelectorEngine +{ + [JsonPropertyName("name")] + public string Name { get; set; } = null!; + + [JsonPropertyName("source")] + public string Source { get; set; } = null!; + + [JsonPropertyName("contentScript")] + public bool? ContentScript { get; set; } +}